라우터 핸들러를 사용하면 웹 요청 및 응답 API를 사용하여 지정된 경로에 대한 custom request handler를 만들 수 있습니다.
라우트 핸들러는 app 디렉터리 내에서만 사용할 수 있습니다. 이는 pages 디렉터리 내의 API 라우트와 동일한 역할을 담당하기 때문에 API 라우트와 라우트 핸들러를 함께 사용할 필요가 없다는 것을 의미합니다.
Convention
라우트 핸들러는 앱 디렉토리 내의 route.js|ts 파일에 정의됩니다.
/* app/api/route.ts */
export async function GET(request: Request) {}
라우트 핸들러는 page.js 및 layout.js와 유사하게 앱 디렉터리 내에서 중첩될 수 있습니다. 그러나 page.js와 동일한 라우트 세그먼트 수준에 route.js 파일이 존재할 수는 없습니다.
지원되는 HTTP 메서드
다음 HTTP 메서드가 지원됩니다: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. 지원되지 않는 메서드를 호출하면 Next.js는 405 Method Not Allowed 응답을 반환합니다.
확장된 NextRequest 및 NextResponse API
Next.js는 네이티브 Request와 Response를 지원하는 것 외에도, NextRequest와 NextResponse를 확장하여 고급 사용 사례에 편리한 도우미를 제공합니다.
Behavior
Static Route Handlers
GET 메서드와 Response 객체를 사용할 때, 라우트 핸들러는 기본적으로 정적으로 평가됩니다.
/*app/items/route.ts*/
import { NextResponse } from 'next/server'
export async function GET() {
const res = await fetch('<https://data.mongodb-api.com/>...', {
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY,
},
})
const data = await res.json()
return NextResponse.json({ data })
}
TypeScript 경고: Response.json()이 유효하지만 기본 TypeScript 유형은 현재 오류를 표시하므로 입력된 응답에 대신 NextResponse.json()을 사용할 수 있습니다.
Dynamic Route Handlers
라우트 핸들러는 다음과 같은 경우에 동적으로 평가됩니다.
- GET 메서드를 사용하여 Request 객체를 사용할 때.
- 다른 HTTP 메서드를 사용할 때.
- 쿠키와 헤더와 같은 동적 함수를 사용할 때.
- Segment Config 옵션에서 동적 모드를 수동으로 지정할 때.
/*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 메서드를 사용하면 라우트 핸들러가 동적으로 평가됩니다.
이전에는 API 라우트를 폼 제출 처리와 같은 사용 사례에 라우트 핸들러의 POST메서드를 사용했습니다. 그러나 라우트 핸들러는 이러한 사용 사례에 적합한 해결책이 아닐 수 있습니다. 해당 용도에는 mutaions의 사용을 권장할 예정입니다.
Route Resolution
- 라우트는 레이아웃이나 페이지 같은 클라이언트 측 네비게이션에 참여하지 않습니다.
- route.js 파일은 page.js와 같은 경로에 있을 수 없습니다.
Page | Route | Result |
app/page.js | app/route.js | X |
app/page.js | app/api/route.js | O |
app/[user]/page.js | app/api/route.js | O |
각 route.js 또는 page.js 파일은 해당 경로에 대한 모든 HTTP 메서드를 대신합니다.
Examples
다음 예제는 Route Handler를 다른 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 }, // Revalidate every 60 seconds
})
const data = await res.json()
return NextResponse.json(data)
}
또는 재검증 세그먼트 구성 옵션을 사용할 수 있습니다.
export const revalidate = 60
Dynamic Functions
| 동적 함수
라우트 핸들러는 쿠키 및 헤더와 같은 Next.js의 동적 함수와 함께 사용할 수 있습니다.
Cookies
next/headers의 cookies를 사용하여 쿠키를 읽을 수 있습니다. 이 서버 함수는 라우트 핸들러에서 직접 호출하거나 다른 함수 내에 중첩하여 호출할 수 있습니다.
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}` },
})
}
or
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const token = request.cookies.get('token')
}
Headers
next/headers의 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 },
})
}
or
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const requestHeaders = new Headers(request.headers)
}
Dynamic Route Segments
라우트 핸들러는 동적 세그먼트를 사용하여 동적 데이터에서 request 핸들러를 생성할 수 있습니다.
/* app/items/[slug]/route.ts */
export async function GET(
request: Request,
{ params }: { params: { slug: string } }
) {
const slug = params.slug // 'a', 'b', or 'c'
}
Route | Example URL | params |
app/items/[slug]/route.js | /items/a | { slug: 'a' } |
app/items/[slug]/route.js | /items/b | { slug: 'b' } |
app/items/[slug]/route.js | /items/c | { slug: 'c' } |
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
표준 웹 API 메서드를 사용하여 요청 본문을 읽을 수 있습니다.
/* 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
표준 웹 API 메서드를 사용하여 응답에 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
라우트 핸들러는 스트리밍을 기능을 포함하고 Edge와 Node.js 런타임을 원활하게 지원하기 위해 isomorphic Web API를 지원합니다. 라우트 핸들러는 페이지와 레이아웃과 동일한 경로 세그먼트 구성을 사용하기 때문에, 일반적인 용도의 정적으로 재생성된 라우트 핸들러와 같은 long-awaited에 대한 기능을 지원합니다.
💡 "Isomorphic Web API"는 서버 측과 클라이언트 측에서 동일한 API를 사용하여 일관성 있는 개발을 지원하는 개념입니다. 이는 라우트 핸들러에서 사용되며, 라우트 핸들러는 서버 측과 클라이언트 측에서 실행될 수 있는 코드입니다.
"Isomorphic"이란 용어는 동일한 코드가 여러 환경에서 동작할 수 있는 것을 의미합니다. 웹 애플리케이션 개발에서는 서버 측과 클라이언트 측에서 코드를 공유하고 재사용할 수 있는 장점이 있습니다. 이러한 개념을 "Isomorphic Web"이라고 합니다.
라우트 핸들러의 경우, 서버 측에서 렌더링되거나 API 요청을 처리하는 데 사용될 수 있습니다. 이를 위해 라우트 핸들러는 Edge (서버) 및 Node.js (클라이언트) 런타임에서 모두 실행될 수 있는 API를 제공합니다. 따라서 동일한 라우트 핸들러 코드를 서버와 클라이언트 모두에서 사용할 수 있으며, 개발자는 일관성 있는 개발 경험을 얻을 수 있습니다.
또한, 라우트 핸들러는 스트리밍과 같은 기능을 지원하여 데이터의 효율적인 처리와 전송을 가능하게 합니다. 따라서 라우트 핸들러를 사용하면 서버와 클라이언트 간의 일관성 있는 데이터 처리와 전달이 가능해집니다.
런타임 세그먼트 구성 옵션을 사용하여 런타임을 지정할 수 있습니다.
export const runtime = 'edge' // 'nodejs' is the default
Non-UI Responses
라우트 핸들러를 사용하여 UI가 아닌 콘텐츠를 반환할 수 있습니다. sitemap.xml, robots.txt, app icons, 및 open graph images 모두 지원 기능이 내장되어 있습니다.
/* 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
라우트 핸들러는 페이지 및 레이아웃과 동일한 경로 세그먼트 구성을 사용합니다.
느낀점 및 활용방안
대부분의 서비스는 NestJS, Spring boot, Django등과 같은 백엔드 프레임워크를 사용해 RESTful 한 백엔드 서비스가 구현되어 있기 때문에 Route Handlers의 활용성이 크게 와닿지 않을 것입니다. (저 또한 그렇고요..)
만약 1인 개발자이고, 서비스의 규모가 작고, 추후에 기능이 추가되거나 변경될 가능성이 없고, 별도의 백엔드를 구현하고 싶지 않을 때라는 조건이 모두 성립되면 Route Handler를 사용해 Isomorphic 한 Web API를 구축하는 것도 좋은 방법이라고 생각합니다.
실제 서비스를 개발하면서 NextJS의 Route Handler기능을 사용한 사례는 다음과 같습니다:
- next auth를 사용한 인증 관리
- 백엔드 지원 없이 외부 API를 숨기고 싶을때
- 백엔드 지원 없이 임시로 CORS 문제를 해결해야 될 때
여러 경로를 통해 조사를 해봤지만 Route Handler에 대한 고도화된 활용사례는 없는 듯싶습니다. Route Handler를 이용한 대규모 서비스사례가 있다면 어떻게 구성했는지 궁금 하긴 하네요. 혹시 대규모 서비스에 적용한 사례를 알고 있다면 공유 부탁드립니다! 🙇🏻♂️
'기억보단 기록을 > Next JS (App Router)' 카테고리의 다른 글
NextJS 테스트 코드 작성하기 - 리액트 테스트에 대하여 (0) | 2023.09.13 |
---|---|
[NextJS 13] Routing - Middleware (0) | 2023.06.26 |
[NextJS 13] Routing - Intercepting Routes(라우트 가로채기) (0) | 2023.06.12 |
[NextJS 13] Routing - Parallel Routes (병렬 라우트) (0) | 2023.06.12 |
[NextJS 13] Routing - Error Handling(에러처리) (0) | 2023.06.10 |