2024
November
Next.js 15

๐Ÿ”— **Next.js 15 (opens in a new tab)

๐Ÿ—“๏ธ ๋ฒˆ์—ญ ๋‚ ์งœ: 2024.11.07

๐Ÿงš ๋ฒˆ์—ญํ•œ ํฌ๋ฃจ: ๋Ÿฌ๊ธฐ(๋ฐ•์ •์šฐ)


Next.js 15

Next.js 15๊ฐ€ ๊ณต์‹์ ์œผ๋กœ ์•ˆ์ •ํ™”๋˜์–ด ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ๋ฆด๋ฆฌ์Šค๋Š” RC1 (opens in a new tab)๊ณผ RC2 (opens in a new tab)์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฉฐ, ์•ˆ์ •์„ฑ์— ์ค‘์ ์„ ๋‘๋ฉด์„œ๋„ ์—ฌ๋Ÿฌ๋ถ„์ด ์ข‹์•„ํ•˜์‹ค ๋งŒํ•œ ํฅ๋ฏธ๋กœ์šด ๊ธฐ๋Šฅ๋“ค์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ์˜ค๋Š˜ ๋ฐ”๋กœ Next.js 15๋ฅผ ์‹œ๋„ํ•ด ๋ณด์„ธ์š”.

# Use the new automated upgrade CLI
npx @next/codemod@canary upgrade latest

# ...or upgrade manually
npm install next@latest react@rc react-dom@rc

๋‹ค๊ฐ€์˜ค๋Š” ๋ชฉ์š”์ผ, 10์›” 24์ผ์— ์—ด๋ฆฌ๋Š” Next.js Conf์—์„œ ๊ณง ๋‹ค๊ฐ€์˜ฌ ์—…๋ฐ์ดํŠธ์— ๋Œ€ํ•ด ๋” ๋งŽ์€ ๋‚ด์šฉ์„ ๊ณต์œ ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

Next.js 15์˜ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • @next/codemod CLI: ์ตœ์‹  Next.js ๋ฐ React ๋ฒ„์ „์œผ๋กœ ์†์‰ฝ๊ฒŒ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ์ˆ˜ ์žˆ๋Š” CLI ๋„๊ตฌ.
  • ๋น„๋™๊ธฐ ์š”์ฒญ API (Breaking): ๋ Œ๋”๋ง ๋ฐ ์บ์‹ฑ ๋ชจ๋ธ์„ ๋‹จ์ˆœํ™”ํ•˜๊ธฐ ์œ„ํ•œ ๋‹จ๊ณ„์  ์ ‘๊ทผ.
  • ์บ์‹ฑ ๋ฐฉ์‹ (Breaking): fetch ์š”์ฒญ, GET ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ, ํด๋ผ์ด์–ธํŠธ ํƒ์ƒ‰์ด ๊ธฐ๋ณธ์ ์œผ๋กœ ์บ์‹œ๋˜์ง€ ์•Š์Œ.
  • React 19 ์ง€์›: React 19, React Compiler (์‹คํ—˜์ ), ๋ฐ hydration ์˜ค๋ฅ˜ ๊ฐœ์„  ์ง€์›.
  • Turbopack Dev (Stable): ์„ฑ๋Šฅ ๋ฐ ์•ˆ์ •์„ฑ ํ–ฅ์ƒ.
  • ์ •์  ํ‘œ์‹œ๊ธฐ: ๊ฐœ๋ฐœ ์ค‘ ์ •์  ๋ผ์šฐํŠธ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œ์‹œํ•˜๋Š” ์ƒˆ ์ธ๋””์ผ€์ดํ„ฐ.
  • unstable_after API (Experimental): ์‘๋‹ต์ด ์ŠคํŠธ๋ฆฌ๋ฐ ์™„๋ฃŒ๋œ ํ›„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์‹คํ—˜์  API.
  • instrumentation.js API (Stable): ์„œ๋ฒ„ ์ˆ˜๋ช… ์ฃผ๊ธฐ ๊ฐ€์‹œ์„ฑ์„ ์œ„ํ•œ ์ƒˆ๋กœ์šด API.
  • ํ–ฅ์ƒ๋œ ํผ (next/form): HTML ํผ์— ํด๋ผ์ด์–ธํŠธ ์ธก ํƒ์ƒ‰ ๊ธฐ๋Šฅ ์ถ”๊ฐ€.
  • next.config: next.config.ts์—์„œ TypeScript ์ง€์›.
  • ์…€ํ”„ ํ˜ธ์ŠคํŒ… ๊ฐœ์„ : Cache-Control ํ—ค๋”์— ๋Œ€ํ•œ ๋” ๋งŽ์€ ์ œ์–ด ์˜ต์…˜ ์ œ๊ณต.
  • ์„œ๋ฒ„ ์•ก์…˜ ๋ณด์•ˆ: ์ถ”์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ ์—”๋“œํฌ์ธํŠธ์™€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์•ก์…˜ ์ œ๊ฑฐ.
  • ์™ธ๋ถ€ ํŒจํ‚ค์ง€ ๋ฒˆ๋“ค๋ง (Stable): ์•ฑ ๋ฐ ํŽ˜์ด์ง€ ๋ผ์šฐํ„ฐ์šฉ ์ƒˆ ๊ตฌ์„ฑ ์˜ต์…˜.
  • ESLint 9 ์ง€์›: ESLint 9 ์ง€์› ์ถ”๊ฐ€.
  • ๊ฐœ๋ฐœ ๋ฐ ๋นŒ๋“œ ์„ฑ๋Šฅ: ๋” ๋น ๋ฅธ ๋นŒ๋“œ ์‹œ๊ฐ„๊ณผ ํ–ฅ์ƒ๋œ Fast Refresh.

์›ํ™œํ•œ ์—…๊ทธ๋ ˆ์ด๋“œ๋ฅผ ์œ„ํ•œ @next/codemod CLI

๋ชจ๋“  ์ฃผ์š” Next.js ๋ฆด๋ฆฌ์Šค์—๋Š” codemod๋ผ๋Š” ์ž๋™ ์ฝ”๋“œ ๋ณ€ํ™˜ ๋„๊ตฌ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์–ด, ์ฃผ์š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์†์‰ฝ๊ฒŒ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฒˆ์—๋Š” ์—…๊ทธ๋ ˆ์ด๋“œ ๊ณผ์ •์„ ๋”์šฑ ๋งค๋„๋Ÿฝ๊ฒŒ ๋•๊ธฐ ์œ„ํ•ด, ํ–ฅ์ƒ๋œ codemod CLI๋ฅผ ์ถœ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.

npx @next/codemod@canary upgrade latest

์ด ๋„๊ตฌ๋Š” ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ์ตœ์‹  ์•ˆ์ • ๋ฒ„์ „ ๋˜๋Š” ์‚ฌ์ „ ๋ฆด๋ฆฌ์Šค ๋ฒ„์ „์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค. CLI๋Š” ์ข…์†์„ฑ์„ ์—…๋ฐ์ดํŠธํ•˜๊ณ , ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ codemod๋ฅผ ํ‘œ์‹œํ•˜๋ฉฐ, ์ด๋ฅผ ์ ์šฉํ•˜๋Š” ๊ณผ์ •์„ ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค.

canary ํƒœ๊ทธ๋Š” codemod์˜ ์ตœ์‹  ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋ฉฐ, latest๋Š” Next.js ๋ฒ„์ „์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ตœ์‹  Next.js ๋ฒ„์ „์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋”๋ผ๋„, ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋„๊ตฌ๋ฅผ ์ง€์†์ ์œผ๋กœ ๊ฐœ์„ ํ•  ๊ณ„ํš์ด๋ฏ€๋กœ canary ๋ฒ„์ „์˜ codemod๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

Next.js codemod CLI์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Next.js ๊ณต์‹ ๋ฌธ์„œ (opens in a new tab)๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

๋น„๋™๊ธฐ Request API (์ฃผ์š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ)

๊ธฐ์กด ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(SSR)์—์„œ๋Š” ์„œ๋ฒ„๊ฐ€ ์š”์ฒญ์„ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์š”์ฒญ์— ๋”ฐ๋ฅธ ํŠน์ • ๋ฐ์ดํ„ฐ์— ์˜์กดํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ์š”์ฒญ์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ ๊ธฐ๋‹ค๋ฆด ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ด์ƒ์ ์œผ๋กœ๋Š” ์„œ๋ฒ„๊ฐ€ ์š”์ฒญ์ด ๋„์ฐฉํ•˜๊ธฐ ์ „์— ๊ฐ€๋Šฅํ•œ ํ•œ ๋งŽ์€ ์ค€๋น„๋ฅผ ๋งˆ์ณ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ณ  ๋ฏธ๋ž˜์˜ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด, ์–ธ์ œ ์š”์ฒญ์„ ๊ธฐ๋‹ค๋ ค์•ผ ํ• ์ง€๋ฅผ ์„œ๋ฒ„๊ฐ€ ์•Œ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ, ํ—ค๋”, ์ฟ ํ‚ค, params, searchParams์™€ ๊ฐ™์ด ์š”์ฒญ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง€๋Š” ๋ฐ์ดํ„ฐ์— ์˜์กดํ•˜๋Š” API๋ฅผ ๋น„๋™๊ธฐ๋กœ ์ „ํ™˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

import { cookies } from "next/headers";
 
export async function AdminPanel() {
  const cookieStore = await cookies();
  const token = cookieStore.get("token");
 
  // ...
}

์ด๋ฒˆ ๋ณ€๊ฒฝ์€ ์ฃผ์š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์œผ๋กœ, ๋‹ค์Œ API์— ์˜ํ–ฅ์„ ๋ฏธ์นฉ๋‹ˆ๋‹ค.

  • cookies
  • headers
  • draftMode
  • params (layout.js, page.js, route.js, default.js, generateMetadata, generateViewport ํŒŒ์ผ๋“ค์—์„œ)
  • searchParams (page.js์—์„œ)

๋” ์‰ฝ๊ฒŒ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ๋„๋ก, ์ด๋Ÿฌํ•œ API๋Š” ์ผ์‹œ์ ์œผ๋กœ ๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๋‹ค์Œ ์ฃผ์š” ๋ฒ„์ „๊นŒ์ง€ ๊ฐœ๋ฐœ ๋ฐ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ž๋™ํ™”ํ•˜๊ธฐ ์œ„ํ•œ codemod (opens in a new tab)๋„ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

npx @next/codemod@canary next-async-request-api .

codemod๊ฐ€ ์ฝ”๋“œ์˜ ์™„์ „ํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ˆ˜ํ–‰ํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ, ์—…๊ทธ๋ ˆ์ด๋“œ ๊ฐ€์ด๋“œ (opens in a new tab)๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”. ์ƒˆ๋กœ์šด API๋กœ Next.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๋Š” ์˜ˆ์ œ (opens in a new tab)๋„ ํ•จ๊ป˜ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์บ์‹ฑ ๋ฐฉ์‹

Next.js App Router๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•œ ์บ์‹ฑ ๋ฐฉ์‹์„ ์ œ๊ณตํ•˜์—ฌ ํ•„์š” ์‹œ ์บ์‹ฑ์„ ์„ ํƒ์ ์œผ๋กœ ํ•ด์ œํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ”ํƒ•์œผ๋กœ, Partial Prerendering(PPR)๊ณผ fetch๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€์˜ ์ƒํ˜ธ ์ž‘์šฉ ๋ฐฉ์‹์„ ๊ณ ๋ คํ•˜์—ฌ ์บ์‹ฑ ๊ทœ์น™ (opens in a new tab)์„ ์žฌ๊ฒ€ํ† ํ–ˆ์Šต๋‹ˆ๋‹ค.

Next.js 15์—์„œ๋Š” GET ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์™€ ํด๋ผ์ด์–ธํŠธ ๋ผ์šฐํ„ฐ ์บ์‹œ์˜ ๊ธฐ๋ณธ ์„ค์ •์„ ์บ์‹ฑ๋œ ์ƒํƒœ์—์„œ ๋น„์บ์‹ฑ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ „์˜ ์บ์‹ฑ ๋™์ž‘์„ ์œ ์ง€ํ•˜๋ ค๋ฉด, ์ง์ ‘ ์บ์‹ฑ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•ž์œผ๋กœ ๋ช‡ ๋‹ฌ ๊ฐ„ Next.js์˜ ์บ์‹ฑ ๊ธฐ๋Šฅ์„ ์ง€์†์ ์œผ๋กœ ๊ฐœ์„ ํ•  ์˜ˆ์ •์ด๋ฉฐ, ๋” ๋งŽ์€ ์ •๋ณด๋ฅผ ๊ณง ๊ณต์œ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

GET ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์บ์‹ฑ๋˜์ง€ ์•Š์Œ

Next.js 14์—์„œ๋Š” GET HTTP ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋™์  ํ•จ์ˆ˜๋‚˜ ๋™์  ๊ตฌ์„ฑ ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํ•œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์บ์‹ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ Next.js 15๋ถ€ํ„ฐ๋Š” GET ํ•จ์ˆ˜๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์บ์‹ฑ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์—ฌ์ „ํžˆ export const dynamic = 'force-static'๊ณผ ๊ฐ™์€ ์ •์  ๋ผ์šฐํŠธ ๊ตฌ์„ฑ ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์บ์‹ฑ์„ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

sitemap.ts, opengraph-image.tsx, icon.tsx์™€ ๊ฐ™์€ ํŠน๋ณ„ํ•œ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์™€ ๊ธฐํƒ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํŒŒ์ผ์€ ๋™์  ํ•จ์ˆ˜๋‚˜ ๋™์  ๊ตฌ์„ฑ ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํ•œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ •์ ์œผ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

ํด๋ผ์ด์–ธํŠธ ๋ผ์šฐํ„ฐ ์บ์‹œ๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์บ์‹ฑํ•˜์ง€ ์•Š์Œ

Next.js 14.2.0์—์„œ๋Š” ๋ผ์šฐํ„ฐ ์บ์‹œ์˜ ์‚ฌ์šฉ์ž ์ง€์ • ๊ตฌ์„ฑ์„ ์œ„ํ•ด ์‹คํ—˜์ ์ธ staleTimes ํ”Œ๋ž˜๊ทธ๋ฅผ ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค.

Next.js 15์—์„œ๋Š” ์ด ํ”Œ๋ž˜๊ทธ๊ฐ€ ์—ฌ์ „ํžˆ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ํŽ˜์ด์ง€ ์„ธ๊ทธ๋จผํŠธ์˜ ๊ธฐ๋ณธ staleTime ๊ฐ’์„ 0์œผ๋กœ ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์•ฑ์„ ํƒ์ƒ‰ํ•  ๋•Œ๋งˆ๋‹ค ํ™œ์„ฑํ™”๋œ ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์‹  ๋ฐ์ดํ„ฐ๊ฐ€ ํ•ญ์ƒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ค‘์š”ํ•œ ๋™์ž‘์€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • ๊ณต์œ  ๋ ˆ์ด์•„์›ƒ ๋ฐ์ดํ„ฐ: ๋ถ€๋ถ„ ๋ Œ๋”๋ง์„ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„์—์„œ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋’ค๋กœ/์•ž์œผ๋กœ ํƒ์ƒ‰: ์Šคํฌ๋กค ์œ„์น˜ ๋ณต์›์„ ์œ„ํ•ด ์บ์‹œ์—์„œ ๋ณต์›๋ฉ๋‹ˆ๋‹ค.
  • loading.js: 5๋ถ„๊ฐ„(๋˜๋Š” staleTimes.static ์„ค์ • ๊ฐ’) ์บ์‹ฑ๋œ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

์ด์ „ ํด๋ผ์ด์–ธํŠธ ๋ผ์šฐํ„ฐ ์บ์‹œ ๋™์ž‘์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋‹ค์Œ ์„ค์ •์„ ํ†ตํ•ด ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const nextConfig = {
  experimental: {
    staleTimes: {
      dynamic: 30,
    },
  },
};
 
export default nextConfig;

React 19

Next.js 15 ๋ฆด๋ฆฌ์Šค์˜ ์ผํ™˜์œผ๋กœ, ๊ณง ์ถœ์‹œ๋  React 19์™€ ํ˜ธํ™˜๋˜๋„๋ก ์—…๋ฐ์ดํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ฒ„์ „ 15์—์„œ App Router๋Š” React 19 RC๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, ์ปค๋ฎค๋‹ˆํ‹ฐ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ˜์˜ํ•ด Pages Router์— React 18์— ๋Œ€ํ•œ ํ•˜์œ„ ํ˜ธํ™˜์„ฑ๋„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. Pages Router๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ์ค€๋น„๊ฐ€ ๋˜๋ฉด React 19๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

React 19๋Š” ์•„์ง RC ๋‹จ๊ณ„์— ์žˆ์ง€๋งŒ, ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ์˜ ๊ด‘๋ฒ”์œ„ํ•œ ํ…Œ์ŠคํŠธ์™€ React ํŒ€๊ณผ์˜ ๊ธด๋ฐ€ํ•œ ํ˜‘๋ ฅ์„ ํ†ตํ•ด ์•ˆ์ •์„ฑ์„ ํ™•์‹ ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ฃผ์š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ์ถฉ๋ถ„ํžˆ ๊ฒ€์ฆ๋˜์–ด ๊ธฐ์กด App Router ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ Next.js 15๋ฅผ ์•ˆ์ • ๋ฒ„์ „์œผ๋กœ ์ถœ์‹œํ•˜์—ฌ, ํ”„๋กœ์ ํŠธ๊ฐ€ React 19 GA์— ๋Œ€๋น„ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ค€๋น„ํ–ˆ์Šต๋‹ˆ๋‹ค.

์›ํ™œํ•œ ์ „ํ™˜์„ ์œ„ํ•ด, ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๋•๋Š” codemod์™€ ์ž๋™ํ™” ๋„๊ตฌ๋„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ Next.js 15 ์—…๊ทธ๋ ˆ์ด๋“œ ๊ฐ€์ด๋“œ (opens in a new tab), React 19 ์—…๊ทธ๋ ˆ์ด๋“œ ๊ฐ€์ด๋“œ (opens in a new tab), ๊ทธ๋ฆฌ๊ณ  React Conf Keynote (opens in a new tab)๋ฅผ ํ†ตํ•ด ํ™•์ธํ•ด ๋ณด์„ธ์š”.

Pages Router์™€ React 18

Next.js 15๋Š” Pages Router์— ๋Œ€ํ•ด React 18์˜ ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์„ ์œ ์ง€ํ•˜์—ฌ, ์‚ฌ์šฉ์ž๊ฐ€ Next.js 15์˜ ๊ฐœ์„  ์‚ฌํ•ญ์„ ํ™œ์šฉํ•˜๋ฉด์„œ๋„ React 18์„ ๊ณ„์† ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

RC1 ์ดํ›„ ์ปค๋ฎค๋‹ˆํ‹ฐ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ˜์˜ํ•˜์—ฌ React 18 ์ง€์›์„ ํฌํ•จํ•˜๋„๋ก ์—…๋ฐ์ดํŠธํ–ˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด Pages Router์™€ ํ•จ๊ป˜ React 18์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ Next.js 15๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ์จ ์—…๊ทธ๋ ˆ์ด๋“œ ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ๋” ํฐ ์„ ํƒ๊ถŒ์„ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ : ํ•˜๋‚˜์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Pages Router๋ฅผ React 18๋กœ, App Router๋ฅผ React 19๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์ด๋Ÿฌํ•œ ์„ค์ •์€ ๊ถŒ์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํ˜ผํ•ฉ ์‚ฌ์šฉ์€ ์˜ˆ๊ธฐ์น˜ ์•Š์€ ๋™์ž‘์ด๋‚˜ ํƒ€์ž… ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋‘ ๋ฒ„์ „ ๊ฐ„์˜ API ๋ฐ ๋ Œ๋”๋ง ๋กœ์ง์ด ์™„์ „ํžˆ ์ผ์น˜ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

React Compiler๋Š” Meta์˜ React ํŒ€์ด ๊ฐœ๋ฐœํ•œ ์ƒˆ๋กœ์šด ์‹คํ—˜์  ์ปดํŒŒ์ผ๋Ÿฌ๋กœ, JavaScript์˜ ์˜๋ฏธ์™€ React์˜ ๊ทœ์น™์„ ๊นŠ์ด ์ดํ•ดํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ์ž๊ฐ€ useMemo๋‚˜ useCallback๊ณผ ๊ฐ™์€ API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ˆ˜๋™์œผ๋กœ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ํ•ด์•ผ ํ•˜๋Š” ์ž‘์—…์„ ์ค„์—ฌ, ์ฝ”๋“œ๋ฅผ ๋” ๊ฐ„๋‹จํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

Next.js 15์—์„œ๋Š” React Compiler์— ๋Œ€ํ•œ ์ง€์›์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ๊ณผ Next.js์˜ ๊ตฌ์„ฑ ์˜ต์…˜์€ Next.js ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

์ฃผ์˜: ํ˜„์žฌ React Compiler๋Š” Babel ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ์ด๋กœ ์ธํ•ด ๊ฐœ๋ฐœ ๋ฐ ๋นŒ๋“œ ์‹œ๊ฐ„์ด ๋Š๋ ค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

React Compiler (์‹คํ—˜์ )

React Compiler (opens in a new tab)๋Š” Meta์˜ React ํŒ€์ด ๊ฐœ๋ฐœํ•œ ์ƒˆ๋กœ์šด ์‹คํ—˜์  ์ปดํŒŒ์ผ๋Ÿฌ๋กœ, JavaScript์˜ ์˜๋ฏธ์™€ React ๊ทœ์น™ (opens in a new tab)์„ ๊นŠ์ด ์ดํ•ดํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ์ž๋Š” useMemo์™€ useCallback ๊ฐ™์€ API๋ฅผ ์ด์šฉํ•ด ์ˆ˜๋™์œผ๋กœ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ์„ค์ •ํ•ด์•ผ ํ•˜๋Š” ์ž‘์—…์„ ์ค„์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ฝ”๋“œ๊ฐ€ ๋” ๊ฐ„๋‹จํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฌ์›Œ์ ธ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ๋„ ๋‚ฎ์•„์ง‘๋‹ˆ๋‹ค.

Next.js 15์—์„œ๋Š” React Compiler์— ๋Œ€ํ•œ ์ง€์›์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. React Compiler์™€ Next.js์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ตฌ์„ฑ ์˜ต์…˜์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š” (opens in a new tab).

์ฐธ๊ณ : React Compiler๋Š” ํ˜„์žฌ Babel ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ๋งŒ ์ œ๊ณต๋˜๋ฏ€๋กœ, ๊ฐœ๋ฐœ ๋ฐ ๋นŒ๋“œ ์†๋„๊ฐ€ ๋Š๋ ค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์˜ค๋ฅ˜ ๊ฐœ์„ 

Next.js 14.1์—์„œ๋Š” ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€์™€ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์˜ค๋ฅ˜๋ฅผ ๊ฐœ์„ ํ–ˆ์œผ๋ฉฐ, Next.js 15์—์„œ๋Š” ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋” ํ–ฅ์ƒ๋œ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์˜ค๋ฅ˜ ํ™”๋ฉด์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์†Œ์Šค ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์œ„ํ•œ ๊ถŒ์žฅ ์‚ฌํ•ญ์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, Next.js 14.1์—์„œ์˜ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์•˜์Šต๋‹ˆ๋‹ค.

Next.js 14.1์˜ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€

Next.js 15์—์„œ๋Š” ์ด๋ฅผ ๊ฐœ์„ ํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Next.js 15์—์„œ ๊ฐœ์„ ๋œ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€

Turbopack Dev

next dev --turbo ๋ช…๋ น์–ด๊ฐ€ ์ด์ œ ์•ˆ์ •ํ™”๋˜์–ด ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ํ•œ์ธต ๋น ๋ฅด๊ฒŒ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ €ํฌ๋Š” ์ด ๊ธฐ๋Šฅ์„ vercel.com, nextjs.org, v0 (opens in a new tab)๋ฅผ ๋น„๋กฏํ•œ ์—ฌ๋Ÿฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ ์šฉํ•˜๋ฉฐ ์ข‹์€ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ๋Œ€๊ทœ๋ชจ Next.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ธ vercel.com์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ฑ๋Šฅ ๊ฐœ์„ ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค:

  • ๋กœ์ปฌ ์„œ๋ฒ„ ์‹œ์ž‘ ์†๋„ ์ตœ๋Œ€ 76.7% ํ–ฅ์ƒ
  • Fast Refresh๋ฅผ ํ†ตํ•œ ์ฝ”๋“œ ์—…๋ฐ์ดํŠธ ์†๋„ ์ตœ๋Œ€ 96.3% ํ–ฅ์ƒ
  • ์บ์‹ฑ ์—†์ด ์ดˆ๊ธฐ ๋ผ์šฐํŠธ ์ปดํŒŒ์ผ ์†๋„ ์ตœ๋Œ€ 45.8% ํ–ฅ์ƒ (ํ˜„์žฌ Turbopack์€ ๋””์Šคํฌ ์บ์‹ฑ์„ ์ง€์›ํ•˜์ง€ ์•Š์Œ)

Turbopack Dev์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ƒˆ๋กœ์šด ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ธ”๋กœ๊ทธ ๊ธ€ (opens in a new tab)

์ •์  ๋ผ์šฐํŠธ ํ‘œ์‹œ๊ธฐ

Next.js๋Š” ์ด์ œ ๊ฐœ๋ฐœ ์ค‘์— ์ •์  ๋ผ์šฐํŠธ ํ‘œ์‹œ๊ธฐ๋ฅผ ์ œ๊ณตํ•˜์—ฌ, ์–ด๋–ค ๋ผ์šฐํŠธ๊ฐ€ ์ •์ ์ธ์ง€ ๋˜๋Š” ๋™์ ์ธ์ง€ ์‰ฝ๊ฒŒ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์‹œ๊ฐ์  ์•ˆ๋‚ด๋ฅผ ํ†ตํ•ด ํŽ˜์ด์ง€๊ฐ€ ์–ด๋–ป๊ฒŒ ๋ Œ๋”๋ง๋˜๋Š”์ง€ ์ดํ•ดํ•˜์—ฌ ์„ฑ๋Šฅ ์ตœ์ ํ™”์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

์ •์  ๋ผ์šฐํŠธ ํ‘œ์‹œ๊ธฐ

๋นŒ๋“œ ๊ฒฐ๊ณผ๋ฌผ์—์„œ ๋ชจ๋“  ๋ผ์šฐํŠธ์˜ ๋ Œ๋”๋ง ์ „๋žต์„ next build (opens in a new tab)๋ฅผ ํ†ตํ•ด ํ™•์ธํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

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

์ •์  ๋ผ์šฐํŠธ ํ‘œ์‹œ๊ธฐ (opens in a new tab)์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด์„ธ์š”. ํ•„์š”์‹œ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

unstable_after๋ฅผ ์‚ฌ์šฉํ•œ ์‘๋‹ต ํ›„ ์ฝ”๋“œ ์‹คํ–‰ (์‹คํ—˜์ )

์‚ฌ์šฉ์ž ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์„œ๋ฒ„๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์‘๋‹ต์„ ๊ณ„์‚ฐํ•˜๋Š” ๋ฐ ์ง์ ‘ ๊ด€๋ จ๋œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋กœ๊ทธ ๊ธฐ๋ก, ๋ถ„์„, ์™ธ๋ถ€ ์‹œ์Šคํ…œ ๋™๊ธฐํ™”์™€ ๊ฐ™์€ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ž‘์—…์€ ์‘๋‹ต๊ณผ ์ง์ ‘์ ์œผ๋กœ ๊ด€๋ จ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž๊ฐ€ ์™„๋ฃŒ๋˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆด ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์‘๋‹ต ์ดํ›„์— ์ž‘์—…์„ ์ง€์—ฐํ•˜๋ ค๊ณ  ํ•ด๋„ ์„œ๋ฒ„๋ฆฌ์Šค ํ•จ์ˆ˜๋Š” ์‘๋‹ต์ด ๋‹ซํžŒ ํ›„ ์ฆ‰์‹œ ์ฒ˜๋ฆฌ๋ฅผ ์ค‘์ง€ํ•˜๋ฏ€๋กœ ์–ด๋ ค์›€์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

after()๋Š” ์‘๋‹ต์ด ์ŠคํŠธ๋ฆฌ๋ฐ์„ ๋งˆ์นœ ํ›„ ์ž‘์—…์„ ์˜ˆ์•ฝํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” ์ƒˆ๋กœ์šด ์‹คํ—˜์  API๋กœ, ๋ณด์กฐ ์ž‘์—…์ด ์ฃผ์š” ์‘๋‹ต์„ ๋ฐฉํ•ดํ•˜์ง€ ์•Š๊ณ  ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉํ•˜๋ ค๋ฉด next.config.js์— experimental.after๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

// next.config.ts
const nextConfig = {
  experimental: {
    after: true,
  },
};

export default nextConfig;

๊ทธ๋Ÿฐ ๋‹ค์Œ, ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ, ์„œ๋ฒ„ ์•ก์…˜, ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ๋˜๋Š” ๋ฏธ๋“ค์›จ์–ด์—์„œ ์ด ํ•จ์ˆ˜๋ฅผ importํ•ฉ๋‹ˆ๋‹ค.

import { unstable_after as after } from 'next/server';
import { log } from '@/app/utils';

export default function Layout({ children }) {
  // ๋ณด์กฐ ์ž‘์—…
  after(() => {
    log();
  });

  // ์ฃผ์š” ์ž‘์—…
  return <>{children}</>;
}

unstable_after์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ (opens in a new tab)๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

instrumentation.js (์•ˆ์ • ๋ฒ„์ „)

instrumentation.js ํŒŒ์ผ๊ณผ register() API๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋“ค์€ Next.js ์„œ๋ฒ„์˜ ๋ผ์ดํ”„์‚ฌ์ดํด์— ์ ‘๊ทผํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ , ์˜ค๋ฅ˜์˜ ๊ทผ์›์„ ์ถ”์ ํ•˜๋ฉฐ OpenTelemetry์™€ ๊ฐ™์€ ๊ฐ€์‹œ์„ฑ(Observability) ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ๊นŠ์ด ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ธฐ๋Šฅ์€ ์ด์ œ ์•ˆ์ •ํ™”๋˜์—ˆ์œผ๋ฉฐ, experimental.instrumentationHook ๊ตฌ์„ฑ ์˜ต์…˜์„ ์ œ๊ฑฐํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ Sentry์™€ ํ˜‘๋ ฅํ•˜์—ฌ ์ƒˆ๋กœ์šด onRequestError ํ›…์„ ์„ค๊ณ„ํ–ˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์„œ๋ฒ„์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ชจ๋“  ์˜ค๋ฅ˜์— ๋Œ€ํ•œ ์ค‘์š”ํ•œ ์ปจํ…์ŠคํŠธ๋ฅผ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค. ์ˆ˜์ง‘ ํ•ญ๋ชฉ์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
    • ๋ผ์šฐํ„ฐ: Pages Router ๋˜๋Š” App Router
    • ์„œ๋ฒ„ ์ปจํ…์ŠคํŠธ: ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ, ์„œ๋ฒ„ ์•ก์…˜, ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ๋˜๋Š” ๋ฏธ๋“ค์›จ์–ด
  • ์ˆ˜์ง‘ํ•œ ์˜ค๋ฅ˜ ์ •๋ณด๋ฅผ ์„ ํ˜ธํ•˜๋Š” ๊ฐ€์‹œ์„ฑ ์ œ๊ณต์ž์— ๋ณด๊ณ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
export async function onRequestError(err, request, context) {
  await fetch('https://...', {
    method: 'POST',
    body: JSON.stringify({ message: err.message, request, context }),
    headers: { 'Content-Type': 'application/json' },
  });
}

export async function register() {
  // ์„ ํ˜ธํ•˜๋Š” ๊ฐ€์‹œ์„ฑ ์ œ๊ณต์ž SDK ์„ค์ •
}

onRequestError ํ•จ์ˆ˜์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ (opens in a new tab)์—์„œ ํ™•์ธํ•˜์„ธ์š”.

<Form> ์ปดํฌ๋„ŒํŠธ

์ƒˆ๋กœ์šด <Form> ์ปดํฌ๋„ŒํŠธ๋Š” HTML <form> ์š”์†Œ๋ฅผ ํ™•์žฅํ•˜์—ฌ ํ”„๋ฆฌํŽ˜์น˜, ํด๋ผ์ด์–ธํŠธ ์ธก ํƒ์ƒ‰, ์ ์ง„์  ํ–ฅ์ƒ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์ด ์ปดํฌ๋„ŒํŠธ๋Š” ๊ฒฐ๊ณผ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” ๊ฒ€์ƒ‰ ํผ๊ณผ ๊ฐ™์ด ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” ํผ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

// app/page.jsx
import Form from 'next/form';

export default function Page() {
  return (
    <Form action="/search">
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  );
}

<Form> ์ปดํฌ๋„ŒํŠธ์˜ ์ฃผ์š” ๊ธฐ๋Šฅ.

  • ํ”„๋ฆฌํŽ˜์น˜: ํผ์ด ๋ณด์ด๋ฉด ๋ ˆ์ด์•„์›ƒ (opens in a new tab)๊ณผ ๋กœ๋”ฉ (opens in a new tab) UI๊ฐ€ ๋ฏธ๋ฆฌ ๋กœ๋“œ๋˜์–ด ๋น ๋ฅธ ํƒ์ƒ‰์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ ์ธก ํƒ์ƒ‰: ํผ ์ œ์ถœ ์‹œ, ๊ณต์œ  ๋ ˆ์ด์•„์›ƒ๊ณผ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๊ฐ€ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
  • ์ ์ง„์  ํ–ฅ์ƒ: JavaScript๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋„ ํผ์€ ์ „์ฒด ํŽ˜์ด์ง€ ํƒ์ƒ‰์„ ํ†ตํ•ด ์ •์ƒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ด์ „์—๋Š” ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ๋งŽ์€ ์ˆ˜์ž‘์—…์ด ํ•„์š”ํ–ˆ์ง€๋งŒ, ์ด์ œ <Form> ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ„ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

'use client'

import { useEffect } from 'react' import { useRouter } from 'next/navigation'

export default function Form(props) { const action = props.action const router = useRouter()

useEffect(() => { // if form target is a URL, prefetch it if (typeof action === 'string') { router.prefetch(action) } }, [action, router])

function onSubmit(event) { event.preventDefault()

// grab all of the form fields and trigger a `router.push` with the data URL encoded
const formData = new FormData(event.currentTarget)
const data = new URLSearchParams()

for (const [name, value] of formData) {
  data.append(name, value as string)
}

router.push(`${action}?${data.toString()}`)

}

if (typeof action === 'string') { return <form onSubmit={onSubmit} {...props} /> }

return <form {...props} /> }

<Form> ์ปดํฌ๋„ŒํŠธ (opens in a new tab)์— ๋Œ€ํ•ด์„œ ๋” ์•Œ์•„๋ณด์„ธ์š”.

next.config.ts ์ง€์›

Next.js๋Š” ์ด์ œ TypeScript ํŒŒ์ผ ํ˜•์‹์ธ next.config.ts๋ฅผ ์ง€์›ํ•˜๋ฉฐ, ์ž๋™ ์™„์„ฑ๊ณผ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์œ„ํ•œ NextConfig ํƒ€์ž…์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

// next.config.ts
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  /* ์„ค์ • ์˜ต์…˜ ์ž…๋ ฅ */
};
 
export default nextConfig;

TypeScript ์ง€์›์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Next.js ๊ณต์‹ ๋ฌธ์„œ (opens in a new tab)์—์„œ ํ™•์ธํ•˜์„ธ์š”.

์ž์ฒด ํ˜ธ์ŠคํŒ… ๊ฐœ์„  ์‚ฌํ•ญ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž์ฒด ํ˜ธ์ŠคํŒ…ํ•  ๋•Œ, Cache-Control ์ง€์‹œ์— ๋Œ€ํ•œ ๋” ๋งŽ์€ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋Œ€ํ‘œ์ ์ธ ๊ฒฝ์šฐ๋กœ ISR ํŽ˜์ด์ง€์— ๋Œ€ํ•ด stale-while-revalidate ๊ธฐ๊ฐ„์„ ์ œ์–ดํ•˜๋Š” ์ƒํ™ฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ๋‘ ๊ฐ€์ง€ ๊ฐœ์„  ์‚ฌํ•ญ์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค:

next.config์—์„œ expireTime (opens in a new tab) ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ด์ „์˜ experimental.swrDelta ์˜ต์…˜์„ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์ด 1๋…„์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜์–ด ๋Œ€๋ถ€๋ถ„์˜ CDN์ด stale-while-revalidate๋ฅผ ์›ํ•˜๋Š” ๋Œ€๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๊ธฐ๋ณธ Cache-Control ๊ฐ’์„ ๋” ์ด์ƒ ์‚ฌ์šฉ์ž ์ง€์ • ๊ฐ’์„ ๋ฎ์–ด์“ฐ์ง€ ์•Š์•„, CDN ์„ค์ •๊ณผ์˜ ํ˜ธํ™˜์„ฑ์„ ๋ณด์žฅํ•˜๋ฉด์„œ ์™„์ „ํ•œ ์ œ์–ด๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ์ด๋ฏธ์ง€ ์ตœ์ ํ™” ๊ธฐ๋Šฅ์„ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ „์—๋Š” Next.js ์„œ๋ฒ„์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ตœ์ ํ™”ํ•˜๊ธฐ ์œ„ํ•ด sharp ์„ค์น˜๋ฅผ ๊ถŒ์žฅํ–ˆ์œผ๋‚˜, ์ข…์ข… ๋ˆ„๋ฝ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. Next.js 15์—์„œ๋Š” next start ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ๋…๋ฆฝ ์‹คํ–‰ ๋ชจ๋“œ (opens in a new tab)์—์„œ ์‹คํ–‰ํ•  ๋•Œ sharp์„ ์ž๋™์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ˆ˜๋™ ์„ค์น˜๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ Next.js ์ž์ฒด ํ˜ธ์ŠคํŒ…์— ๋Œ€ํ•œ ์ƒˆ๋กœ์šด ๋ฐ๋ชจ์™€ ํŠœํ† ๋ฆฌ์–ผ ๋น„๋””์˜ค (opens in a new tab)์—์„œ ํ™•์ธํ•˜์„ธ์š”.

์„œ๋ฒ„ ์•ก์…˜์˜ ๋ณด์•ˆ ๊ฐ•ํ™”

์„œ๋ฒ„ ์•ก์…˜(Server Actions)์€ ํด๋ผ์ด์–ธํŠธ์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋ฒ„ ์ธก ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ํŒŒ์ผ ์ƒ๋‹จ์— 'use server' ์ง€์‹œ์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ exportํ•˜์—ฌ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„œ๋ฒ„ ์•ก์…˜์ด๋‚˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๊ฐ€ ์ฝ”๋“œ์—์„œ ๋‹ค๋ฅธ ๊ณณ์— import๋˜์ง€ ์•Š๋”๋ผ๋„, ์ด๋Š” ์—ฌ์ „ํžˆ ๊ณต๊ฐœ์ ์œผ๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ HTTP ์—”๋“œํฌ์ธํŠธ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋™์ž‘์€ ๊ธฐ์ˆ ์ ์œผ๋กœ๋Š” ์˜ฌ๋ฐ”๋ฅด์ง€๋งŒ, ์˜๋„์น˜ ์•Š๊ฒŒ ์ด๋Ÿฌํ•œ ํ•จ์ˆ˜๊ฐ€ ๋…ธ์ถœ๋  ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐœ์„  ์‚ฌํ•ญ์„ ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ ์ œ๊ฑฐ: ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์„œ๋ฒ„ ์•ก์…˜์€ ํด๋ผ์ด์–ธํŠธ ์ธก ๋ฒˆ๋“ค์—์„œ ID๊ฐ€ ๋…ธ์ถœ๋˜์ง€ ์•Š์œผ๋ฉฐ, ์ด๋กœ ์ธํ•ด ๋ฒˆ๋“ค ํฌ๊ธฐ๊ฐ€ ์ค„์–ด๋“ค๊ณ  ์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค.
  • ๋ณด์•ˆ์„ฑ์ด ๊ฐ•ํ™”๋œ ์•ก์…˜ ID: Next.js๋Š” ์ถ”์ธก์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ  ๋น„๊ฒฐ์ •์ ์ธ ID๋ฅผ ์ƒ์„ฑํ•˜์—ฌ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„ ์•ก์…˜์„ ์ฐธ์กฐํ•˜๊ณ  ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ID๋Š” ๋นŒ๋“œ ๊ฐ„์— ์ฃผ๊ธฐ์ ์œผ๋กœ ์žฌ๊ณ„์‚ฐ๋˜์–ด ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์ œ ์ฝ”๋“œ.

// app/actions.js
"use server";
 
// ์ด ์•ก์…˜์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ๋˜๋ฏ€๋กœ Next.js๊ฐ€
// ํด๋ผ์ด์–ธํŠธ์—์„œ ์ฐธ์กฐ ๋ฐ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณด์•ˆ ID๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
export async function updateUserAction(formData) {}
 
// ์ด ์•ก์…˜์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ
// Next.js๊ฐ€ `next build` ์ค‘์— ์ž๋™์œผ๋กœ ์ด ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ 
// ๊ณต๊ฐœ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
export async function deleteUserAction(formData) {}

์„œ๋ฒ„ ์•ก์…˜์€ ์—ฌ์ „ํžˆ ๊ณต๊ฐœ HTTP ์—”๋“œํฌ์ธํŠธ๋กœ ์ทจ๊ธ‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์•ก์…˜ ๋ณด์•ˆ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ (opens in a new tab)๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

์™ธ๋ถ€ ํŒจํ‚ค์ง€ ๋ฒˆ๋“ค๋ง ์ตœ์ ํ™” (์•ˆ์ • ๋ฒ„์ „)

์™ธ๋ถ€ ํŒจํ‚ค์ง€๋ฅผ ๋ฒˆ๋“ค๋งํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ดˆ๊ธฐ ์‹œ์ž‘ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. App Router์—์„œ๋Š” ์™ธ๋ถ€ ํŒจํ‚ค์ง€๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฒˆ๋“ค๋ง๋˜๋ฉฐ, ์ƒˆ๋กœ์šด serverExternalPackages ์„ค์ • ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ํŒจํ‚ค์ง€๋ฅผ ๋ฒˆ๋“ค๋ง์—์„œ ์ œ์™ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Pages Router์—์„œ๋Š” ์™ธ๋ถ€ ํŒจํ‚ค์ง€๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฒˆ๋“ค๋ง๋˜์ง€ ์•Š์ง€๋งŒ, ๊ธฐ์กด transpilePackages ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฒˆ๋“ค๋งํ•  ํŒจํ‚ค์ง€ ๋ชฉ๋ก์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์˜ต์…˜์—์„œ๋Š” ๊ฐ ํŒจํ‚ค์ง€๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

App Router์™€ Pages Router์˜ ์„ค์ •์„ ํ†ตํ•ฉํ•˜๊ธฐ ์œ„ํ•ด, Pages Router์—์„œ ์™ธ๋ถ€ ํŒจํ‚ค์ง€๋ฅผ ์ž๋™์œผ๋กœ ๋ฒˆ๋“ค๋งํ•˜๋Š” bundlePagesRouterDependencies ์˜ต์…˜์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด App Router์˜ ๊ธฐ๋ณธ ๋ฒˆ๋“ค๋ง ๋™์ž‘๊ณผ ์ผ์น˜ํ•˜๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•„์š” ์‹œ serverExternalPackages๋ฅผ ์‚ฌ์šฉํ•ด ํŠน์ • ํŒจํ‚ค์ง€๋ฅผ ๋ฒˆ๋“ค๋ง์—์„œ ์ œ์™ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// next.config.ts
const nextConfig = {
  // Pages Router์—์„œ ์™ธ๋ถ€ ํŒจํ‚ค์ง€๋ฅผ ์ž๋™์œผ๋กœ ๋ฒˆ๋“ค๋ง:
  bundlePagesRouterDependencies: true,
  // App ๋ฐ Pages Router์—์„œ ํŠน์ • ํŒจํ‚ค์ง€๋ฅผ ๋ฒˆ๋“ค๋ง์—์„œ ์ œ์™ธ:
  serverExternalPackages: ['package-name'],
};

export default nextConfig;

์™ธ๋ถ€ ํŒจํ‚ค์ง€ ์ตœ์ ํ™”์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Next.js ๊ณต์‹ ๋ฌธ์„œ (opens in a new tab)์—์„œ ํ™•์ธํ•˜์„ธ์š”.

Next.js 15์—์„œ๋Š” ESLint 9์— ๋Œ€ํ•œ ์ง€์›์„ ๋„์ž…ํ•˜์—ฌ, 2024๋…„ 10์›” 5์ผ์— ์ข…๋ฃŒ๋œ ESLint 8์˜ ์ง€์›์„ ์ด์–ด๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ๊ฐœ๋ฐœ์ž๋Š” ESLint 8๊ณผ 9 ์ค‘ ์›ํ•˜๋Š” ๋ฒ„์ „์„ ์„ ํƒํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ESLint 9 ์ง€์›

ESLint 9 (opens in a new tab)๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ๊ฒฝ์šฐ, ์ƒˆ๋กœ์šด ๊ตฌ์„ฑ ํ˜•์‹ (opens in a new tab)์„ ์•„์ง ์ฑ„ํƒํ•˜์ง€ ์•Š์€ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” Next.js๊ฐ€ ์ž๋™์œผ๋กœ ESLINT_USE_FLAT_CONFIG=false ์„ค์ •์„ ์ ์šฉํ•˜์—ฌ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์›ํ™œํ•˜๊ฒŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, --ext ๋ฐ --ignore-path์™€ ๊ฐ™์€ ์‚ฌ์šฉ ์ค‘๋‹จ๋œ ์˜ต์…˜์€ next lint ์‹คํ–‰ ์‹œ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค. ESLint 10์—์„œ๋Š” ์ด๋Ÿฌํ•œ ์ด์ „ ๊ตฌ์„ฑ ์˜ต์…˜์ด ์™„์ „ํžˆ ์ง€์›๋˜์ง€ ์•Š์„ ์˜ˆ์ •์ด๋ฏ€๋กœ, ์กฐ์†ํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ (opens in a new tab)

์ด ์—…๋ฐ์ดํŠธ์˜ ์ผํ™˜์œผ๋กœ, React Hooks ์‚ฌ์šฉ์— ๋Œ€ํ•œ ์ƒˆ๋กœ์šด ๊ทœ์น™์„ ๋„์ž…ํ•œ eslint-plugin-react-hooks๋ฅผ v5.0.0์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ eslint-plugin-react-hooks@5.0.0์˜ ๋ณ€๊ฒฝ ๋กœ๊ทธ์—์„œ ํ™•์ธ (opens in a new tab)ํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ ๋ฐ ๋นŒ๋“œ ์„ฑ๋Šฅ ๊ฐœ์„ 

์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ HMR ๊ฐœ๋ฐœ ์ค‘์— ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋Š” ์ €์žฅ๋  ๋•Œ๋งˆ๋‹ค ๋‹ค์‹œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” API ์—”๋“œํฌ์ธํŠธ๋‚˜ ์„œ๋“œํŒŒํ‹ฐ ์„œ๋น„์Šค์— ๋Œ€ํ•œ fetch ์š”์ฒญ์ด ๋ฐ˜๋ณต์ ์œผ๋กœ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค.

๋กœ์ปฌ ๊ฐœ๋ฐœ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๊ณ  API ์š”์ฒญ์— ๋Œ€ํ•œ ๋น„์šฉ์„ ์ค„์ด๊ธฐ ์œ„ํ•ด, ์ด์ œ ํ•ซ ๋ชจ๋“ˆ ๊ต์ฒด(HMR)๊ฐ€ ์ด์ „ ๋ Œ๋”๋ง์—์„œ์˜ fetch ์‘๋‹ต์„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ HMR ์บ์‹œ (opens in a new tab)๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

App Router๋ฅผ ์œ„ํ•œ ๋” ๋น ๋ฅธ ์ •์  ์ƒ์„ฑ

์ •์  ์ƒ์„ฑ ๊ณผ์ •์„ ์ตœ์ ํ™”ํ•˜์—ฌ ํŠนํžˆ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ๋Š๋ฆฐ ํŽ˜์ด์ง€์˜ ๋นŒ๋“œ ์‹œ๊ฐ„์„ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด์ „์—๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก ํƒ์ƒ‰ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ํ•œ ๋ฒˆ, ์ดˆ๊ธฐ ํŽ˜์ด์ง€ ๋ฐฉ๋ฌธ์„ ์œ„ํ•œ HTML ๋ Œ๋”๋ง์„ ์œ„ํ•ด ๋‹ค์‹œ ํ•œ ๋ฒˆ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐฉ์‹์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์„ ์žฌ์‚ฌ์šฉํ•˜์—ฌ ๋‘ ๋ฒˆ์งธ ๋ Œ๋”๋ง์„ ์ƒ๋žตํ•จ์œผ๋กœ์จ ์ž‘์—…๋Ÿ‰๊ณผ ๋นŒ๋“œ ์‹œ๊ฐ„์„ ์ค„์˜€์Šต๋‹ˆ๋‹ค.

์ถ”๊ฐ€๋กœ, ์ •์  ์ƒ์„ฑ ์ž‘์—…์ž๋“ค์ด ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€์—์„œ fetch ์บ์‹œ๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. ์บ์‹œ์—์„œ ์ œ์™ธ๋˜์ง€ ์•Š์€ fetch ํ˜ธ์ถœ์˜ ๊ฒฐ๊ณผ๋Š” ๋™์ผํ•œ ์ž‘์—…์ž๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋Š” ๋‹ค๋ฅธ ํŽ˜์ด์ง€์—์„œ๋„ ์žฌ์‚ฌ์šฉ๋˜์–ด, ๋™์ผํ•œ ๋ฐ์ดํ„ฐ ์š”์ฒญ ํšŸ์ˆ˜๊ฐ€ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค.

๊ณ ๊ธ‰ ์ •์  ์ƒ์„ฑ ์ œ์–ด (์‹คํ—˜์ )

๊ณ ๊ธ‰ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์œ„ํ•ด ์ •์  ์ƒ์„ฑ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋” ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ์‹คํ—˜์  ์ง€์› ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํŠน๋ณ„ํ•œ ์š”๊ตฌ ์‚ฌํ•ญ์ด ์—†๋‹ค๋ฉด ํ˜„์žฌ ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์˜ต์…˜์€ ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€์™€ ๋™์‹œ์„ฑ ์ฆ๊ฐ€๋กœ ์ธํ•ด ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

// next.config.ts
const nextConfig = {
  experimental: {
    // ํŽ˜์ด์ง€ ์ƒ์„ฑ ์‹คํŒจ ์‹œ, ๋นŒ๋“œ๊ฐ€ ์‹คํŒจํ•˜๊ธฐ ์ „ ์žฌ์‹œ๋„ ํšŸ์ˆ˜
    staticGenerationRetryCount: 1,
    // ์ž‘์—…์ž๋‹น ์ฒ˜๋ฆฌํ•  ํŽ˜์ด์ง€ ์ˆ˜
    staticGenerationMaxConcurrency: 8,
    // ์ƒˆ๋กœ์šด ์ž‘์—…์ž ์Šค๋ ˆ๋“œ๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•œ ์ตœ์†Œ ํŽ˜์ด์ง€ ์ˆ˜
    staticGenerationMinPagesPerWorker: 25,
  },
};
 
export default nextConfig;

์ •์  ์ƒ์„ฑ ์˜ต์…˜์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Next.js ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.