Next.js 13 - 1. Routing - 1.7. Route Handlers

Chaewon Kang·2023년 4월 26일
3

Route Handlers

루트 핸들러는 웹의 요청/응답 API들을 이용하여 특정 루트에 커스텀 리퀘스트 핸들러를 생성할 수 있도록 도와줍니다.

알아두면 좋은 것: 루트 핸들러는 app디렉토리 내에서만 사용할 수 있습니다. 루트 핸들러들은 pages 디렉토리 내의 API Routes 와 동일하며, 이는 API Routes와 루트 핸들러를 함께 사용하지 않는다는 것을 의미합니다.

Convention (규약)

루트 핸들러는 app디렉토리 내의 route.js | ts파일에 정의합니다:

// app/api/route.ts

export async function GET(request: Request) {}

루트 핸들러는 page.jslayout.js와 비슷하게 app 디렉토리 내에 중첩될 수 있습니다. 하지만 page.js와 같은 루트 세그먼트 레벨에는 route.js파일이 올 수 없습니다.

Supported HTTP Methods (지원되는 HTTP 메서드)

다음과 같은 HTTP 메서드들이 사용 가능합니다: GET, POST, PATCH, DELETE, HEAD, 그리고 OPTIONS. 지원되지 않는 메서드가 호출되면, Next.js는 405 Method Not Allowed 응답을 리턴합니다.

Extended NextRequest and NextResponse APIs (확장된 NextRequestNextResponse API)

네이티브 Request와 Response를 지원하는 것에 더불어, Next.js는 이 둘을 NextRequest, 그리고 NextResponse와 함께 확장하여 더 향상된 케이스들을 위해 편리한 도구들을 제공합니다

Behavior (행동 양식)

Static Route Handler (정적 루트 핸들러)

GET 메서드를 Response객체와 함께 이용할 경우, 루트 핸들러들은 기본적으로 정적으로 여겨집니다

// app/items/route.ts

import { NextResponse } from 'next/server';

export async function GET() {
	const res = await fetch('https://data.mangodb-api/com/...', {
    	headers: {
        	'Content-Type': 'application/json',
            'API-Key': process.env.DATA_API_KEY,
        }
    });
    const data = await res.json();
    
    return NextResponse.json({ data })
}

타입스크립트 경고: Response.json()은 유효하지만, 네이티브 타입스크립트의 타입들은 현재 에러를 보여줍니다. 타이핑된 응답에 대해서 NextResponse.json()을 대신 사용할 수 있습니다.

정적 페칭은 robots.txt, rss.xml, sitemap.xml 등의 루트들을 위한 커스텀 루트 핸들러를 생성하는 데에 유용할 수 있습니다. 예시를 확인하세요.

Dynamic Route Handlers (동적 루트 핸들러)

루트 핸들러는 다음과 같은 경우에 동적이라고 여겨집니다:

  • Request 객체를 GET 메서드와 함께 사용하는 경우
  • 다른 HTTP 메서드들을 사용하는 경우
  • cookiesheaders와 같은 동적 함수들을 사용하는 경우

예를 들어:

// app/products/api/route.ts

import { NextResponse } from 'next/server';

export async function GET(request: Request) {
	const { searchParams } = new URL(request.url);
    const id = searchParams.get('id');
    const res = await fetch(`https://data.mongodb-api.com/product/${id}`, {
    	headers: {
        	'Content-Type': 'application/json',
            'API-Key': process.env.DATA_API_KEY,
        }
    });
    const product = await res.json();
    
    return NextResponse.json({ product })
}

이와 비슷하게, POST 메서드도 루트 핸들러가 동적으로 여겨지도록 합니다

// app/items/route.ts

import { NextResponse } from 'next/server';

export async function POST() {
	const res = await fetch('https://data.mongodb-api.com/...', {
    	method: 'POST',
        headers: {
        	'Content-Type': 'application/json',
            'API-Key': process.env.DATA_API_KEY,
        },
        body: JSON.stringify({ time: new Date().toISOString() }),
    });
    
    const data = await res.json();
    
    return NextResponse.json(data);
}

노트 : 이전에는, API Routes 양식 제출과 같은 케이스들을 핸들링하는 데에 사용될 수 있었습니다. Route Handlers 는 이러한 케이스에 대한 해결책은 될 수 없습니다. 우리가 준비되면 mutations를 추천할게요.

Route Resolution (루트 해결)

route를 가장 낮은 라우팅 원시 레벨로 고려할 수 있습니다.

  • page와 같은 클라이언트 사이드 네비게이션이나 레이아웃에 관여하지 않습니다.
  • route.js 파일은 page.js 와 같은 루트에 위치할 수 없습니다.

각각의 route.jspage.js는 해당 루트를 위한 모든 HTTP 동사들을 차지합니다.

// `app/page.js`

export default function Page() {
	return <h1>Hello, Next.js!</h1>
}

// ❌ 충돌이 일어납니다
// `app/route.js`
export async function POST(request) {}

Examples (예시들)

아래의 예시들은 루트 핸들러를 다른 Next.js API 및 기능들과 어떻게 결합할 수 있는지 보여줍니다.

Revalidating Static Data (정적 데이터를 다시 유효화하기)

next.revalidate 옵션을 사용해서, 정적 데이터 페칭을 다시 유효화할 수 있습니다.

// app/items/route.ts

import { NextResponse } from 'next/server';

export async function GET() {
	const res = await fetch('https://data.mongodb-api.com/...', {
    	next: { revalidate: 60 } // 매 60초마다 다시 유효화됩니다
    });
    const data = await res.json();
    
    return NextResponse.json(data)
}

혹은, revalidate 세그먼트 설정 옵션을 사용할 수 있습니다:

export const revalidate = 60;

Dynamic Functions (동적 함수들)

루트 핸들러는 cookiesheaders와 같은 Next.js의 동적 함수들과 함께 이용될 수 있습니다.

Cookies (쿠키)

next/headerscookies를 통해 쿠키를 읽어올 수 있습니다. 이 서버 함수는 루트 핸들러 안에서, 또는 다른 함수의 내부에서 다이렉트로 호출될 수 있습니다.

cookies 인스턴스는 읽기 전용입니다. 쿠키를 세팅하려면, Set-Cookie헤더를 이용해서 새로운 Response객체를 리턴해야 합니다.

// app/api/route.ts

import { cookies } from 'next/headers'

export async function GET(request: Request) {
	const cookieStore = cookies();
    const token = cookieStore.get('token');
    
    return new Response('Hello, Next.js!, {
    	status: 200,
        headers: { 'Set-Cookie': `token=${token}` }
    });
}

대안으로는, 기저의 WEB API의 가장 상단 추상화를 이용해서 쿠키를 읽어올 수 있습니다 (NextRequest):

// app/api/route.ts

import { type NextRequest } from 'next/server'

export async function GET(request: NextRequest) {
	const token = request.cookies.get('token');
}

Headers (헤더)

next/headersheaders를 이용해서 헤더를 읽어올 수 있습니다. 이 서버 함수는 루트 핸들러 안에서, 또는 다른 함수의 내부에서 다이렉트로 호출될 수 있습니다.

headers 인스턴스는 읽기 전용입니다. 헤더를 세팅하려면, 새로운 headers를 통해 Response객체를 리턴해야 합니다.

// app/api/route.ts

import { headers } from 'next/headers';

export async function GET(request: Request) {
	const headersList = headers();
    const referer = headersList.get('referer');
    
    return new Response('Hello, Next.js!', {
    	status: 200,
        headers: { 'referer': referer }
    });
}

대안으로는, 기저의 WEB API의 가장 상단 추상화를 이용해서 헤더를 읽어올 수 있습니다 (NextRequest):

// app/api/route.ts

import { type NextRequest } from 'next/server';

export async function GET(request: NextRequest) {
	const requestHeaders = new Headers(request.headers)
}

Redirects (리다이렉트)

// app/api/route.ts

import { redirect } from 'next/navigation';

export async function GET(request: Request) {
	redirect('https://nextjs.org/')
}

Dynamic Route Segment (다이나믹 루트 세그먼트)

루트 핸들러는 동적 데이터로부터 리퀘스트 핸들러를 생성할 수 있도록 동적 세그먼트들을 이용할 수 있습니다.

// app/items/[slug]/route.js

export async function GET(request: Request, { params }: {
	params: { slug: string }
}) {
	const slug = params.slug; // 'a', 'b', 또는 'c'
}

노트: generateStaticParams()는 아직 라우트 핸들러와 사용할 수 없습니다

Streaming (스트리밍)

// app/api/route.ts

// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
function iteratorToStream(iterator: any) {
  return new ReadableStream({
    async pull(controller) {
      const { value, done } = await iterator.next()

      if (done) {
        controller.close()
      } else {
        controller.enqueue(value)
      }
    },
  })
}

function sleep(time: number) {
  return new Promise((resolve) => {
    setTimeout(resolve, time)
  })
}

const encoder = new TextEncoder()

async function* makeIterator() {
  yield encoder.encode('<p>One</p>')
  await sleep(200)
  yield encoder.encode('<p>Two</p>')
  await sleep(200)
  yield encoder.encode('<p>Three</p>')
}

export async function GET() {
  const iterator = makeIterator()
  const stream = iteratorToStream(iterator)

  return new Response(stream)
}

Request Body (리퀘스트 바디)

기존의 Web API 메서드를 이용하여 Request바디를 읽을 수 있습니다:

// app/items/route.ts

import { NextResponse } from 'next/server';

export async function POST(request: Request) {
	const res = await request.json();
    return NextResponse.json({ res })
}

CORS

기존의 Web API 메서드를 이용하여 Response에 CORS 헤더를 적용할 수 있습니다.

// app/api/route.ts

export async function GET(request: Request) {
  return new Response('Hello, Next.js!', {
    status: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    }
  });
}

Edge and Node.js Runtimes (엣지와 Node.js 런타임)

루트 핸들러는 스트리밍 지원을 포함하여 엣지와 Node.js 런타임을 매끄럽게 지원할 수 있는 isomorphic 웹 API를 가지고 있습니다. 루트 핸들러는 페이지와 레이아웃과 같은 루트 세그먼트 설정을 이용하기 때문에, 일반적인 목적의 정적으로 재생성된 루트 핸들러와 같은 오래 기다려온 기능들을 지원합니다.

런타임을 지정하기 위해서는 runtime 세그먼트 설정 옵션을 사용할 수 있습니다:

export const runtime = 'edge' // 'nodejs'가 기본입니다.

Non-UI Responses (UI가 아닌 응답들)

UI가 아닌 컨텐츠를 리턴하기 위해 루트 핸들러를 사용할 수 있습니다. sitemap.xml, robots.txt, favicon.ico 그리고 오픈그래프 이미지들 모두 빌트인 SEO 서포트를 가지고 있습니다.

// app/rss.xml/route.ts

export async function GET() {
  return new Response(`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">

<channel>
  <title>Next.js Documentation</title>
  <link>https://nextjs.org/docs</link>
  <description>The React Framework for the Web</description>
</channel>

</rss>`);
}

Segment Config Options (세그먼트 설정 옵션들)

루트 핸들러는 페이지와 레이아웃과 같은 루트 세그먼트 설정을 사용합니다.

// app/items/route.ts

export const dynamic = 'auto'
export const dynamicParams = true
export const revalidate = false
export const fetchCache = 'auto'
export const runtime = 'nodejs'
export const preferredRegion = 'auto'

더 많은 정보는 API 레퍼런스를 참조하세요.

profile
문학적 상상력과 기술적 가능성

0개의 댓글