Next.js (15. Route Handlers) 정리
0. Route Handlers
Route Handlers는 Web Request / Response API를 사용해 특정 라우트에 대한 커스텀 요청 핸들러를 만드는 기능이다. app 디렉토리 안에서만 사용할 수 있으며, pages 디렉토리의 API Routes와 동일한 역할을 한다.
1. 기본 개념과 규칙
1-1. 정의 방식
app 디렉토리 안에 route.ts 파일을 만들고 HTTP 메서드명으로 함수를 export한다.
1
2
3
// app/api/route.ts
export async function GET(request: Request) {}
route.ts는 app 디렉토리 어디에나 중첩 배치할 수 있다.
단, page.ts와 동일한 라우트 세그먼트에 route.ts를 배치하면 충돌이 발생한다.
| Page | Route | 결과 |
|---|---|---|
app/page.js | app/route.js | 충돌 |
app/page.js | app/api/route.js | 정상 |
app/[user]/page.js | app/api/route.js | 정상 |
1-2. 지원 HTTP 메서드와 확장 API
GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS 메서드를 지원한다. 지원하지 않는 메서드를 호출하면 Next.js가 405 Method Not Allowed를 반환한다.
네이티브 Request / Response API 외에도 Next.js는 NextRequest와 NextResponse를 제공해 고급 사용 사례를 위한 편의 헬퍼를 추가한다.
1-3. TypeScript — RouteContext
TypeScript에서 Route Handler의 context 파라미터는 전역 RouteContext 헬퍼로 타입을 지정할 수 있다.
1
2
3
4
5
6
7
8
// app/users/[id]/route.ts
import type { NextRequest } from 'next/server'
export async function GET(_req: NextRequest, ctx: RouteContext<'/users/[id]'>) {
const { id } = await ctx.params
return Response.json({ id })
}
RouteContext타입은next dev,next build,next typegen실행 시 자동 생성된다.
2. 캐싱
2-1. 기본 캐싱
Route Handlers는 기본적으로 캐시되지 않는다. GET 메서드에 한해 라우트 파일에 export const dynamic = 'force-static'을 추가해 캐싱을 활성화할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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외의 메서드는 캐시되지 않는다.
2-2. Cache Components 사용 시
Cache Components가 활성화된 경우 GET Route Handler는 일반 UI 라우트와 동일한 프리렌더링 모델을 따른다.
| 상황 | 동작 |
|---|---|
| 비결정적 연산이나 런타임 데이터에 접근하지 않음 | 빌드 타임에 프리렌더링 |
Math.random() 등 비결정적 연산 사용 | 프리렌더링 중단, 요청 시 렌더링 |
headers(), cookies() 등 런타임 API 사용 | 프리렌더링 중단, 요청 시 렌더링 |
비캐시 데이터를 use cache로 감쌈 | 프리렌더링 응답에 포함 가능 |
정적 예시 — 비캐시 데이터나 런타임 데이터에 접근하지 않아 빌드 타임에 프리렌더링된다.
1
2
3
4
5
6
7
// app/api/project-info/route.ts
export async function GET() {
return Response.json({
projectName: 'Next.js',
})
}
동적 예시 — Math.random()과 같은 비결정적 연산(non-deterministic)에 접근하면 빌드 타임 프리렌더링이 중단되고 요청 시점에 렌더링된다.
1
2
3
4
5
6
7
// app/api/random-number/route.ts
export async function GET() {
return Response.json({
randomNumber: Math.random(),
})
}
런타임 데이터 예시 — headers() 같은 런타임 API 호출 시 프리렌더링이 중단된다.
1
2
3
4
5
6
7
8
9
10
// 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 })
}
캐시 예시 — 비캐시 데이터를 use cache로 감싸 프리렌더링 응답에 포함시킨다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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는 Route Handler 본문에 직접 사용할 수 없다. 반드시 별도의 헬퍼 함수로 추출해서 사용해야 한다.

