Post

Next.js (15. Route Handlers) 정리

Next.js (15. Route Handlers) 정리

0. Route Handlers


Route Handlers는 Web Request / Response API를 사용해 특정 라우트에 대한 커스텀 요청 핸들러를 만드는 기능이다. app 디렉토리 안에서만 사용할 수 있으며, pages 디렉토리의 API Routes와 동일한 역할을 한다.

Route.js Special File

1. 기본 개념과 규칙


1-1. 정의 방식


app 디렉토리 안에 route.ts 파일을 만들고 HTTP 메서드명으로 함수를 export한다.

1
2
3
// app/api/route.ts

export async function GET(request: Request) {}

route.tsapp 디렉토리 어디에나 중첩 배치할 수 있다.
단, page.ts와 동일한 라우트 세그먼트에 route.ts를 배치하면 충돌이 발생한다.

PageRoute결과
app/page.jsapp/route.js충돌
app/page.jsapp/api/route.js정상
app/[user]/page.jsapp/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는 NextRequestNextResponse를 제공해 고급 사용 사례를 위한 편의 헬퍼를 추가한다.

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 본문에 직접 사용할 수 없다. 반드시 별도의 헬퍼 함수로 추출해서 사용해야 한다.

This post is licensed under CC BY 4.0 by the author.