ReactNextCentral

라우트 핸들러

Published on
웹의 Request와 Response API를 사용하여 주어진 라우트에 대한 사용자 정의 요청 핸들러를 만듭니다.
Table of Contents

라우트 핸들러는 웹의 RequestResponse API를 사용하여 주어진 라우트에 대한 사용자 정의 요청 핸들러를 생성할 수 있게 해줍니다.

"Route.js 특별한 파일"

알아두면 좋은 정보: Page 라우터의 API 라우트와의 관계

라우트 핸들러는 app 디렉토리 내에서만 사용 가능합니다. pages 디렉토리 내의 API 라우트와 동등한 기능을 제공하므로 API 라우트와 라우트 핸들러를 함께 사용할 필요는 없습니다.


규칙

라우트 핸들러는 app 디렉토리 내 route.js|ts 파일에 정의됩니다.

app/api/route.ts
export async function GET(request: Request) {}
JavaScript
app/api/route.js
export async function GET(request) {}

라우트 핸들러는 page.jslayout.js와 마찬가지로 app 디렉토리 내에서 중첩될 수 있습니다. 그러나 page.js와 동일한 라우트 세그먼트 레벨에 route.js 파일이 있어서는 안됩니다.

지원하는 HTTP 메서드

다음 HTTP 메서드가 지원됩니다: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. 지원하지 않는 메서드가 호출되면 Next.js는 405 Method Not Allowed 응답을 반환합니다.

확장된 NextRequestNextResponse API

기본 RequestResponse를 지원하는 것 외에도, Next.js는 고급 사용 사례를 위한 편리한 도우미를 제공하기 위해 NextRequestNextResponse로 확장합니다.


동작

캐싱

라우트 핸들러는 Response 객체와 함께 GET 메서드를 사용할 때 기본적으로 캐싱됩니다.

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 })
}
JavaScript
app/items/route.js
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 })
}

타입스크립트 주의: Response.json()은 유효하지만, 기본 타입스크립트 타입은 현재 오류를 표시합니다. 대신 타입이 지정된 응답을 위해 NextResponse.json()을 사용할 수 있습니다.

캐싱에서 제외하기

캐싱에서 제외하려면 다음을 사용할 수 있습니다.

  • GET 메서드와 함께 Request 객체를 사용합니다.
  • 다른 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 })
}
JavaScript
app/products/api/route.js
import { NextResponse } from 'next/server'

export async function GET(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)
}
JavaScript
app/items/route.js
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 라우트와 마찬가지로 라우트 핸들러는 폼 제출 처리와 같은 경우에 사용될 수 있습니다. React와 깊게 통합되는 폼 및 변형 처리를 위한 새로운 추상화가 진행 중입니다.

라우트 결정 방식

route를 가장 낮은 수준의 라우팅 원시로 간주할 수 있습니다.

  • page처럼 레이아웃이나 클라이언트 측 네비게이션에 참여 하지 않습니다.
  • page.js와 같은 경로에 route.js 파일이 있을 수 없습니다.
페이지라우트결과
app/page.jsapp/route.js<Cross size={18} /> 충돌
app/page.jsapp/api/route.js<Check size={18} /> 유효
app/[user]/page.jsapp/api/route.js<Check size={18} /> 유효

route.js 또는 page.js 파일은 해당 라우트의 모든 HTTP 동사를 인수로 합니다.

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

// ❌ 충돌
// `app/route.js`
export async function POST(request) {}

예시

다음 예제들은 Route 핸들러를 Next.js의 다른 API와 기능과 어떻게 조합하는지 보여줍니다.

캐싱된 데이터 재검증하기

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)
}
JavaScript
app/items/route.js
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

동적 함수

Route 핸들러는 Next.js의 cookiesheaders와 같은 동적 함수와 함께 사용될 수 있습니다.

쿠키

next/headers에서 cookies를 사용하여 쿠키를 읽을 수 있습니다. 이 서버 함수는 Route 핸들러에서 직접 호출되거나 다른 함수 내에서 중첩될 수 있습니다. 이 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('안녕하세요, Next.js!', {
    status: 200,
    headers: { 'Set-Cookie': `token=${token.value}` },
  })
}
JavaScript
app/api/route.js
import { cookies } from 'next/headers'

export async function GET(request) {
  const cookieStore = cookies()
  const token = cookieStore.get('token')

  return new Response('안녕하세요, 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')
}
JavaScript
app/api/route.js
export async function GET(request) {
  const token = request.cookies.get('token')
}

헤더

next/headers에서 headers를 사용하여 헤더를 읽을 수 있습니다. 이 서버 함수는 Route 핸들러에서 직접 호출되거나 다른 함수 내에서 중첩될 수 있습니다. 이 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('안녕하세요, Next.js!', {
    status: 200,
    headers: { referer: referer },
  })
}
JavaScript
app/api/route.js
import { headers } from 'next/headers'

export async function GET(request) {
  const headersList = headers()
  const referer = headersList.get('referer')

  return new Response('안녕하세요, 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)
}
JavaScript
app/api/route.js
export async function GET(request) {
  const requestHeaders = new Headers(request.headers)
}

리다이렉트

app/api/route.ts
import { redirect } from 'next/navigation'

export async function GET(request: Request) {
  redirect('https://nextjs.org/')
}
JavaScript
app/api/route.js
import { redirect } from 'next/navigation'

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

동적 라우트 세그먼트

계속하기 전에 라우트 정의하기 페이지를 읽어보는 것을 권장합니다.

라우트 핸들러는 동적 세그먼트를 사용하여 동적 데이터로부터 요청 핸들러를 생성할 수 있습니다.

app/items/[slug]/route.ts
export async function GET(
  request: Request,
  { params }: { params: { slug: string } }
) {
  const slug = params.slug // 'a', 'b', 또는 'c'
}
JavaScript
app/items/[slug]/route.js
export async function GET(request, { params }) {
  const slug = params.slug // 'a', 'b', 또는 'c'
}
라우트예시 URLparams
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' }

스트리밍

스트리밍은 AI 생성 컨텐츠에 주로 OpenAI와 같은 대형 언어 모델(LLMs)과 함께 사용됩니다. AI SDK에 대해 더 자세히 알아보세요.

app/api/chat/route.ts
import { Configuration, OpenAIApi } from 'openai-edge'
import { OpenAIStream, StreamingTextResponse } from 'ai'

export const runtime = 'edge'

const apiConfig = new Configuration({
  apiKey: process.env.OPENAI_API_KEY!,
})

const openai = new OpenAIApi(apiConfig)

export async function POST(req: Request) {
  // 요청의 본문에서 `messages` 추출하기
  const { messages } = await req.json()

  // 프롬프트를 기반으로 OpenAI API에 응답 요청하기
  const response = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    stream: true,
    messages: messages,
    max_tokens: 500,
    temperature: 0.7,
    top_p: 1,
    frequency_penalty: 1,
    presence_penalty: 1,
  })

  // 응답을 사용자 친화적인 텍스트 스트림으로 변환하기
  const stream = OpenAIStream(response)

  // 스트림으로 응답하기
  return new StreamingTextResponse(stream)
}
JavaScript
app/api/chat/route.js
import { Configuration, OpenAIApi } from 'openai-edge'
import { OpenAIStream, StreamingTextResponse } from 'ai'

export const runtime = 'edge'

const apiConfig = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
})

const openai = new OpenAIApi(apiConfig)

export async function POST(req) {
  // 요청의 본문에서 `messages` 추출하기
  const { messages } = await req.json()

  // 프롬프트를 기반으로 OpenAI API에 응답 요청하기
  const response = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    stream: true,
    messages: messages,
    max_tokens: 500,
    temperature: 0.7,
    top_p: 1,
    frequency_penalty: 1,
    presence_penalty: 1,
  })

  // 응답을 사용자 친화적인 텍스트 스트림으로 변환하기
  const stream = OpenAIStream(response)

  // 스트림으로 응답하기
  return new StreamingTextResponse(stream)
}

이런 추상화는 스트림을 생성하기 위해 웹 API를 사용합니다. 또한 기본 웹 API를 직접 사용할 수도 있습니다.

app/api/route.ts
// https://developer.mozilla.org/\
// 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)
}
JavaScript
app/api/route.js
// https://developer.mozilla.org/\
// docs/Web/API/ReadableStream#convert_async_iterator_to_stream
function iteratorToStream(iterator) {
  return new ReadableStream({
    async pull(controller) {
      const { value, done } = await iterator.next()

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

function sleep(time) {
  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)
}

요청 본문

표준 웹 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 })
}
JavaScript
app/items/route.js
import { NextResponse } from 'next/server'

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

요청 본문의 FormData

request.formData() 함수를 사용하여 FormData를 읽을 수 있습니다.

app/items/route.ts
import { NextResponse } from 'next/server'

export async function POST(request: Request) {
  const formData = await request.formData()
  const name = formData.get('name')
  const email = formData.get('email')
  return NextResponse.json({ name, email })
}
JavaScript
app/items/route.js
import { NextResponse } from 'next/server'

export async function POST(request) {
  const formData = await request.formData()
  const name = formData.get('name')
  const email = formData.get('email')
  return NextResponse.json({ name, email })
}

formData의 데이터는 모두 문자열이므로, 원하는 형식(예: number)으로 요청을 검증하고 데이터를 검색하기 위해 zod-form-data를 사용하려고 할 수 있습니다.

CORS

표준 웹 API 메서드를 사용하여 Response에 CORS 헤더를 설정할 수 있습니다.

"app/api/route.ts"
export async function GET(request: Request) {
  return new Response('안녕, 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',
    },
  })
}
JavaScript
app/api/route.js
export async function GET(request) {
  return new Response('안녕, 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',
    },
  })
}

엣지 및 Node.js 런타임

라우트 핸들러는 스트리밍을 포함하여 엣지와 Node.js 런타임을 모두 원활하게 지원하기 위해 동형 웹 API를 가지고 있습니다. 라우트 핸들러는 페이지와 레이아웃과 동일한 라우트 세그먼트 구성을 사용하므로 일반적으로 요청되는 기능인 정적으로 재생성된 라우트 핸들러를 지원합니다.

runtime 세그먼트 구성 옵션을 사용하여 런타임을 지정할 수 있습니다.

export const runtime = 'edge' // 기본값은 'nodejs'

비-UI 응답

라우트 핸들러를 사용하여 비-UI 콘텐츠를 반환할 수 있습니다. sitemap.xml, robots.txt, 앱 아이콘, 및 오픈 그래프 이미지 모두 내장 지원이 있습니다.

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 문서</title>
  <link>https://nextjs.org/docs</link>
  <description>웹을 위한 React 프레임워크</description>
</channel>

</rss>`)
}
JavaScript
app/rss.xml/route.js
export async function GET() {
  return new Response(`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">

<channel>
  <title>Next.js 문서</title>
  <link>https://nextjs.org/docs</link>
  <description>웹을 위한 React 프레임워크</description>
</channel>

</rss>`)
}

세그먼트 구성 옵션

라우트 핸들러는 페이지와 레이아웃과 동일한 라우트 세그먼트 구성을 사용합니다.

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'
JavaScript
app/items/route.js
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 참조를 참조하세요.