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

Middleware

๋ฏธ๋“ค์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์š”์ฒญ์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „์— ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์— ๋”ฐ๋ผ ์‘๋‹ต์„ ์žฌ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜, ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜๊ฑฐ๋‚˜, ์š”์ฒญ ๋˜๋Š” ์‘๋‹ต ํ—ค๋”๋ฅผ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜, ์ง์ ‘ ์‘๋‹ตํ•จ์œผ๋กœ์จ ์‘๋‹ต์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์บ์‹œ๋œ ์ฝ˜ํ…์ธ ์™€ ๊ฒฝ๋กœ๊ฐ€ ์ผ์น˜ํ•˜๊ธฐ ์ „์— ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๊ฒฝ๋กœ ๋งค์นญ์„ ์ฐธ์กฐํ•˜์„ธ์š”.


1. Convention(๊ทœ์น™)

ํ”„๋กœ์ ํŠธ์˜ ๋ฃจํŠธ์—์„œ middleware.ts(๋˜๋Š” .js) ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, pages ๋˜๋Š” app๊ณผ ๊ฐ™์€ ๋ ˆ๋ฒจ์— ์žˆ๊ฑฐ๋‚˜ ํ•ด๋‹นํ•˜๋Š” ๊ฒฝ์šฐ src ๋‚ด๋ถ€์— ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.


2. ์˜ˆ์‹œ

middleware.ts
1
import { NextResponse } from 'next/server'
2
import type { NextRequest } from 'next/server'
3
4
// ๋งŒ์•ฝ ๋‚ด๋ถ€์—์„œ `await`์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ์ด ํ•จ์ˆ˜๋Š” `async`๋กœ ํ‘œ์‹œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
5
export function middleware(request: NextRequest) {
6
return NextResponse.redirect(new URL('/home', request.url))
7
}
8
9
// ์•„๋ž˜์˜ "๊ฒฝ๋กœ ์ผ์น˜"๋ฅผ ์ฐธ์กฐํ•˜์—ฌ ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”.
10
export const config = {
11
matcher: '/about/:path*',
12
}

3. Matching Paths(๊ฒฝ๋กœ ์ผ์น˜)

ํ”„๋กœ์ ํŠธ์˜ ๋ชจ๋“  ๊ฒฝ๋กœ์— ๋Œ€ํ•ด ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ์‹คํ–‰ ์ˆœ์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  1. next.config.js์˜ headers
  2. next.config.js์˜ redirects
  3. ๋ฏธ๋“ค์›จ์–ด (rewrites, redirects ๋“ฑ)
  4. next.config.js์˜ beforeFiles (rewrites)
  5. ํŒŒ์ผ ์‹œ์Šคํ…œ ๊ฒฝ๋กœ (public/, _next/static/, pages/, app/ ๋“ฑ)
  6. next.config.js์˜ afterFiles (rewrites)
  7. ๋™์  ๊ฒฝ๋กœ (/blog/[slug])
  8. next.config.js์˜ fallback (rewrites)

๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์‹คํ–‰๋  ๊ฒฝ๋กœ๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ์‚ฌ์šฉ์ž ์ •์˜ matcher ๊ตฌ์„ฑ
  2. ์กฐ๊ฑด๋ฌธ

3.1 Matcher

matcher๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • ๊ฒฝ๋กœ์—์„œ ์‹คํ–‰ํ•  ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ•„ํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

middleware.js
1
export const config = {
2
matcher: '/about/:path*',
3
}

๋ฐฐ์—ด ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹จ์ผ ๊ฒฝ๋กœ ๋˜๋Š” ์—ฌ๋Ÿฌ ๊ฒฝ๋กœ๋ฅผ ์ผ์น˜์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

middleware.js
1
export const config = {
2
matcher: ['/about/:path*', '/dashboard/:path*'],
3
}

matcher ๊ตฌ์„ฑ์€ ๋ถ€์ •์  ์ „๋ฐฉํƒ์ƒ‰(negative lookahead) ๋˜๋Š” ๋ฌธ์ž ์ผ์น˜(character matching)์™€ ๊ฐ™์€ ์™„์ „ํ•œ ์ •๊ทœ์‹์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ํŠน์ • ๊ฒฝ๋กœ๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ๊ฒฝ๋กœ์™€ ์ผ์น˜ํ•˜๋Š” ๋ถ€์ •์  ์ „๋ฐฉํƒ์ƒ‰์˜ ์˜ˆ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

middleware.js
1
export const config = {
2
matcher: [
3
/*
4
* ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ๋กœ๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ์š”์ฒญ ๊ฒฝ๋กœ์™€ ์ผ์น˜์‹œํ‚ต๋‹ˆ๋‹ค.
5
* - api (API ๊ฒฝ๋กœ)
6
* - _next/static (์ •์  ํŒŒ์ผ)
7
* - _next/image (์ด๋ฏธ์ง€ ์ตœ์ ํ™” ํŒŒ์ผ)
8
* - favicon.ico (ํŒŒ๋น„์ฝ˜ ํŒŒ์ผ)
9
*/
10
'/((?!api|_next/static|_next/image|favicon.ico).*)',
11
],
12
}

๋ˆ„๋ฝ๋œ ๋ฐฐ์—ด์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๊ฑฐ์น  ํ•„์š”๊ฐ€ ์—†๋Š” ํ”„๋ฆฌํŽ˜์น˜(next/link์—์„œ)๋ฅผ ๋ฌด์‹œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค:

middleware.js
1
export const config = {
2
matcher: [
3
/*
4
* Match all request paths except for the ones starting with:
5
* - api (API routes)
6
* - _next/static (static files)
7
* - _next/image (image optimization files)
8
* - favicon.ico (favicon file)
9
*/
10
{
11
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
12
missing: [
13
{ type: 'header', key: 'next-router-prefetch' },
14
{ type: 'header', key: 'purpose', value: 'prefetch' },
15
],
16
},
17
],
18
}

์•Œ์•„๋‘๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค: ๋นŒ๋“œ ์‹œ ์ •์ ์œผ๋กœ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ๋„๋ก matcher ๊ฐ’์€ ์ƒ์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ณ€์ˆ˜์™€ ๊ฐ™์€ ๋™์  ๊ฐ’์€ ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค.

matcher ๊ตฌ์„ฑ:

  1. ๋ฐ˜๋“œ์‹œ /๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  2. ์ด๋ฆ„ ์žˆ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    • /about/:path๋Š” /about/a์™€ /about/b์™€ ์ผ์น˜ํ•˜์ง€๋งŒ /about/a/c์™€๋Š” ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  3. ์ด๋ฆ„ ์žˆ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜์—๋Š” ์ˆ˜์ •์ž(์ฝœ๋ก ์œผ๋กœ ์‹œ์ž‘ํ•จ)๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

    • /about/:path*๋Š” *๊ฐ€ 0๊ฐœ ์ด์ƒ์„ ์˜๋ฏธํ•˜๊ธฐ ๋•Œ๋ฌธ์— /about/a/b/c์™€ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.
    • ?๋Š” 0๊ฐœ ๋˜๋Š” 1๊ฐœ, +๋Š” 1๊ฐœ ์ด์ƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
  4. ๊ด„ํ˜ธ๋กœ ๋‘˜๋Ÿฌ์‹ธ์ธ ์ •๊ทœ์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    • /about/(.*)๋Š” /about/:path*์™€ ๋™์ผํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ path-to-regexp ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

์ฐธ๊ณ :

  • ์ด์ „ ๋ฒ„์ „๊ณผ์˜ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด Next.js๋Š” ํ•ญ์ƒ /public์„ /public/index๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ /public/:path์™€ ๊ฐ™์€ matcher๋Š” ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.

3.2 Conditional Statements(์กฐ๊ฑด๋ฌธ)

middleware.ts
1
import { NextResponse } from 'next/server'
2
import type { NextRequest } from 'next/server'
3
4
export function middleware(request: NextRequest) {
5
if (request.nextUrl.pathname.startsWith('/about')) {
6
return NextResponse.rewrite(new URL('/about-2', request.url))
7
}
8
9
if (request.nextUrl.pathname.startsWith('/dashboard')) {
10
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
11
}
12
}

4. NextResponse

NextResponse API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์Œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ๋‹ค๋ฅธ URL๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
  • ์ฃผ์–ด์ง„ URL์„ ํ‘œ์‹œํ•˜์—ฌ ์‘๋‹ต ์žฌ์ž‘์„ฑ
  • API ๊ฒฝ๋กœ, getServerSideProps, ๋ฐ ์žฌ์ž‘์„ฑ ๋Œ€์ƒ์— ๋Œ€ํ•œ ์š”์ฒญ ํ—ค๋” ์„ค์ •
  • ์‘๋‹ต ์ฟ ํ‚ค ์„ค์ •
  • ์‘๋‹ต ํ—ค๋” ์„ค์ •

๋ฏธ๋“ค์›จ์–ด์—์„œ ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๋ ค๋ฉด ๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ๋กœ(Page ๋˜๋Š” Edge API Route)๋กœ ์žฌ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
  2. ์ง์ ‘ NextResponse๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์‘๋‹ต ์ƒ์„ฑํ•˜๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

5. Cookies ์‚ฌ์šฉํ•˜๊ธฐ

์ฟ ํ‚ค๋Š” ์ผ๋ฐ˜์ ์ธ ํ—ค๋”์ž…๋‹ˆ๋‹ค. Request์—์„œ๋Š” Cookie ํ—ค๋”์— ์ €์žฅ๋˜๊ณ , Response์—์„œ๋Š” Set-Cookie ํ—ค๋”์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. Next.js๋Š” NextRequest์™€ NextResponse์˜ cookies ํ™•์žฅ์ž๋ฅผ ํ†ตํ•ด ์ด๋Ÿฌํ•œ ์ฟ ํ‚ค์— ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•˜๊ณ  ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ํŽธ๋ฆฌํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  1. ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์˜ ๊ฒฝ์šฐ cookies์—๋Š” get, getAll, set, delete ์ฟ ํ‚ค ๋ฉ”์„œ๋“œ๊ฐ€ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
    • has๋กœ ์ฟ ํ‚ค์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ฑฐ๋‚˜ clear๋กœ ๋ชจ๋“  ์ฟ ํ‚ค๋ฅผ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ๋‚˜๊ฐ€๋Š” ์‘๋‹ต์˜ ๊ฒฝ์šฐ cookies์—๋Š” get, getAll, set, delete ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
middleware.ts
1
import { NextResponse } from 'next/server'
2
import type { NextRequest } from 'next/server'
3
4
export function middleware(request: NextRequest) {
5
// ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์— "Cookie: nextjs=fast" ํ—ค๋”๊ฐ€ ์žˆ๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ€์ •
6
// `RequestCookies` API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์—์„œ ์ฟ ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ
7
let cookie = request.cookies.get('nextjs')
8
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
9
const allCookies = request.cookies.getAll()
10
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
11
12
request.cookies.has('nextjs') // => true
13
request.cookies.delete('nextjs')
14
request.cookies.has('nextjs') // => false
15
16
// `ResponseCookies` API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต์— ์ฟ ํ‚ค ์„ค์ •ํ•˜๊ธฐ
17
const response = NextResponse.next()
18
response.cookies.set('vercel', 'fast')
19
response.cookies.set({
20
name: 'vercel',
21
value: 'fast',
22
path: '/',
23
})
24
cookie = response.cookies.get('vercel')
25
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
26
// ๋‚˜๊ฐ€๋Š” ์‘๋‹ต์—๋Š” `Set-Cookie: vercel=fast; path=/test` ํ—ค๋”๊ฐ€ ํฌํ•จ๋จ
27
28
return response
29
}

6. Headers ์„ค์ •ํ•˜๊ธฐ

NextResponse API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ๊ณผ ์‘๋‹ต ํ—ค๋”๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์š”์ฒญ ํ—ค๋” ์„ค์ •์€ Next.js v13.0.0 ์ด์ƒ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค).

middleware.ts
1
import { NextResponse } from 'next/server'
2
import type { NextRequest } from 'next/server'
3
4
export function middleware(request: NextRequest) {
5
// ์š”์ฒญ ํ—ค๋”๋ฅผ ๋ณต์ œํ•˜๊ณ  ์ƒˆ๋กœ์šด ํ—ค๋” `x-hello-from-middleware1`๋ฅผ ์„ค์ •
6
const requestHeaders = new Headers(request.headers)
7
requestHeaders.set('x-hello-from-middleware1', 'hello')
8
9
// NextResponse.rewrite์—์„œ๋„ ์š”์ฒญ ํ—ค๋”๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Œ
10
const response = NextResponse.next({
11
request: {
12
// ์ƒˆ ์š”์ฒญ ํ—ค๋”
13
headers: requestHeaders,
14
},
15
})
16
17
// ์ƒˆ ์‘๋‹ต ํ—ค๋” `x-hello-from-middleware2` ์„ค์ •
18
response.headers.set('x-hello-from-middleware2', 'hello')
19
return response
20
}

์ฐธ๊ณ : ๋ฐฑ์—”๋“œ ์›น ์„œ๋ฒ„ ๊ตฌ์„ฑ์— ๋”ฐ๋ผ 431 Request Header Fields Too Large ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํฐ ํ—ค๋”๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•˜์‹ญ์‹œ์˜ค.


7. Producing a Response(์‘๋‹ต ์ƒ์„ฑ)

๋ฏธ๋“ค์›จ์–ด์—์„œ๋Š” Response ๋˜๋Š” NextResponse ์ธ์Šคํ„ด์Šค๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์‘๋‹ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ด ๊ธฐ๋Šฅ์€ Next.js v13.1.0๋ถ€ํ„ฐ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค)

middleware.ts
1
import { NextRequest, NextResponse } from 'next/server'
2
import { isAuthenticated } from '@lib/auth'
3
4
// ๋ฏธ๋“ค์›จ์–ด๋ฅผ `/api/`๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ๋กœ๋กœ ์ œํ•œ
5
export const config = {
6
matcher: '/api/:function*',
7
}
8
9
export function middleware(request: NextRequest) {
10
// ์š”์ฒญ์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ธ์ฆ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ
11
if (!isAuthenticated(request)) {
12
// ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” JSON์œผ๋กœ ์‘๋‹ต
13
return new NextResponse(JSON.stringify({ success: false, message: 'authentication failed' }), {
14
status: 401,
15
headers: { 'content-type': 'application/json' },
16
})
17
}
18
}

7.1 waitUntil ๊ณผ NextFetchEvent

NextFetchEvent ๊ฐ์ฒด๋Š” ๋„ค์ดํ‹ฐ๋ธŒ FetchEvent ๊ฐ์ฒด๋ฅผ ํ™•์žฅํ•˜๋ฉฐ, waitUntil() ๋ฉ”์„œ๋“œ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

waitUntil() ๋ฉ”์„œ๋“œ๋Š” ํ”„๋กœ๋ฏธ์Šค๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›์•„ ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ํ™•์ •๋  ๋•Œ๊นŒ์ง€ ๋ฏธ๋“ค์›จ์–ด์˜ ์ˆ˜๋ช…์„ ์—ฐ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

middleware.ts
1
import { NextResponse } from 'next/server'
2
import type { NextFetchEvent, NextRequest } from 'next/server'
3
4
export function middleware(req: NextRequest, event: NextFetchEvent) {
5
event.waitUntil(
6
fetch('https://my-analytics-platform.com', {
7
method: 'POST',
8
body: JSON.stringify({ pathname: req.nextUrl.pathname }),
9
}),
10
)
11
12
return NextResponse.next()
13
}

8. ๊ณ ๊ธ‰ ๋ฏธ๋“ค์›จ์–ด ํ”Œ๋ž˜๊ทธ

Next.js v13.1์—์„œ๋Š” ๊ณ ๊ธ‰ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์œ„ํ•œ ๋‘ ๊ฐœ์˜ ์ถ”๊ฐ€ ํ”Œ๋ž˜๊ทธ์ธ skipMiddlewareUrlNormalize ๋ฐ skipTrailingSlashRedirect๊ฐ€ ๋„์ž…๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

skipTrailingSlashRedirect๋Š” ํ›„ํ–‰ ์Šฌ๋ž˜์‹œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•œ Next.js ๋ฆฌ๋””๋ ‰์…˜์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฏธ๋“ค์›จ์–ด ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉ์ž ์ •์˜ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด ์ผ๋ถ€ ๊ฒฝ๋กœ์—๋Š” ํ›„ํ–‰ ์Šฌ๋ž˜์‹œ๋ฅผ ์œ ์ง€ํ•˜์ง€๋งŒ, ๋‹ค๋ฅธ ๊ฒฝ๋กœ์—๋Š” ์œ ์ง€ํ•˜์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฆ๋ถ„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ๋” ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.

next.config.js
1
module.exports = {
2
skipTrailingSlashRedirect: true,
3
}
middleware.js
1
const legacyPrefixes = ['/docs', '/blog']
2
3
export default async function middleware(req) {
4
const { pathname } = req.nextUrl
5
6
if (legacyPrefixes.some(prefix => pathname.startsWith(prefix))) {
7
return NextResponse.next()
8
}
9
10
// trailing slash ์ฒ˜๋ฆฌ ์ ์šฉ
11
if (!pathname.endsWith('/') && !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)) {
12
req.nextUrl.pathname += '/'
13
return NextResponse.redirect(req.nextUrl)
14
}
15
}

skipMiddlewareUrlNormalize์€ Next.js์—์„œ ์ง์ ‘ ๋ฐฉ๋ฌธ๊ณผ ํด๋ผ์ด์–ธํŠธ ์ „ํ™˜์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์ˆ˜ํ–‰ํ•˜๋Š” URL ์ •๊ทœํ™”๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์›๋ž˜์˜ URL์„ ์‚ฌ์šฉํ•˜์—ฌ ์™„์ „ํ•œ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•œ ๊ณ ๊ธ‰ ์ƒํ™ฉ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ์ด๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

next.config.js
1
module.exports = {
2
skipMiddlewareUrlNormalize: true,
3
}
middleware.js
1
export default async function middleware(req) {
2
const { pathname } = req.nextUrl
3
4
// GET /_next/data/build-id/hello.json
5
6
console.log(pathname)
7
// ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ์œผ๋ฉด /_next/data/build-id/hello.json
8
// ํ”Œ๋ž˜๊ทธ๊ฐ€ ์—†์œผ๋ฉด /hello๋กœ ์ •๊ทœํ™”๋จ
9
}

9. Runtime

๋ฏธ๋“ค์›จ์–ด๋Š” ํ˜„์žฌ Edge ๋Ÿฐํƒ€์ž„๋งŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. Node.js ๋Ÿฐํƒ€์ž„์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.


10. Version History

๋ฒ„์ „๋ณ€๊ฒฝ ๋‚ด์šฉ
v13.1.0๊ณ ๊ธ‰ ๋ฏธ๋“ค์›จ์–ด ํ”Œ๋ž˜๊ทธ ์ถ”๊ฐ€
v13.0.0๋ฏธ๋“ค์›จ์–ด์—์„œ ์š”์ฒญ ํ—ค๋”, ์‘๋‹ต ํ—ค๋”๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ์‘๋‹ต์„ ์ „์†กํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ
v12.2.0๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์•ˆ์ •ํ™”๋˜์—ˆ์œผ๋ฉฐ ์—…๊ทธ๋ ˆ์ด๋“œ ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”
v12.0.9Edge Runtime์—์„œ ์ ˆ๋Œ€ URL ๊ฐ•์ œ ์ ์šฉ (PR)
v12.0.0๋ฏธ๋“ค์›จ์–ด (Beta) ์ถ”๊ฐ€