๐ŸŽ‰ berenickt ๋ธ”๋กœ๊ทธ์— ์˜จ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ‰
Front
NextJs
12-Route Handlers

Route Handlers

๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ์›น ์š”์ฒญ ๋ฐ ์‘๋‹ต API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ง€์ •๋œ ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž ์ง€์ • ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Route Handlers

์•Œ์•„๋‘๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค:

  • Route ํ•ธ๋“ค๋Ÿฌ๋Š” app ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Route ํ•ธ๋“ค๋Ÿฌ๋Š” page ๋””๋ ‰ํ„ฐ๋ฆฌ ๋‚ด์˜ API ๊ฒฝ๋กœ์™€ ๋™์ผํ•˜๋ฏ€๋กœ, API ๊ฒฝ๋กœ์™€ Route ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

1. Convention(๊ทœ์น™)

๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” app ๋””๋ ‰ํ„ฐ๋ฆฌ ๋‚ด์˜ route.js|ts ํŒŒ์ผ์— ์ •์˜๋ฉ๋‹ˆ๋‹ค:

app/api/route.ts
1
export const dynamic = 'force-dynamic' // defaults to auto
2
export async function GET(request: Request) {}

๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” page.js ๋ฐ layout.js์™€ ์œ ์‚ฌํ•˜๊ฒŒ app ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์— ์ค‘์ฒฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ page.js์™€ ๋™์ผํ•œ ๊ฒฝ๋กœ ์„ธ๊ทธ๋จผํŠธ ์ˆ˜์ค€์—๋Š” route.js ํŒŒ์ผ์ด ์žˆ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.


1.1 ์ง€์›๋˜๋Š” HTTP ๋ฉ”์†Œ๋“œ

  • ์ง€์›๋˜๋Š” HTTP ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: GET, POST, PUT, PATCH, DELETE, HEAD ๋ฐ OPTIONS.
  • ์ง€์›๋˜์ง€ ์•Š๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด Next.js๋Š” 405 ๋ฉ”์„œ๋“œ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Œ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

1.2 ํ™•์žฅ๋œ NextRequest ๋ฐ NextResponse API

๊ธฐ๋ณธ Request ๋ฐ Response์„ ์ง€์›ํ•  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, Next.js๋Š” ๊ณ ๊ธ‰ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์œ„ํ•œ ํŽธ๋ฆฌํ•œ ๋„์šฐ๋ฏธ๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด NextRequest ๋ฐ NextResponse๋กœ ์ด๋ฅผ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค.


2. Behavior(ํ–‰๋™)

2.1 Caching

์‘๋‹ต ๊ฐ์ฒด์™€ ํ•จ๊ป˜ GET ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์บ์‹œ๋ฉ๋‹ˆ๋‹ค.

app/items/route.ts
1
export async function GET() {
2
const res = await fetch('https://data.mongodb-api.com/...', {
3
headers: {
4
'Content-Type': 'application/json',
5
'API-Key': process.env.DATA_API_KEY,
6
},
7
})
8
const data = await res.json()
9
10
return Response.json({ data })
11
}

TypeScript ๊ฒฝ๊ณ :

  • Response.json()์€ TypeScript 5.2๋ถ€ํ„ฐ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค.
  • ํ•˜์œ„ ๋ฒ„์ „์˜ TypeScript๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์ž…๋ ฅ๋œ ์‘๋‹ต์— NextResponse.json()์„ ๋Œ€์‹  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2.2 Opting out of caching

์บ์‹ฑ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • GET ๋ฉ”์„œ๋“œ์™€ ํ•จ๊ป˜ ์š”์ฒญ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๋‹ค๋ฅธ HTTP ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ.
  • ์ฟ ํ‚ค ๋ฐ ํ—ค๋”์™€ ๊ฐ™์€ ๋™์  ํ•จ์ˆ˜ ์‚ฌ์šฉ.
  • ์„ธ๊ทธ๋จผํŠธ ๊ตฌ์„ฑ ์˜ต์…˜์—์„œ ๋™์  ๋ชจ๋“œ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด:

app/products/api/route.ts
1
export async function GET(request: Request) {
2
const { searchParams } = new URL(request.url)
3
const id = searchParams.get('id')
4
const res = await fetch(`https://data.mongodb-api.com/product/${id}`, {
5
headers: {
6
'Content-Type': 'application/json',
7
'API-Key': process.env.DATA_API_KEY!,
8
},
9
})
10
const product = await res.json()
11
12
return Response.json({ product })
13
}

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, POST ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋™์ ์œผ๋กœ ํ‰๊ฐ€๋ฉ๋‹ˆ๋‹ค.

app/items/route.ts
1
export async function POST() {
2
const res = await fetch('https://data.mongodb-api.com/...', {
3
method: 'POST',
4
headers: {
5
'Content-Type': 'application/json',
6
'API-Key': process.env.DATA_API_KEY!,
7
},
8
body: JSON.stringify({ time: new Date().toISOString() }),
9
})
10
11
const data = await res.json()
12
13
return Response.json(data)
14
}

์•Œ์•„๋‘๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค:

  • API ๊ฒฝ๋กœ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๊ฒฝ๋กœ ํ•ธ๋“ค๋Ÿฌ๋Š” ์–‘์‹ ์ œ์ถœ ์ฒ˜๋ฆฌ์™€ ๊ฐ™์€ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • React์™€ ๊ธด๋ฐ€ํ•˜๊ฒŒ ํ†ตํ•ฉ๋˜๋Š” ์–‘์‹ ๋ฐ ๋ณ€ํ˜•์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์ƒˆ๋กœ์šด ์ถ”์ƒํ™”๊ฐ€ ์ž‘์—… ์ค‘์ž…๋‹ˆ๋‹ค.

2.3 Route Resolution(ํ•ด๊ฒฐ)

route๋ฅผ ๊ฐ€์žฅ ๋‚ฎ์€ ์ˆ˜์ค€์˜ routing primitive.๋กœ ๊ฐ„์ฃผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • page์™€ ๊ฐ™์€ ๋ ˆ์ด์•„์›ƒ์ด๋‚˜ ํด๋ผ์ด์–ธํŠธ ์ธก ํƒ์ƒ‰์— ์ฐธ์—ฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • page.js์™€ ๊ฐ™์€ ๊ฒฝ๋กœ์— route.js ํŒŒ์ผ์ด ์žˆ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

You can consider a route the lowest level routing primitive.

  • They do not participate in layouts or client-side navigations like page.
  • There cannot be a route.js file at the same route as page.js.
PageRouteResult
app/page.jsapp/route.jsโŒ Conflict
app/page.jsapp/api/route.jsโœ… Valid
app/[user]/page.jsapp/api/route.jsโœ… Valid

๊ฐ route.js ๋˜๋Š” page.js ํŒŒ์ผ์€ ํ•ด๋‹น ๊ฒฝ๋กœ์˜ ๋ชจ๋“  HTTP ๋™์‚ฌ๋ฅผ ๋Œ€์‹ ํ•ฉ๋‹ˆ๋‹ค.

app/page.js
1
export default function Page() {
2
return <h1>Hello, Next.js!</h1>
3
}
4
5
// โŒ Conflict
6
// `app/route.js`
7
export async function POST(request) {}

3. ์˜ˆ์‹œ

๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋‹ค๋ฅธ Next.js API ๋ฐ ๊ธฐ๋Šฅ๊ณผ ๊ฒฐํ•ฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

3.1 ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ ์žฌ๊ฒ€์ฆ

next.revalidate ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ์˜ ์œ ํšจ์„ฑ์„ ์žฌ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

app/items/route.ts
1
export async function GET() {
2
const res = await fetch('https://data.mongodb-api.com/...', {
3
next: { revalidate: 60 }, // Revalidate every 60 seconds
4
})
5
const data = await res.json()
6
7
return Response.json(data)
8
}

๋˜๋Š”(Alternatively), ์„ธ๊ทธ๋จผํŠธ ๊ตฌ์„ฑ ์žฌ๊ฒ€์ฆ(revalidate) ์˜ต์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค:

1
export const revalidate = 60

3.2 ๋™์  ๊ธฐ๋Šฅ

๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ์ฟ ํ‚ค ๋ฐ ํ—ค๋”์™€ ๊ฐ™์€ Next.js์˜ ๋™์  ํ•จ์ˆ˜์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


3.2.1 Cookies

next/headers์˜ ์ฟ ํ‚ค๋กœ ์ฟ ํ‚ค๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์„œ๋ฒ„ ํ•จ์ˆ˜๋Š” ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ง์ ‘ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ํ•จ์ˆ˜ ์•ˆ์— ์ค‘์ฒฉํ•˜์—ฌ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜๋Š” Set-Cookie ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

app/api/route.ts
1
import { cookies } from 'next/headers'
2
3
export async function GET(request: Request) {
4
const cookieStore = cookies()
5
const token = cookieStore.get('token')
6
7
return new Response('Hello, Next.js!', {
8
status: 200,
9
headers: { 'Set-Cookie': `token=${token.value}` },
10
})
11
}

๊ธฐ๋ณธ ์›น API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์—์„œ ์ฟ ํ‚ค๋ฅผ ์ฝ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค(NextRequest):

app/api/route.ts
1
import { type NextRequest } from 'next/server'
2
3
export async function GET(request: NextRequest) {
4
const token = request.cookies.get('token')
5
}

3.2.2 Headers

next/headers์—์„œ ํ—ค๋”๋กœ ํ—ค๋”๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์„œ๋ฒ„ ํ•จ์ˆ˜๋Š” ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ง์ ‘ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ํ•จ์ˆ˜ ์•ˆ์— ์ค‘์ฒฉํ•˜์—ฌ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด headers ์ธ์Šคํ„ด์Šค๋Š” ์ฝ๊ธฐ ์ „์šฉ์ž…๋‹ˆ๋‹ค. headers๋ฅผ ์„ค์ •ํ•˜๋ ค๋ฉด, ์ƒˆ ํ—ค๋”๊ฐ€ ํฌํ•จ๋œ ์ƒˆ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

app/api/route.ts
1
import { headers } from 'next/headers'
2
3
export async function GET(request: Request) {
4
const headersList = headers()
5
const referer = headersList.get('referer')
6
7
return new Response('Hello, Next.js!', {
8
status: 200,
9
headers: { referer: referer },
10
})
11
}

๊ธฐ๋ณธ ์›น API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์˜ ํ—ค๋”๋ฅผ ์ฝ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค(NextRequest):

app/api/route.ts
1
import { type NextRequest } from 'next/server'
2
3
export async function GET(request: NextRequest) {
4
const requestHeaders = new Headers(request.headers)
5
}

3.3 Redirects(๋ฆฌ๋””๋ ‰์…˜)

app/api/route.ts
1
import { redirect } from 'next/navigation'
2
3
export async function GET(request: Request) {
4
redirect('https://nextjs.org/')
5
}

3.4 Dynamic Route Segments

๊ณ„์†ํ•˜๊ธฐ ์ „์— ๊ฒฝ๋กœ ์ •์˜ ํŽ˜์ด์ง€๋ฅผ ์ฝ์–ด๋ณด์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ๋™์  ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™์  ๋ฐ์ดํ„ฐ์—์„œ ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

app/items/[slug]/route.ts
1
export async function GET(request: Request, { params }: { params: { slug: string } }) {
2
const slug = params.slug // 'a', 'b', or 'c'
3
}
RouteExample 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' }

3.5 URL Query Parameters

๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌ๋˜๋Š” ์š”์ฒญ ๊ฐ์ฒด๋Š” ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ณด๋‹ค ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋“ฑ ๋ช‡ ๊ฐ€์ง€ ์ถ”๊ฐ€ ํŽธ์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋Š” NextRequest ์ธ์Šคํ„ด์Šค์ž…๋‹ˆ๋‹ค.

app/api/search/route.ts
1
import { type NextRequest } from 'next/server'
2
3
export function GET(request: NextRequest) {
4
const searchParams = request.nextUrl.searchParams
5
const query = searchParams.get('query')
6
// query is "hello" for /api/search?query=hello
7
}

3.6 Streaming

์ŠคํŠธ๋ฆฌ๋ฐ์€ ์ผ๋ฐ˜์ ์œผ๋กœ AI๊ฐ€ ์ƒ์„ฑํ•œ ์ฝ˜ํ…์ธ ์— OpenAI์™€ ๊ฐ™์€ ๋Œ€๊ทœ๋ชจ ์–ธ์–ด ๋ชจ๋ธ(LLM)๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. AI SDK์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”.

app/api/chat/route.ts
1
import OpenAI from 'openai'
2
import { OpenAIStream, StreamingTextResponse } from 'ai'
3
4
const openai = new OpenAI({
5
apiKey: process.env.OPENAI_API_KEY,
6
})
7
8
export const runtime = 'edge'
9
10
export async function POST(req: Request) {
11
const { messages } = await req.json()
12
const response = await openai.chat.completions.create({
13
model: 'gpt-3.5-turbo',
14
stream: true,
15
messages,
16
})
17
18
const stream = OpenAIStream(response)
19
20
return new StreamingTextResponse(stream)
21
}

์ด๋Ÿฌํ•œ ์ถ”์ƒํ™”๋Š” ์›น API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ŠคํŠธ๋ฆผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ์›น API๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

app/api/route.ts
1
// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
2
function iteratorToStream(iterator: any) {
3
return new ReadableStream({
4
async pull(controller) {
5
const { value, done } = await iterator.next()
6
7
if (done) {
8
controller.close()
9
} else {
10
controller.enqueue(value)
11
}
12
},
13
})
14
}
15
16
function sleep(time: number) {
17
return new Promise(resolve => {
18
setTimeout(resolve, time)
19
})
20
}
21
22
const encoder = new TextEncoder()
23
24
async function* makeIterator() {
25
yield encoder.encode('<p>One</p>')
26
await sleep(200)
27
yield encoder.encode('<p>Two</p>')
28
await sleep(200)
29
yield encoder.encode('<p>Three</p>')
30
}
31
32
export async function GET() {
33
const iterator = makeIterator()
34
const stream = iteratorToStream(iterator)
35
36
return new Response(stream)
37
}

3.7 Request Body

ํ‘œ์ค€ ์›น API ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ์š”์ฒญ body์„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

app/items/route.ts
1
export async function POST(request: Request) {
2
const res = await request.json()
3
return Response.json({ res })
4
}

3.8 Request Body FormData

request.formData() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ FormData๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

app/items/route.ts
1
export async function POST(request: Request) {
2
const formData = await request.formData()
3
const name = formData.get('name')
4
const email = formData.get('email')
5
return Response.json({ name, email })
6
}

formData ๋ฐ์ดํ„ฐ๋Š” ๋ชจ๋‘ ๋ฌธ์ž์—ด์ด๋ฏ€๋กœ, zod-form-data๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๊ณ  ์›ํ•˜๋Š” ํ˜•์‹(e.g. number)์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


3.9 CORS

ํ‘œ์ค€ ์›น API ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ํŠน์ • ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์— ๋Œ€ํ•œ CORS ํ—ค๋”๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

You can set CORS headers on a Response using the standard Web API methods:

app/api/route.ts
1
export const dynamic = 'force-dynamic' // defaults to auto
2
3
export async function GET(request: Request) {
4
return new Response('Hello, Next.js!', {
5
status: 200,
6
headers: {
7
'Access-Control-Allow-Origin': '*',
8
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
9
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
10
},
11
})
12
}

์•Œ์•„๋‘๋ฉด ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค:


3.10 Webhooks

๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํƒ€์‚ฌ ์„œ๋น„์Šค์—์„œ ์›นํ›…์„ ์ˆ˜์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

app/api/route.ts
1
export async function POST(request: Request) {
2
try {
3
const text = await request.text()
4
// Process the webhook payload
5
} catch (error) {
6
return new Response(`Webhook error: ${error.message}`, {
7
status: 400,
8
})
9
}
10
11
return new Response('Success!', {
12
status: 200,
13
})
14
}

ํŠนํžˆ Pages ๋ผ์šฐํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” API ๋ผ์šฐํ„ฐ์™€ ๋‹ฌ๋ฆฌ, ์ถ”๊ฐ€ ๊ตฌ์„ฑ์„ ์œ„ํ•ด bodyParser๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.


3.11 Edge ๋ฐ Node.js ๋Ÿฐํƒ€์ž„

๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์—๋Š” ์ŠคํŠธ๋ฆฌ๋ฐ ์ง€์›์„ ํฌํ•จํ•˜์—ฌ Edge ๋ฐ Node.js ๋Ÿฐํƒ€์ž„์„ ์›ํ™œํ•˜๊ฒŒ ์ง€์›ํ•˜๋Š” ๋™ํ˜• ์›น API๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ํŽ˜์ด์ง€ ๋ฐ ๋ ˆ์ด์•„์›ƒ๊ณผ ๋™์ผํ•œ ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ฒ”์šฉ ์ •์ ์œผ๋กœ ์žฌ์ƒ์„ฑ๋˜๋Š” ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์™€ ๊ฐ™์ด ์˜ค๋žซ๋™์•ˆ ๊ธฐ๋‹ค๋ ค์˜จ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

runtime ์„ธ๊ทธ๋จผํŠธ ๊ตฌ์„ฑ ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋Ÿฐํƒ€์ž„์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

1
export const runtime = 'edge' // 'nodejs' is the default

3.12 Non-UI Responses

๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ non-UI ์ฝ˜ํ…์ธ ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. sitemap.xml robots.txt, app icons ๋ฐ open graph images๋Š” ๋ชจ๋‘ ๊ธฐ๋ณธ ์ง€์›๋ฉ๋‹ˆ๋‹ค.

app/rss.xml/route.ts
1
export const dynamic = 'force-dynamic' // defaults to auto
2
3
export async function GET() {
4
return new Response(
5
`<?xml version="1.0" encoding="UTF-8" ?>
6
<rss version="2.0">
7
8
<channel>
9
<title>Next.js Documentation</title>
10
<link>https://nextjs.org/docs</link>
11
<description>The React Framework for the Web</description>
12
</channel>
13
14
</rss>`,
15
{
16
headers: {
17
'Content-Type': 'text/xml',
18
},
19
},
20
)
21
}

3.13 ์„ธ๊ทธ๋จผํŠธ ๊ตฌ์„ฑ ์˜ต์…˜

๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ํŽ˜์ด์ง€ ๋ฐ ๋ ˆ์ด์•„์›ƒ๊ณผ ๋™์ผํ•œ ๋ผ์šฐํŠธ ์„ธ๊ทธ๋จผํŠธ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

app/items/route.ts
1
export const dynamic = 'auto'
2
export const dynamicParams = true
3
export const revalidate = false
4
export const fetchCache = 'auto'
5
export const runtime = 'nodejs'
6
export const preferredRegion = 'auto'

์ž์„ธํ•œ ๋‚ด์šฉ์€ API ์ฐธ์กฐ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.