라우트 핸들러를 사용하면 Web Request/Response API를 활용해 특정 경로에 대한 커스텀 요청 핸들러를 만들 수 있다.
라우트 핸들러는 app 디렉터리 내의 route.js|ts 파일에 정의한다.
// app/api/route.ts
export async function GET(request: Request) {}
page.js, layout.js와 마찬가지로 app 디렉터리 안 어디에든 중첩해서 배치할 수 있다. 단, route.js와 page.js는 동일한 경로 세그먼트에 함께 존재할 수 없다.
라우트 핸들러는
app디렉터리 전용이다.pages디렉터리의 API 라우트와 동일한 역할을 하므로 둘을 함께 사용할 필요는 없다.
GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS를 지원한다. 지원하지 않는 메서드를 호출하면 Next.js가 405 Method Not Allowed 응답을 반환한다.
기본 Request/Response 기능 외에도, Next.js는 NextRequest와 NextResponse를 확장하여 고급 사용 사례에 유용한 헬퍼를 제공한다.
라우트 핸들러는 기본적으로 캐시되지 않는다. GET 메서드에 한해 캐시를 활성화할 수 있으며, 라우트 파일에서 export const dynamic = 'force-static'을 선언하면 된다.
// app/items/route.ts
export const dynamic = 'force-static'
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 Response.json({ data })
}
GET과 함께 같은 파일에 정의된 다른 HTTP 메서드는 캐시 대상이 아니다.
캐시 컴포넌트가 활성화되면 GET 라우트 핸들러는 일반 UI 라우트와 동일한 렌더링 모델을 따른다. 기본적으로 요청 시점에 실행되며, 동적 데이터나 런타임 데이터에 접근하지 않으면 사전 렌더링될 수 있고, use cache를 통해 정적 응답에 동적 데이터를 포함시킬 수도 있다.
정적 예시 — 동적 데이터나 런타임 데이터에 접근하지 않으므로 빌드 시점에 사전 렌더링된다.
// app/api/project-info/route.ts
export async function GET() {
return Response.json({
projectName: 'Next.js',
})
}
동적 예시 — 비결정적 연산에 접근한다. Math.random()이 호출되면 사전 렌더링이 중단되고 요청 시점 렌더링으로 전환된다.
// app/api/random-number/route.ts
export async function GET() {
return Response.json({
randomNumber: Math.random(),
})
}
런타임 데이터 예시 — 요청별 데이터에 접근한다. headers()와 같은 런타임 API가 호출되면 사전 렌더링이 종료된다.
// app/api/user-agent/route.ts
import { headers } from 'next/headers'
export async function GET() {
const headersList = await headers()
const userAgent = headersList.get('user-agent')
return Response.json({ userAgent })
}
GET핸들러가 네트워크 요청, DB 쿼리, 비동기 파일 시스템 작업, 요청 객체 속성(req.url,request.headers등), 런타임 API(cookies(),headers(),connection()등), 또는 비결정적 연산에 접근하면 사전 렌더링이 중단된다.
캐시 예시 — 동적 데이터(DB 쿼리)에 접근하지만, use cache를 통해 사전 렌더링된 응답에 포함시킨다.
// app/api/products/route.ts
import { cacheLife } from 'next/cache'
export async function GET() {
const products = await getProducts()
return Response.json(products)
}
async function getProducts() {
'use cache'
cacheLife('hours')
return await db.query('SELECT * FROM products')
}
use cache는 라우트 핸들러 본문에서 직접 사용할 수 없다. 별도의 헬퍼 함수로 분리해 사용해야 한다. 캐시된 응답은cacheLife에 설정된 주기에 따라 재검증된다.