Next.js v15๋ถํฐ๋ react ๋ฐ react-dom ์ต์ ๋ฒ์ ์ด v19์ ๋๋ค.
{
"name": "nextjs",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "15.0.2",
"react": "19.0.0-rc-0bc30748-20241028",
"react-dom": "19.0.0-rc-0bc30748-20241028"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"typescript": "^5"
},
"packageManager": "yarn@4.2.2"
}Next v13๋ถํฐ app ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ์ ๋ฐ๋ผ ๋ผ์ฐํ ๋๋ฉฐ ๊ฐ ํ์ผ์ ์ญํ (๊ธฐ๋ฅ)๊ณผ ์ปจ๋ฒค์ ์ด ์ง์ ๋์ด ์์ต๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ์ปดํฌ๋ํธ๋ค์ RSC(React Server Component)๋ก ๋์ํฉ๋๋ค. ๋ง์ฝ RCC(React Client Component)๋ก ์ค์ ํ๊ธฐ ์ํด์๋ ํ์ผ ์ต์๋จ์ "use client";๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
// app/page.tsx
'use client' // RCC ์ปดํฌ๋ํธ ์ ์ธ
export default function Page() {
return <main>Content,,,</main>
}๋๋ ํ ๋ฆฌ๋ช ์ "[folderName]"์ฒ๋ผ ๋๊ดํธ๋ก ๊น์ผ ๊ฒฝ์ฐ ๊ธฐ์กด ๋ค์ด๋๋ฏน ๋ผ์ฐํ ์ผ๋ก ๋์ํฉ๋๋ค.
๋ค์ด๋๋ฏน ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๋ page.tsx, layout.tsx, route.ts, generateMetadata ํจ์์ params prop์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค.
// app/blog/[slug]/page.tsx
type Params = Promise<{ slug: string }>
interface PageProps {
params?: Params // ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ ๊ฐ params[folderName]์ผ๋ก ์ ๊ทผ
}
export default function Page({ params }: PageProps) {
/**
* URL: "/blog/b" , params: { slug: 'b' }
* URL: "/blog/a" , params: { slug: 'a' }
**/
return <main>Content,,,</main>
}๋ค์ด๋๋ฏน ๋ผ์ฐํ ์ด๋ URL ๊ฒฝ๋ก๊ฐ์ผ๋ก ํ์ด์ง ์ปดํฌ๋ํธ ๋ด ๋ ๋๋ง๋ ์ ๋ณด๋ฅผ ๋์ ์ผ๋ก ๊ฒฐ์ ํ์ฌ ํ์ด์ง๋ฅผ ๋ ๋๋งํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ์ด๋ ํ๋์ ํ์ด์ง๋ฅผ ์ฌ์ฌ์ฉํ์ฌ ์ฌ๋ฌ ์ฝํ ์ธ ๋ฅผ ๋์ํ๊ธฐ ์ํด์ ์ฌ์ฉํฉ๋๋ค.
๋๋ ํ ๋ฆฌ ๋ช ์ "[...forderName]"์ผ๋ก ์์ฑํ๊ฒ ๋๋ฉด ๋งค์นญ๋๋ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๊ฐ ์๋ ๊ฒฝ์ฐ Catch-All Segments๊ฐ ํ์ฑํ ๋ฉ๋๋ค.
-
Route: "/app/shop/[...slug]/page.tsx" -> URL: "/shop/a" -> params: { slug: ['a'] }
-
Route: "/app/shop/[...slug]/page.tsx" -> URL: "/shop/a/b" -> params: { slug: ['a', 'b'] }
-
Route: "/app/shop/[...slug]/page.tsx" -> URL: "/shop/a/b/c" -> params: { slug: ['a', 'b', 'c'] }
์ ์์ ์ฒ๋ผ Catch-All Routes๋ params๋ก ์ฌ๋ฌ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ ๊ฐ๋ค์ ์์๋ก ๊ฐ๋ ๋ฐฐ์ด๋ก ์ ๋ฌ๋ฐ์ต๋๋ค.
Catch-All Segments๋ params๊ฐ 1๊ฐ ์ด์์ธ ๊ฒฝ์ฐ์๋ง ํ์ฑํ ๋์ง๋ง "[[...forderName]]"์ผ๋ก ๋๋ ํ ๋ฆฌ๋ช ์ ์์ฑํ๋ฉด ํด๋น Catch-All Segments๋ params๊ฐ 0๊ฐ์ธ ๊ฒฝ์ฐ์๋ ํ์ฑํ ๋ฉ๋๋ค.
- Route: "/app/shop/[[...slug]]/page.tsx" -> URL: "/shop" -> params: { }
์ ์์ ์ฒ๋ผ params๊ฐ 0๊ฐ์ธ ๊ฒฝ์ฐ params๋ ๋น ๊ฐ์ฒด๊ฐ ์ ๋ฌ๋ฉ๋๋ค.
์ผ๋ฐ์ ์ผ๋ก app๋๋ ํ ๋ฆฌ ๋ด ๋๋ ํ ๋ฆฌ๋ช ๋ค์ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๋ก URL ๊ฒฝ๋ก์ ๋งคํ๋ฉ๋๋ค. ํ์ง๋ง ๋๋ ํ ๋ฆฌ๋ช ์ "(folderName)"์ฒ๋ผ ์๊ดํธ๋ก ๊ฐ์ผ ๊ฒฝ์ฐ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๋ก ์ค์ ๋์ง ์์ผ๋ฉฐ ์ค์ ๋ผ์ฐํ ์์ ํด๋น ๊ฒฝ๋ก๋ ๋ฌด์๋ฉ๋๋ค.
์๋ฅผ ๋ค์ด, "app/(marketing)/about" ๊ฒฝ๋ก์ ์์ฑ๋ ํ์ด์ง๋ "/(marketing)/about"์์ "(marketing)"์ด ๋ฌด์๋ "/about"์ผ๋ก ๋ผ์ฐํ ๋ฉ๋๋ค.
์ฆ, ์๊ดํธ๋ก ๋๋ ํ ๋ฆฌ๋ฅผ ๋ง๋ค์ด ๊ฒฝ๋ก๋ค์ ๊ทธ๋ฃน์ ์์ฑํ ์ ์์ผ๋ฉฐ, layout.tsx๋ template.tsx๋ฅผ ํตํด URL ๊ฒฝ๋ก์ ์ํฅ์ ์ฃผ์ง ์๊ณ ๊ฒฝ๋ก ๊ทธ๋ฃน๋ณ layout์ ์ค์ ํ ๋ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๋๋ ํ ๋ง๋ช ์ "@folderName"๋ก ์์ฑํ ๊ฒฝ์ฐ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๊ฐ ์๋ ์ฌ๋กฏ์ผ๋ก ์ค์ ๋๋ฉฐ ์ค์ ๋ก ํด๋น ๊ฒฝ๋ก๋ ๋ฌด์๋ฉ๋๋ค.
ํจ๋ฌ๋ ๋ผ์ฐํ ์ ํ๋์ ๋ ์ด์์์ ์ฌ๋ฌ page๋ฅผ ํ์ํ๊ธฐ ์ํด์ ์ฌ์ฉํฉ๋๋ค.
app
โโโ page.tsx
โ
โโโ layout.tsx
โ
โโโ @team
โโโ settings
โโโ page.tsx์ ๊ตฌ์กฐ์ฒ๋ผ "/app/@team/settings/page.tsx"๊ฐ ์กด์ฌํ ๋ "/"์์ "/settigns"๋ก ์ด๋ํ๊ฒ ๋๋ฉด "/app/page.tsx"๊ฐ ๋ ๋๋ง๋ ์ํ์์ "/app/@team/settgins/page.tsx"๋ ๋ณ๋ ฌ์ ์ผ๋ก ๋ ๋๋งํ ์ ์์ต๋๋ค. ์ค์ URL์ "/settings"์ด์ง๋ง "/app/page.tsx"๊ฐ ๋ ๋๋ง๋ ์ํ๋ฅผ ์ ์งํ๋ฉด์ "/app/@team/settings/page.tsx"๋ ๋ณ๋ ฌ๋ก ๋ ๋๋ง ์ํฌ ์ ์์ต๋๋ค.
"@forderName/page.tsx"์์ exportํ ์ปดํฌ๋ํธ์ ๊ฒฝ์ฐ @forderName์ ๋์ผํ ๋ ๋ฒจ์ ์๋ layout ์ปดํฌ๋ํธ prop์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค. ๋ง์ฝ ๋์ผํ ๋ ๋ฒจ์ ์๋ค๋ฉด ๊ฐ์ฅ ๊ฐ๊น์ด ์์ layout.tsx์ Layout ์ปดํฌ๋ํธ์๊ฒ ์ ๋ฌ๋ฉ๋๋ค. ์ด๋ ์ ๋ฌ๋๋ prop ๋ค์ด๋ฐ์ ๋๋ ํ ๋ฆฌ๋ช (forderName)์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค.
์๋ฅผ ๋ค์ด, "/app/@modal/page.tsx"๊ฐ export defaultํ ์ปดํฌ๋ํธ๋ "/app/layout.tsx"๊ฐ export defaultํ Layout ์ปดํฌ๋ํธ์ modal prop์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค.
// app/layout.tsx
import { ReactNode } from 'react'
interface LayoutProps {
children: ReactNode // "app/page.tsx"์์ export defaultํ ์ปดํฌ๋ํธ
modal?: ReactNode // "app/@modal/page.tsx"์์ export defaultํ ์ปดํฌ๋ํธ
}
export default function Layout({ children, modal }: LayoutProps) {
<html lang="ko">
<body>
{children}
{modal}
</body>
</html>
}ํ๋ฌ๋ ๋ผ์ฐํ ์ ์ฃผ์ํ ์ ์ผ๋ก๋ ์๋์ ๊ฐ์ต๋๋ค.
- Soft Navigation ์ฌ์ฉํ๋ ๊ฒฝ์ฐ Next๋ ํ์ฌ ์ผ์นํ๋ ์ฌ๋กฏ์ด ์๋๋ผ๋ฉด ์ด์ ํ์ฑํ๋ ์ฌ๋กฏ ํ์ฑ์ ์ ์งํฉ๋๋ค. ๋ํ ์์ฒญํ ๊ฒฝ๋ก์ ๋ํ ํ์ด์ง๊ฐ ์๋๋ผ๋ 404 ์๋ฌ๋ฅผ ํ์ํ์ง ์๊ณ ์ด์ ์ ํ์ฑํ๋ ํ์ด์ง๋ฅผ ๊ณ์ ํ์ํด์ค๋๋ค.
app
โโโ page.tsx
โ
โโโ layout.tsx
โ
โโโ @team
โ โโโ default.tsx
โ โโโ settings
โ โโโ page.tsx
โ
โโโ @analytics
โโโ page.tsxapp ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ๊ฐ ์์ ๊ฐ๊ณ ์ด๊ธฐ ๊ฒฝ๋ก๊ฐ "/"์ผ ๋ "/app/@analytics/page.tsx"๊ฐ ํ์ฑํ๋ฉ๋๋ค. "/app/@team/page.tsx"๋ ์กด์ฌํ์ง ์์ผ๋ฏ๋ก "/app/@team/default.tsx"๊ฐ ํ์ฑํ๋ฉ๋๋ค.
์ดํ "/"์์ "/settings"๋ก Soft Navigation ์ฌ์ฉํ๋ค๋ฉด "/app/@team/settings/page.tsx"์ด ํ์ฑํ ๋ฉ๋๋ค. ์ด๋ "/app/@analytics/settings/page.tsx"๊ฐ ์กด์ฌํ์ง ์๊ธฐ ๋๋ฌธ์ ๊ธฐ์กด์ ํ์ฑํ๋ "/app/@analytics/page.tsx" ํ์ฑ์ ์ ์งํฉ๋๋ค. "/app/settings/page.tsx" ๋ํ ์กด์ฌํ์ง ์์ง๋ง 404 ์๋ฌ๋ฅผ ํ์ํ์ง ์๊ณ ๊ธฐ์กด์ ํ์ฑํ๋ ํ์ด์ง("/app/page.tsx") ๋ ๋๋ง์ ์ ์งํฉ๋๋ค. ์ฆ, ์ค์ ๊ฒฝ๋ก๋ "/settings"์ธ๋ฐ ๋ ๋๋ง๋ ํ์ด์ง๋ "/app/page.tsx"์ด๋ฉฐ "/app/@analytics/page.tsx"์ "/app/@team/settings/page.tsx"๊ฐ ํ์ฑ๋ ์ํ๊ฐ ๋ฉ๋๋ค.
์ฌ๋กฏ๊ณผ ์ธ๊ทธ๋จผํธ๋ ์๋ก ๋ ๋ฆฝ์ ์ ๋๋ค. ๋ค์ ๋งํด, ์ค์ ๊ฒฝ๋ก์ ๋งค์นญ๋๋ ์ธ๊ทธ๋จผํธ์ ์ฌ๋กฏ ์ด๋ฆ์ด ๋์ผํ๋๋ผ๋ ์๋ก ์ถฉ๋ํ์ง ์๊ณ ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํ๊ฒ ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, "/app/settings/page.tsx"์ "/app/@team/settings/page.tsx" ๋ ๋ค ์กด์ฌํ๋๋ผ๋ ์๋ก ์ถฉ๋ํ์ง ์์ผ๋ฉฐ ์ค์ ๊ฒฝ๋ก๊ฐ "/settings"์ผ ๋ ๋ ๋ค ๋ ๋๋ง๋ฉ๋๋ค.
- Hard Navigation ์ฌ์ฉํ๋ ๊ฒฝ์ฐ Next๋ ๋จผ์ ๋งค์นญ๋๋ ์ฌ๋กฏ์ ํ์ธํ๊ณ ๋ง์ฝ ์๋ ๊ฒฝ์ฐ์๋ ์์ ๋๋ ํ ๋ฆฌ๋ก ์ฌ๋ผ๊ฐ๋ฉด์ ๊ฐ์ฅ ๊ฐ๊น์ด default.tsx๊ฐ export defaultํ ์ปดํฌ๋ํธ ๋ ๋๋ง์ ์๋ํฉ๋๋ค. ์ด๋ default.tsx ํ์ผ์กฐ์ฐจ ์๋ค๋ฉด 404์๋ฌ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
app
โโโ page.tsx
โ
โโโ layout.tsx
โ
โโโ @team
โ โโโ default.tsx
โ โโโ settings
โ โโโ page.tsx
โ
โโโ @analytics
โโโ page.tsxapp ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ๊ฐ ์์ ๊ฐ์ ๋ "/settings"๋ก Hard Navigationํ๋ ๊ฒฝ์ฐ "/app/settings/page.tsx"๊ฐ ์กด์ฌํ์ง ์์ 404์๋ฌ๋ฅผ ํ์ํ๊ฒ ๋ฉ๋๋ค. ๋ง์ฝ "/app/settings/page.tsx"๊ฐ ์๋ค ํ๋๋ผ๋ "/app/@analytics/settings/page.tsx"์ด ์๊ธฐ ๋๋ฌธ์ "/app/@analytics/default.tsx" ๋ ๋๋ง์ ์๋ํ๋๋ฐ, ํด๋น ํ์ผ์กฐ์ฐจ ์กด์ฌํ์ง ์์ 404์๋ฌ๊ฐ ๋ฐ์์ํค๊ฒ ๋ฉ๋๋ค.
์ฆ, Hard Navigationํ๋ ๊ฒฝ์ฐ์๋ ์ด์ ์ ํ์ฑํ๋ ์ฌ๋กฏ์ด๋ ๋ ๋๋ง๋ ํ์ด์ง์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ๊ณ ์์ง ์๊ธฐ ๋๋ฌธ์ Soft Navigation๊ณผ ๋ค๋ฅด๊ฒ ๋์ํ๊ฒ ๋ฉ๋๋ค.
๋๋ ํ ๋ฆฌ๋ช ์ "(...)fortuneName", "(..)fortuneName" ํน์ "(.)fortuneName"๋ก ์์ฑํ ๊ฒฝ์ฐ ์ธํฐ์ ํ ๋ผ์ฐํธ๋ก ๋์ํฉ๋๋ค.
-
(.): ๋์ผํ ๋ ๋ฒจ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๋ฅผ ์ธํฐ์ ํ
-
(..): ํ๋์ ์์ ๋ ๋ฒจ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๋ฅผ ์ธํฐ์ ํ
-
(..)(..): ๋ ๊ฐ์ ์์ ๋ ๋ฒจ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๋ฅผ ์ธํฐ์ ํ
-
(...): app ๋๋ ํ ๋ฆฌ ๋ ๋ฒจ์ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๋ฅผ ์ธํฐ์ ํ
์ฃผ์ํ ์ ์ผ๋ก Route Groups, Slot(Parallel Routes), Private Routes ๋ฑ URL ๊ฒฝ๋ก์ ์ํฅ์ ์ฃผ์ง ์๋ ๊ฒ๋ค์ ๋ฌด์๋์ด ์ธํฐ์ ํ ๋ฉ๋๋ค. ์ฆ, ํ์ผ ์์คํ ์ด ์๋ ๋ผํฌํธ ์ธ๊ทธ๋จผํธ๋ง์ ๊ณ ๋ คํ์ฌ ์ธํฐ์ ํ ํฉ๋๋ค.
app
โโโ page.tsx
โ
โโโ layout.tsx
โ
โโโ dashborad
โ โโโ (..i)
โ โโโ page.tsx
โ
โโโ i
โโโ page.tsxapp ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ๊ฐ ์์ ๊ฐ์ ๋ "/i"๋ก ์ด๋ํ๋ ๊ฒฝ์ฐ "app/i/page.tsx"๊ฐ ์๋ "app/dashboard/(..i)/page.tsx"๊ฐ ๋ ๋๋ง๋ฉ๋๋ค.
์ธํฐ์ ํ ๋ผ์ฐํธ๋ Soft Navigation์ ๊ฒฝ์ฐ์๋ง ์ธํฐ์ ํ ๋๋ฉฐ Soft Navigation์ด ์๋ ๊ฒฝ์ฐ ๊ธฐ์กด ํ์ด์ง ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋ฉ๋๋ค. ์ฆ, "/i"๋ก Hard Navigation์ ์ฌ์ฉํ์ฌ ์ด๋ํ๋ ๊ฒฝ์ฐ์๋ "app/i/page.tsx"๊ฐ ๋ ๋๋ง๋ฉ๋๋ค.
Parallel Routes๋ด Intercepting Routes๋ฅผ ํจ๊ป ์ฌ์ฉํ์ฌ ๋ ๋ฆฝ๋ URL ๊ฒฝ๋ก๋ฅผ ๊ฐ๋ ๋ชจ๋ฌ์ ๊ตฌํํ ์ ์์ต๋๋ค. ๋ ๋ฆฝ๋ URL ๊ฒฝ๋ก๋ฅผ ๊ฐ๋ ๋ชจ๋ฌ์ ๊ฒฝ์ฐ URL์ ํตํด ๋ชจ๋ฌ ๋ด์ฉ์ ๊ณต์ ํ ์ ์์ผ๋ฉฐ, Hard Navigation์ ์ฌ์ฉํ๋๋ผ๋ ๊ทธ ์ํ๊ฐ ์ ์ง๋๊ณ , ๋ค๋ก ๊ฐ๊ธฐ ํน์ ์์ผ๋ก ๊ฐ๊ธฐ๋ฅผ ํตํด ๋ชจ๋ฌ์ ์ด๊ฑฐ๋ ๋ซ์ ์๋ ์์ต๋๋ค.
app
โโโ page.tsx
โ
โโโ layout.tsx
โ
โโโ @modal
โโโ login
โโโ page.tsx๋ง์ฝ ์์ ๊ฐ์ app ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง ๊ฒฝ์ฐ, "/login"์ผ๋ก Soft Navigation ํ๋ค๋ฉด "/app/@modal/login/page.tsx"๊ฐ ํ์ฑํ ๋์ง๋ง, Hard Navigation์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ 404 ์๋ฌ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค. Hard Navigation ํ๋ค๋ฉด ์ค์ ๊ฒฝ๋ก์ ๋งค์นญ๋๋ ํ์ด์ง๋ฅผ ๋ ๋๋งํ๊ฒ ๋๋๋ฐ ์ ๊ตฌ์กฐ์ ๊ฒฝ์ฐ "/app/login/page.tsx" ํ์ผ์ด ์กด์ฌํ์ง ์์ 404 ์๋ฌ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
app
โโโ page.tsx
โ
โโโ layout.tsx
โ
โโโ @modal
| โโโ login
| โโโ page.tsx
|
โโโ login
โโโ page.tsx๊ทธ๋ ๋ค๋ฉด ์์ ๊ฐ์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง ๋ Hard Navigation์ ์ฌ์ฉํ๋ฉด 404์๋ฌ๋ ๋ฐ์ํ์ง ์์ง๋ง, Soft Navigation๊ณผ Hard Navigation ๋ ๋ค "/app/login/page.tsx"๊ฐ ๋ ๋๋ง๋๊ณ "/app/@modal/login/page.tsx"๊ฐ ๋ณ๋ ฌ ๋ผ์ฐํ ๋ฉ๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ๋ชจ๋ฌ์ ๊ฒฝ์ฐ ๊ธฐ์กด ํ์ด์ง ๋ ๋๋ง์ ์ ์งํ๋ฉด์ ๋ชจ๋ฌ์ ํ์ํด์ฃผ์ด์ผ ํ์ง๋ง ์์ ๊ฒฝ์ฐ ๊ธฐ์กด ํ์ด์ง ๋ ๋๋ง์ ์ ์งํ์ง ๋ชปํ๊ณ "/app/login/page.tsx"๊ฐ ๋ ๋๋ง๋ฉ๋๋ค.
๊ตฌํํ๋ ค๋ ๋์์ Soft Navigation์ผ๋ก "/login"์ผ๋ก ์ด๋ํ๊ฒ ๋๋ฉด ๊ธฐ์กด ๋ ๋๋ง๋ ํ์ด์ง์ ๋ณ๋ ฌ๋ก "/app/@modal/login/page.tsx"๊ฐ ๋ ๋๋ง๋๊ณ , Hard Navigationํ๋ ๊ฒฝ์ฐ์๋ 404 ์๋ฌ๊ฐ ๋ฐ์ํ์ง ์์ผ๋ฉด์ "/app/login/page.tsx"๋ง ๋ ๋๋ง๋๋๋ก ํ๋ ค๋ฉด Parallel Routes์ Intercepting Routes๋ฅผ ๊ฐ์ด ์ฌ์ฉํ์ฌ ๊ตฌํํ ์ ์์ต๋๋ค.
// app/@modal/(.)login/page.tsx
import Modal from '@/shard/components/Modal'
export default function Page() {
return (
<Modal>
<form>Login Form,,,</form>
</Modal>
)
}// app/login/page.tsx
export default function Page() {
return <form>Login Form,,,</form>
}"app/login/page.tsx"์ "app/@modal/(.)login/page.tsx"๋ฅผ ์์ ๊ฐ์ด ์์ฑํด์ค๋๋ค.
app
โโโ page.tsx
โ
โโโ layout.tsx
โ
โโโ @modal
| โโโ (.)login
| โโโ page.tsx
|
โโโ login
โโโ page.tsx๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ๊ฐ ์์ ๊ฐ์ ๋ Soft Navigation์ ์ฌ์ฉํ์ฌ "/login"์ผ๋ก ์ด๋ํ๋ ๊ฒฝ์ฐ "/app/@modal/(.)login/page.tsx"๊ฐ ๋ ๋๋ง๋ฉ๋๋ค. ์ถ๊ฐ์ ์ผ๋ก Hard Navigationํ์ฌ ์ด๋ํ ๊ฒฝ์ฐ์๋ "/app/login/page.tsx"๋ง์ด ๋ ๋๋ง๋ฉ๋๋ค.
layout.tsx ํ์ผ์์ export default๋ Layout ์ปดํฌ๋ํธ๋ props๋ก page.tsx๊ฐ export defaultํ ์ปดํฌ๋ํธ๋ฅผ children prop์ผ๋ก ์ ๋ฌ๋ฐ์ต๋๋ค. ๋ค์ด๋๋ฏน ๋ผ์ฐํธ์ ๊ฒฝ์ฐ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ ๊ฐ์ ์ถ๊ฐ์ ์ผ๋ก params prop๋ ์ ๋ฌ๋ฐ์ผ๋ฉฐ ์ด๋ ๋์ ๋ผ์ฐํ ์ ๋ํ ๊ฒฝ๋ก๊ฐ์ ๊ฐ์ฒด ํํ๋ก ์ ๋ฌ๋ฐ์ต๋๋ค. ๋์ผํ ๋ ๋ฒจ์ ํ๋ฌ๋ ๋ผ์ฐํธ๊ฐ ์กด์ฌํ๋ค๋ฉด ์ถ๊ฐ์ ์ผ๋ก ํด๋น ์ฌ๋กฏ๋ช ์ผ๋ก ์ปดํฌ๋ํธ๋ฅผ prop์ผ๋ก ์ ๋ฌ๋ฐ์ต๋๋ค.
app ๋๋ ํ ๋ฆฌ์ ๋ฃจํธ ๋ ๋ฒจ์๋ ํ์์ ์ผ๋ก ํ๋์ layout.tsx("app/layout.tsx") ํ์ผ์ด ํ์ํ๋ฉฐ, ํด๋น ์ปดํฌ๋ํธ๋ html๊ณผ body ํ๊ทธ๋ฅผ ์ ์ํฉ๋๋ค.
app ๋๋ ํ ๋ฆฌ ๋ด ๊ฐ ํ์ ๋๋ ํ ๋ฆฌ ๋ง๋ค ํ๋์ฉ ์ค์ ๊ฐ๋ฅํ๋ฉฐ ๋ง์ฝ ํ์ ๋๋ ํ ๋ฆฌ์ layout.tsx๊ฐ ์กด์ฌํ๋ ๊ฒฝ์ฐ ์์ layout.tsx๊ฐ ํ์ layout.tsx๋ฅผ ํฌํจํ๋ ํ์์ผ๋ก ์ ์ฉ(nested layout)๋ฉ๋๋ค.
์ฃผ์ํ ์ ์ผ๋ก๋ ํ์ ๊ฒฝ๋ก๋ก Soft Navigation์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ Layout ์ปดํฌ๋ํธ๋ ๋ฆฌ๋ ๋๋ง ๋์ง ์์ต๋๋ค. ๋ง์ฝ ๋ฆฌ๋ ๋๋ง์ด ํ์ํ Layout ์ปดํฌ๋ํธ๊ฐ ํ์ํ ๊ฒฝ์ฐ layout.tsx ๋์ template.tsx ์ฌ์ฉํด์ผ ํฉ๋๋ค.
// app/layout.tsx
import { ReactNode } from 'react'
type Params = Promise<{ slug: string }>
interface LayoutProps {
children: ReactNode // app/page.tsx์์ export default๋ ์ปดํฌ๋ํธ
params?: Params // ๋ค์ด๋๋ฏน ๋ผ์ฐํ
๋๋ ๊ฒฝ์ฐ ๋์ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ ๊ฐ
}
export default function Layout({ children }: LayoutProps) {
return (
<html>
<body>{children}</body>
</html>
)
}template.tsx๋ layout.tsx์ ์ ์ฌํ๊ฒ ํ์ด์ง ๋ ์ด์์ ์ญํ ์ ํ์ง๋ง Layout ์ปดํฌ๋ํธ์๋ ๋ค๋ฅด๊ฒ Soft Navigationํ๋ ๊ฒฝ์ฐ ๋งค๋ฒ ์๋กญ๊ฒ ๋ ๋๋ง๋ฉ๋๋ค.
template.tsx์ layout.tsx๋ฅผ ๊ฐ์ด ์ฌ์ฉํ๊ฒ ๋๋ค๋ฉด ์๋์ ๊ฐ์ด Layout ์ปดํฌ๋ํธ ํ์์ Template ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋๊ณ , Template ์ปดํฌ๋ํธ ํ์์ Page ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋ฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก Layout๊ณผ ์ ์ฌํ ์ญํ ์ ํ๋ฏ๋ก ํ๋๋ง ์ฌ์ฉํ๋ฉฐ, ๋๋๋ก ํน๋ณํ ์ด์ ๊ฐ ์๋ค๋ฉด Layout ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
<Layout>
{/* Note that the template is given a unique key. */}
<Template key={routeParam}>{children}</Template>
</Layout>Template ์ปดํฌ๋ํธ๋ ์๋์ ์ฒ๋ผ ๋งค๋ฒ ๋ ๋๋ง์ด ํ์ํ ๊ฒฝ์ฐ์ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
-
"use client"๋ฅผ ์์ฑํ์ฌ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๋ก ์ค์ ํ๊ณ useEffect๋ useStateํ ์ ์์กดํ๋ ๊ธฐ๋ฅ์ด ํ์ํ ๊ฒฝ์ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
-
Next ๊ธฐ์กด ํ๋ ์์ํฌ ๋์์ ๋ณ๊ฒฝํ๊ณ ์ถ์ ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, Layout ์ปดํฌ๋ํธ ํ์์ Suspense ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋๋ผ๋ fallback ์ปดํฌ๋ํธ๋ ์ฒ์์๋ง ํ์๋๊ณ ์ดํ ๋ผ์ฐํ ๋๋๋ผ๋ ํ์๋์ง ์์ง๋ง Template ์ปดํฐ๋ํธ๋ ๋ผ์ฐํ ๋ ๋๋งค๋ค ํ์ํด์ค๋๋ค.
page.tsx์์ export defaultํ ์ปดํฌ๋ํธ๋ layout.tsx(or template.tsx)์์ export defaultํ ์ปดํฌ๋ํธ children prop์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค. ์ฆ, ์ค์ ํ์ด์ง ์ปจํ ์ธ ๋ฅผ ๋ํ๋ด๋ ์ปดํฌ๋ํธ์ ๋๋ค.
๋๋ ํ ๋ฆฌ๋ง๋ค page.tsx ํ์ผ์ ํ์์ด๋ฉฐ, ๋ง์ฝ page.tsxํ์ผ์ด ์๋ค๋ฉด ํด๋น ๊ฒฝ๋ก๋ก ๋ผ์ฐํ ๋์ง ์์ต๋๋ค.
Page ์ปดํฌ๋ํธ๋ ๋์ ๋ผ์ฐํธ์ ๊ฒฝ์ฐ ๋์ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ ๊ฐ์ params prop์ผ๋ก ์ ๋ฌ๋ฐ์ผ๋ฉฐ, ์ฟผ๋ฆฌ์คํธ๋ง์ ๋ํ ์ ๋ณด๋ searchParams prop์ผ๋ก ์ ๋ฌ๋ฐ์ต๋๋ค.
// app/page.tsx
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
interface PageProps {
params?: Params // ๋์ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ์ ๋ํ ์ ๋ณด
searchParams?: SearchParams // ์ฟผ๋ฆฌ์คํธ๋ง ๊ฐ์ ๋ํ ์ ๋ณด
}
export default function Page({ params, searchParams }: PageProps) {
return <section>Page Content,,,</section>
}default.tsx ํ์ผ์ด export defaultํ ์ปดํฌ๋ํธ๋ Slot(Parallel Routes)์ ๋ํ fallback ์ปดํฌ๋ํธ ์ ๋๋ค.
ํ๋ฌ๋ ๋ผ์ฐํธ๋ฅผ ์ฌ์ฉํ์ง ์๋ ํ์ ๊ฒฝ๋ก๊ฐ ์กด์ฌํ๋ ๊ฒฝ์ฐ์๋ default.tsx ํ์ผ์ ์์ ๊ฒฝ๋ก์ ์ถ๊ฐํด์ฃผ์ด์ผ ํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ์ผ์นํ๋ ์ฌ๋กฏ์ด ์กด์ฌํ์ง ์์ ๋ Hard Navigationํ๋ ๊ฒฝ์ฐ Layout ์ปดํฌ๋ํธ๊ฐ 404 ์๋ฌ๋ฅผ ํ์ํ๊ฒ ๋ฉ๋๋ค.
์๋ฅผ ๋ค์ด, ํ๋ฌ๋ ๋ผ์ฐํธ๊ฐ "/app/@team/settings/page.tsx"์๋ง ์กด์ฌํ๋ ๊ฒฝ์ฐ "/"์ผ๋ก ๋ผ์ฐํ ๋๋ ๊ฒฝ์ฐ "/app/@team/page.tsx"๊ฐ expoprt defaultํ ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์ง ๋ชปํด 404์๋ฌ๊ฐ ๋์ค๊ฒ ๋ฉ๋๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ "/app/@team/default.tsx"๋ฅผ ์ถ๊ฐํ์ฌ ์ด์ ๊ฐ์ ์ํฉ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
app ๋๋ ํ ๋ฆฌ ๋ด loading.tsx ํ์ผ์ด export defaultํ ์ปดํฌ๋ํธ๋ Next๊ฐ ์๋์ผ๋ก Page ์ปดํฌ๋ํธ๋ฅผ Suspense๋ก ๋ํํ๊ณ fallback prop์ loading.tsx๊ฐ export defaultํ ์ปดํฌ๋ํธ๋ฅผ ์ ๋ฌํ๊ฒ ๋ฉ๋๋ค.
Loading ์ปดํฌ๋ํธ๋ Page ์ปดํฌ๋ํธ ๋ด์ฉ์ ๋ก๋ํ๋ ๋์ Loading ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๊ณ , ์ดํ ๋ ๋๋ง์ด ์๋ฃ๋๋ฉด Page ์ปดํฌ๋ํธ๋ก ๊ต์ฒด์์ผ์ค๋๋ค.
<Layout>
<Suspense fallback={<Loading />}>
<Page />
</Suspense>
</Layout>์์ ๊ฐ์ด Page ์ปดํฌ๋ํธ๊ฐ ์๋์ผ๋ก Suspense ์ปดํฌ๋ํธ๋ก ๋ํ๋๋ฉฐ fallback prop์ผ๋ก Loading ์ปดํฌ๋ํธ๋ฅผ ์ ๋ฌํ๊ณ ์์ต๋๋ค. ์ฆ, Loading ์ปดํฌ๋ํธ๋ ํ์ด์ง ์ ์ฒด์ ๋ํ fallback ์ปดํฌ๋ํธ๋ก์ ๋์ํ๊ฒ ๋ฉ๋๋ค.
์ฃผ์ํ ์ ์ผ๋ก๋ Loading ์ปดํฌ๋ํธ๋ Hard Navigation์ ๊ฒฝ์ฐ์๋ง ํ์๋๋ฉฐ Soft Navigation์ ๊ฒฝ์ฐ์๋ ํ์๋์ง ์์ต๋๋ค. ์ฆ, loading.tsx๋ ์ ์ฒด ํ์ด์ง ๋ก๋ ์์๋ง ์๋์ผ๋ก ๋์ํ๋ฉฐ Soft Navigation์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ ์๋์ผ๋ก ๋ก๋ฉ ์ํ๋ฅผ ๊ด๋ฆฌํด์ผ ํฉ๋๋ค.
error.tsx ํ์ผ์ด export defaultํ ์ปดํฌ๋ํธ๋ Next๊ฐ ์๋์ผ๋ก Page ์ปดํฌ๋ํธ๋ฅผ ErrorBoundary ์ปดํฌ๋ํธ๋ก ๋ํํ๊ณ fallback prop์ error.tsx๊ฐ export defaultํ ์ปดํฌ๋ํธ๋ฅผ ์ ๋ฌํ๊ฒ ๋ฉ๋๋ค.
<Layout>
<ErrorBoudnary fallback={<Error />}>
<Page />
</ErrorBoudnary>
</Layout>Error ์ปดํฌ๋ํธ๋ ํ์์ ์ผ๋ก "use client";๋ฅผ ์์ฑํ์ฌ RCC๋ก์ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ์ด๋ ์๋ฒ์ ํด๋ผ์ด์ธํธ ์๋ฌ ๋ชจ๋๋ฅผ ์บ์นํ๊ธฐ ์ํด์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๋ก ์ฌ์ฉํด์ผ ํฉ๋๋ค. ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๋ ์๋ฒ์ ํด๋ผ์ด์ธํธ ์์ชฝ์์ ์คํ๋์ง๋ง ์๋ฒ ์ปดํฌ๋ํธ๋ ์๋ฒ์์๋ง ์คํ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
Error ์ปดํฌ๋ํธ๋ props๋ก error์ reset์ ์ ๋ฌ๋ฐ์ต๋๋ค. error๋ก ๋ฐ์ํ ์๋ฌ ๊ฐ์ฒด๋ฅผ ์ ๋ฌ๋ฐ์ผ๋ฉฐ, reset ํจ์๋ฅผ ์คํํ๋ฉด Next๋ ๋ฆฌ๋ ๋๋ง์ ์๋ํ๋ฉฐ, ๋ฆฌ๋ ๋๋ง ์ฑ๊ณต์ ์ฑ๊ณตํ ๋ ๋๋ง ๊ฒฐ๊ณผ๋ก ๊ต์ฒด์์ผ์ค๋๋ค.
'use client' // Error ์ปดํฌ๋ํธ๋ ํ์์ ์ผ๋ก RCC๋ก ์ค์
interface ErrorProps {
error: Error
reset: () => void
}
export default function Error({ error, reset }: ErrorProps) {
return (
<section>
An Error occurred: {error.message}<br />
<button onClick={() => reset()}>Retry</button>
</section>
)
}error.tsx๋ layout.tsx๋ template.tsx์์ ๋ฐ์ํ ์๋ฌ๋ ์บ์นํ์ง ์์ต๋๋ค. ๋ง์ฝ ํด๋น ์๋ฌ๋ฅผ ์บ์นํ๋ ค๋ฉด global-error.tsx๋ฅผ ์ถ๊ฐํด์ฃผ์ด์ผ ํฉ๋๋ค. ์ ๋ฌ๋ฐ๋ props๋ error.tsx์ Error ์ปดํฌ๋ํธ์ ๋์ผํฉ๋๋ค.
not-found.tsx ํ์ผ์ ๋งค์นญ๋๋ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๊ฐ ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ์ ํ์ํ ์ปดํฌ๋ํธ ์ ๋๋ค.
์ถ๊ฐ์ ์ผ๋ก "next/navigation"์ notFound ํจ์๋ฅผ ํธ์ถํ๋ฉด not-found.tsx ํ์ผ์์ export default๋ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋ฉ๋๋ค.
Route Handlers๋ ์์ฒญ ํ๋ฉด ์๋ต์ผ๋ก ํ์ด์ง๊ฐ ์๋ JSON์ ์๋ต์ผ๋ก ์ ๋ฌํด์ฃผ๋ API ์ญํ ์ ํฉ๋๋ค. ์ฆ, Route Handler๋ ์๋ฒ์์ ์คํ๋๋ ํจ์๋ก ์ฟ ํค๋ ํค๋์ ์ ๊ทผํ ์ ์์ต๋๋ค.
Route Handlers๋ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์์ฒญ ๊ฐ์ฒด(NextRequest)๋ฅผ ์ ๋ฌ๋ฐ๊ณ , ๋ ๋ฒ์งธ ์ธ์๋ก๋ ๋์ ๋ผ์ฐํ ์ ๊ฒฝ์ฐ params๋ผ๋ ํ๋กํผํฐ๋ฅผ ๊ฐ๋ ๊ฐ์ฒด๋ฅผ ์ ๋ฌ๋ฐ์ผ๋ฉฐ params ํ๋ก๋ฌํฐ๋ ๋์ ๊ฒฝ๋ก์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ์ฒด๋ก์ ์ ๋ฌ๋ฐ์ต๋๋ค.
Route Handlers๋ "app/api" ๋๋ ํ ๋ฆฌ ๋ด ์ถ๊ฐํ ์ ์์ผ๋ฉฐ, ์ดํ ์ค์ฒฉ๋ ๋๋ ํ ๋ฆฌ๋ช ์ด path๊ฐ ์ผ๋ถ๋ก ์ฌ์ฉ๋ฉ๋๋ค. ๋ํ ํ์ผ ๋ค์ด๋ฐ์ "route.ts"๋ก ์ถ๊ฐํด์ฃผ์ด์ผ ํฉ๋๋ค.
์๋ฅผ ๋ค์ด, "app/api/items/[id]/route.ts"๋ผ๋ ํ์ผ์ ์ถ๊ฐํ๋ฉด ์์ฒญํ ๋๋ "/api/items/1"๋ก ์์ฒญํ ์ ์๊ฒ ๋ฉ๋๋ค.
Route Handlers๊ฐ ์ง์ํ๋ HTTP Method๋ก๋ GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS์ ์ง์ํ๋ฉฐ ์ง์ํฉ๋๋ค. route.ts ํ์ผ์ ์ง์ํ๋ ๋ฉ์๋๋ช ์ ๊ฐ๋ ํจ์๋ฅผ exportํด์ผ ํ๋ฉฐ async ํจ์๋ก ์ ์ํ์ฌ API๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
// app/api/route.ts
import { NextRequest, NextResponse } from 'next/server'
type Params = Promise<{ slug: string }>
export async function GET(request: NextRequest, context: { params?: Params }) {
try {
const requestBody = await request.json() // ์์ฒญ body ๊ฐ
const responseBody = { success: true }
return NextResponse.json(responseBody, { status: 200 })
} catch (error) {
return NextResponse.json('Fail to fetch data', { status: 500 })
}
}๋ฏธ๋ค์จ์ด๋ ์ค์ ์์ฒญ์ ๊ฑฐ์น๊ธฐ ์ ์ ํน์ ์ญํ ์ ์ํํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค. ๋ฏธ๋ค์จ์ด๋ฅผ ์ถ๊ฐํ๊ธฐ ์ํด์๋ ํ๋ก์ ํธ ๋ฃจํธ ๊ฒฝ๋ก์ "middleware.ts"๋ผ๋ ๋ค์ด๋ฐ์ผ๋ก ํ์ผ์ ์ถ๊ฐํด์ฃผ์ด์ผ ํฉ๋๋ค. ์ฃผ์ํ ์ ์ผ๋ก ํ์ด์ง๋ API๋ฟ๋ง ์๋๋ผ ๋ชจ๋ ๋ฆฌ์์ค์ ๋ํ ์์ฒญ๋ค๋ ๋ฏธ๋ค์จ์ด๋ฅผ ๊ฑฐ์น๊ฒ ๋ฉ๋๋ค.
middleware ํจ์๋ async ํจ์๋ก ์ ์ํ ์ ์์ผ๋ฉฐ, ์ธ์๋ก ์์ฒญ ๊ฐ์ฒด(NextRequest)๋ฅผ ์ ๋ฌ๋ฐ์ต๋๋ค.
import { NextRequest, NextResponse } from 'next/server'
export async function middleware(request: NextReqeust) {
return NextResponse.redirect(new URL('/home', request.url))
}
export const config = {
matcher: ['/about/:path', '/dashboard/:path']
}๋ฏธ๋ค์จ์ด ์ค์ ์ ๊ฒฝ์ฐ์๋ config๋ผ๋ ๊ฐ์ฒด๋ฅผ exportํ์ฌ ์ค์ ํ ์ ์์ผ๋ฉฐ, config.matcher๋ฅผ ํตํด ๋ฏธ๋ค์จ์ด๋ฅผ ์คํํ ํน์ ๊ฒฝ๋ก๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค. ์ ์์ ์ ๊ฒฝ์ฐ์๋ "/about" ์ดํ ๋ชจ๋ ๊ฒฝ๋ก์ "/dashboard" ์ดํ ๋ชจ๋ ๊ฒฝ๋ก์ ๋ํด์ ๋ฏธ๋ค์จ์ด๊ฐ ์คํ๋ฉ๋๋ค. matcher๋ ์ ๊ทํํ์์ผ๋ก๋ ์์ฑ ๊ฐ๋ฅํฉ๋๋ค.
์ฃผ์ํ ์ ์ผ๋ก๋ config ๊ฐ์ฒด ์์ฒด๋ ๋น๋ํ์์ ์คํ๋์ด ์ ์ฉ๋๊ธฐ ๋๋ฌธ์ ๋์ ์ธ ๊ฐ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
"next/server"๊ฐ ์ ๊ณตํ๋ NextRequest์ NextResponse๋ Web Request/Response API๋ฅผ ํ์ฅํ ๊ฒ์ ๋๋ค.
import { NextRequest, NextResponse } from 'next/server'
const request = new NextRequest()
const response = NextResponse.next()
// ์์ฒญ/์๋ต cookie ๊ฐ์ set ์์ผ์ค๋๋ค.
request.cookies.set('key', 'value')
response.cookies.set('key', 'value')
// ๋งค์นญ๋ cookie ๊ฐ์ ๋ฐํํฉ๋๋ค.
// ๋งค์นญ๋ cookie๊ฐ ์๋ ๊ฒฝ์ฐ undefined๋ฅผ ๋ฐํํ๊ณ , ์ฌ๋ฌ ๊ฐ๊ฐ ๋งค์นญ๋ ๊ฒฝ์ฐ ์ฒซ ๋ฒ์งธ๋ก ๋งค์นญ๋ cookie๋ฅผ ๋ฐํํฉ๋๋ค.
request.cookies.get('key')
response.cookies.get('key')
// ๋งค์นญ๋ ๋ชจ๋ cookie ๊ฐ์ ๋ฐฐ์ด์ ๋ด์ ๋ฐํํฉ๋๋ค.
request.cookies.getAll('key')
response.cookies.getAll('key')
// ๋งค์นญ๋ cookie ๊ฐ์ ์ ๊ฑฐํฉ๋๋ค.
// ๋ฐํ๊ฐ์ ์ ๊ฑฐ ์ฑ๊ณต ์ฌ๋ถ๋ฅผ ๋ถ๋ฆฌ์ธ ๊ฐ์ผ๋ก ๋ฐํํฉ๋๋ค.
request.cookies.delete('key')
response.cookies.delete('key')
// ๋งค์นญ๋ cookie ๊ฐ ์กด์ฌ ์ฌ๋ถ๋ฅผ ๋ถ๋ฆฌ์ธ ๊ฐ์ผ๋ก ๋ฐํํฉ๋๋ค.
request.cookies.has('key')
// ์์ฒญ์ Set-Cookie ํค๋๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
request.cookies.clear()
// URL ๋๋ฉ์ธ์ ๋ฐํํฉ๋๋ค.
request.nextUrl.baseUrl
// URL path๊ฐ์ ๋ฐํํฉ๋๋ค.
request.nextUrl.pathname
// URL ์ฟผ๋ฆฌ ์คํธ๋ง ๊ฐ์ ๊ฐ์ฒด๋ก ๋ฐํํฉ๋๋ค.
request.nextUrl.searchParams
// JSON์ body๋ก ๊ฐ๋ ์๋ต์ ์์ฑํฉ๋๋ค.
NextResponse.json({ success: true }, { status: 200 })
// ํน์ URL๋ก redirect์ํค๋ ์๋ต์ ์์ฑํฉ๋๋ค.
// ํด๋ผ์ด์ธํธ์ธก์์ ํด๋น ์๋ต์ ์ ๋ฌ๋ฐ๊ฒ ๋๋ฉด "/home" ๊ฒฝ๋ก๋ก ์ด๋ํ๊ฒ ๋ฉ๋๋ค.
NextResponse.redirect(new URL('/home', reqeust.url))
// rewrite ๋ฉ์๋๋ route handler์์ ์ฌ์ฉํ ์ ์๊ณ , next middleware์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
// redirect์๋ ๋ค๋ฅด๊ฒ ์์ฒญํ URL path๊ฐ์ ๋ณ๊ฒฝํ์ง ์๊ณ , ๋ค๋ฅธ ํ์ด์ง๋ route handler๋ก ์์ฒญ์ ์ ๋ฌํ ์ ์์ต๋๋ค.
NextResponse.rewrite(new URL('/proxy', reqeust.url))
// next ๋ฉ์๋๋ route handler์์ ์ฌ์ฉํ ์ ์๊ณ , next middleware์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
// next ๋ฉ์๋๋ ์์ฒญ์ ์ค๋จํ์ง ์๊ณ ๋ค์ ๋จ๊ณ๋ก ๋๊ธธ ์ ์์ต๋๋ค. ์ฆ, ๋ฏธ๋ค์จ์ด ์ดํ ์ค์ ์์ฒญ์ ์ด์ด์ ์งํํ๋๋ก ๋์์ค๋๋ค.
NextResponse.next()layout.tsx, page.tsx, Route Handlers์๋ Route Segment ์ต์ ์ ์ค์ ํ ์ ์์ต๋๋ค.
Route Segment ์ต์ ์ ํตํด ๋ฐ์ดํฐ Next ์๋ฒ์ ์บ์ฑ๋์ด ์๋ Data Cache์ Full Route Cache๋ฅผ ๋ค๋ฃฐ ์ ์์ต๋๋ค.
-
"auto"(default): Next๊ฐ ํ์ด์ง์์ ์ฌ์ฉ๋๋ ํ ์ดํฐ ์์ค์ fetch ์์ฒญ์ ๋ถ์ํ์ฌ ์๋์ผ๋ก ํ์ด์ง ์์ฑ ๋ฐฉ์์ ์ ์ ํน์ ๋์ ์ผ๋ก ๊ฒฐ์ ํ๊ฒ ๋ฉ๋๋ค.
-
"force-dynamic": Next๊ฐ ํ์ด์ง ์์ฑ ๋ฐฉ์์ ๋์ ์์ฑ์ผ๋ก ๊ฐ์ ํ๋ฉฐ(Full Route Cache), fetch ์์ฒญ์ ๋ํ ๋ฐ์ดํฐ๋ฅผ ์บ์ฑํ์ง ์๊ณ ์ธ์ ๋ ์ต์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค(Data Cache). ์ฆ, ํ์ด์ง ์์ฒญํ ๋๋ง๋ค fetch๋ก ๊ฐ์ ธ์จ ์ต์ ๋ฐ์ดํฐ ๊ธฐ๋ฐ์ผ๋ก ์์ฑํ ํ์ด์ง๋ฅผ ํด๋ผ์ด์ธํธ์ธก์ ์ ๋ฌํฉ๋๋ค.
-
"force-static": Next๊ฐ ํ์ด์ง ์์ฑ ๋ฐฉ์์ ์ ์ ์์ฑ์ผ๋ก ๊ฐ์ ํ๋ฉฐ(Full Route Cache), fetch ์์ฒญ์ ๋ํ ๋ฐ์ดํฐ๋ ์ธ์ ๋ ์บ์ฑํ์ฌ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค(Data Cache). ์ฆ, fetch์ ๋ํ ์๋ต๊ฐ์ Next ์๋ฒ์ ์บ์ฑ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฌ์ฉํ์ฌ ๋น๋ ํ์๋ ์์ฑํ ํ์ด์ง๋ฅผ ํด๋ผ์ด์ธํธ์ธก์ ์ ๋ฌํฉ๋๋ค.
export const dynamic = "force-dynamic"
export const dynamic = "force-static"
// ,,,-
true(default): ๋ค์ด๋๋ฏน ๋ผ์ฐํ ์ฒ๋ฆฌ๊ฐ ๋ฐํ์์ ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญ๋ ๊ฒฝ๋ก๋ก ๋์ ์ผ๋ก ํด์๋์ด ์ฒ๋ฆฌ๋ฉ๋๋ค.
-
false: ๋์ ๊ฒฝ๋ก๊ฐ ๋ฐํ์์ด ์๋ ๋น๋ ํ์๋ ๊ฒฐ์ ๋๊ธฐ ๋๋ฌธ์ generateStaticParams๋ผ๋ ํจ์๋ฅผ exportํ์ฌ ๋น๋ ํ์์ ์์ฑ๋ ๋์ ๊ฒฝ๋ก ์ ๋ณด๋ฅผ ์์ฑํด์ฃผ์ด์ผ ํฉ๋๋ค.
export const dynamicParams = false
type Params = Promise<{ slug: string }>
interface PageProps {
params?: Params
}
export default function Page({ params }: PageProps) {
// ,,,
}
export async function generateStaticParams() {
const paths = [{ params: { slug: 'post-1' } }, { params: { slug: 'post-2' } }]
return { paths, fallback: false }
}Data Cache์ Full Route Cache์ ์บ์ฑ ์ง์์๊ฐ์ ์ค์ ํ ์ ์์ต๋๋ค.
-
false(default): fetch์ ์๋ต ๋ฐ์ดํฐ๋ Next ์๋ฒ์ Data Cache์ ์บ์ฑ๋ ๋ฐ์ดํธ๋ฅผ ์ฌ์ฌ์ฉํ๊ณ , ํ์ด์ง๋ ๋น๋ ํ์๋ ์์ฑํ ํ์ด์ง๋ฅผ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
-
number: Next ์๋ฒ์ Data Cache์ ์บ์ฑ๋ fetch ์๋ต๊ฐ๊ณผ Full Route Cache์ ์ง์์๊ฐ์ ์ด ๋จ์๋ก ์ค์ ํ ์ ์์ต๋๋ค.
export const revalidate = 3600 // 60 seconds
// ,,,fetch ์์ฒญ์ ๋ํ ์บ์ฑ ์์ฒญ์ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ ๋ณ๋ก ์ ์ดํ ์ ์์ต๋๋ค. ๋ค์ ๋งํด, fetch์ Data Cache ์ค์ ์ ๋ผ์ฐํธ๋ณ๋ก ์ง์ ํ๋ ์ต์ ์ ๋๋ค.
-
"auto"(default): cache ์ต์ ์ ๋ช ์ํ์ง ์์ fetch์ ๋ํด cache ์ต์ ์ "no-store"์ผ๋ก ์ค์ ํฉ๋๋ค.
-
"default-cache": fetch์ cache ์ต์ ์ด ๋ช ์๋์ด ์์ง ์์ fetch์ ๋ํด cache ์ต์ ์ "force-cache"๋ก ์ค์ ํฉ๋๋ค.
-
"force-cache": ๋ชจ๋ fetch์ Data Cache ์ฌ์ฉ์ ๊ฐ์ ํ๋ฉฐ fetch์ cache ์ต์ ์ "force-cache" ์ค์ ์ผ๋ก ๊ฐ์ ํฉ๋๋ค.
-
"only-cache": ๋ชจ๋ fetch์ Data Cache ์ฌ์ฉ์ ๊ฐ์ ํ๋ฉฐ fetch์ cache ์ต์ ์ "force-cache" ์ค์ ์ผ๋ก ๊ฐ์ ํฉ๋๋ค. ์ถ๊ฐ์ ์ผ๋ก "no-store"๋ฅผ ์ฌ์ฉํ๋ fetch์ ๋ํด์๋ ์๋ฌ๋ฅผ ๋ฐ์์ํต๋๋ค,
-
"default-no-store": fetch์ cache ์ต์ ์ด ๋ช ์๋์ด ์์ง ์์ fetch์ ๋ํด cache ์ต์ ์ "no-store"๋ก ์ค์ ํฉ๋๋ค.
-
"force-no-store": ๋ชจ๋ fetch์ Data Cache๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉฐ fetch์ cache ์ต์ ์ "no-store" ์ค์ ์ผ๋ก ๊ฐ์ ํฉ๋๋ค.
-
"only-no-store": ๋ชจ๋ fetch์ Data Cache๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉฐ fetch์ cache ์ต์ ์ "no-store" ์ค์ ์ผ๋ก ๊ฐ์ ํฉ๋๋ค. ์ถ๊ฐ์ ์ผ๋ก "force-cache"๋ฅผ ์ฌ์ฉํ๋ fetch์ ๋ํด์๋ ์๋ฌ๋ฅผ ๋ฐ์์ํต๋๋ค.
"next/link"๊ฐ ์ ๊ณตํ๋ Link ์ปดํฌ๋ํธ๋ html a ํ๊ทธ๋ฅผ ํ์ฅํ ์ปดํฌ๋ํธ๋ก์ prefetching ๊ธฐ๋ฅ๊น์ง ์ ๊ณตํฉ๋๋ค. Link ์ปดํฌ๋ํธ๋ RSC์ RCC ๋ ๋ค ์ฌ์ฉํ ์ ์์ต๋๋ค.
Link ์ปดํฌ๋ํธํธ๋ a ํ๊ทธ๋ฅผ ํ์ฅํ ์ปดํฌ๋ํธ๋ก a ํ๊ทธ์ ์์ฑ ๊ฐ๋ฅํ ์ดํธ๋ฆฌ๋ทฐํธ๋ค์ ๊ทธ๋๋ก ์์ฑํ ์ ์์ต๋๋ค.
import Link from 'next/link'
export default function Page() {
return (
<>
<Link href='/item' />
</>
)
}"next/navigation"๊ฐ ์ ๊ณตํ๋ userRouter ํ ์ด ๋ฐํํ๋ ๊ฐ์ฒด๋ฅผ ํตํด์ Soft Navigating์ ์ฌ์ฉํ ์ ์์ต๋๋ค. useRouter๋ ๋ฆฌ์กํธ ํ ์ผ๋ก RCC์์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค.
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
// History stack์ ํ๋์ ์คํ์ push ํ๊ณ ์ด๋ํฉ๋๋ค.
router.push('/items')
// ํ์ฌ URL์ ๋ํด refresh๋ฅผ ์ํํฉ๋๋ค.
router.refresh()
// ํน์ URL์ prefetchingํ์ฌ ๋ ๋น ๋ฅธ Navigating์ ์ ๊ณตํฉ๋๋ค.
router.prefetch('/item')
// History stack์์ ํ๋์ ์คํ์ pop ํ๊ณ ์ด๋ํฉ๋๋ค.
router.back()
// History stack์์ ํ๋์ ์คํ ์์ผ๋ก ์ด๋ํฉ๋๋ค.
router.forward()
// ,,,
}"next/navigation"์ด ์ ๊ณตํ๋ permanentRedirect ํจ์๋ RCC, RSC, Route Handler, Server Action ๋ชจ๋ ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
import { permanentRedirect } from 'next/navigation'
export default function Page() {
permanentRedirect('/login', { type: 'replace' })
// ,,,
}permanentRedirect ํจ์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก๋ URL์ ์ ๋ฌํ๊ณ ๋ ๋ฒ์งธ ์ธ์๋ก๋ ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ ์ ์์ผ๋ฉฐ type ํ๋กํผํฐ์ "replace"(default) ํน์ "push"๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค.
์ฃผ์ํ ์ ์ผ๋ก Server Actions์์ ์ฌ์ฉํ ๊ฒฝ์ฐ์๋ type์ default๊ฐ push๋ก ๋์ํ๊ฒ ๋ฉ๋๋ค.
"next/navigation"์ด ์ ๊ณตํ๋ redirect ํจ์๋ RSC, Route Handler, Server Action์์๋ง ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
import { redirect } from 'next/navigation'
export default function Page() {
redirect('/login', { type: 'replace' })
// ,,,
}redirect ํจ์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก๋ URL์ ์ ๋ฌํ๊ณ ๋ ๋ฒ์งธ ์ธ์๋ก๋ ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ ์ ์์ผ๋ฉฐ type ํ๋กํผํฐ์ "replace"(default) ํน์ "push"๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค.
"next/navigation"์ด ์ ๊ณตํ๋ notFound ํจ์ ํธ์ถ ์ not-found.tsx ํ์ผ์์ export default๋ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋ฉ๋๋ค.
"next/headers"๊ฐ ์ ๊ณตํ๋ cookies ํจ์๋ RSC, Route Handler, Server Action์์ ์ฌ์ฉ ๊ฐ๋ฅํ ํจ์๋ก ์์ฒญ ๊ฐ์ฒด์ ์ฟ ํค ๊ฐ์ ์ฝ์ ์ ์์ต๋๋ค.
import { cookies } from 'next/headers'
export default async function Page() {
// ์์ฒญ ๊ฐ์ฒด์ ๋ํ ์ฟ ํค
const cookieStore = await cookies()
// ์ธ์๋ก ์ ๋ฌํ ์ฟ ํค ์ด๋ฆ๊ณผ ๋งค์นญ๋ ์ฟ ํค๊ฐ์ ๋ฐํํฉ๋๋ค. ๋งค์นญ๋ ์ฟ ํค๊ฐ ์๋ ๊ฒฝ์ฐ undeinfed๋ฅผ ๋ฐํํฉ๋๋ค.
// ๋งค์นญ๋ ๊ฒฐ๊ณผ๊ฐ ์ฌ๋ฌ ๊ฐ์ธ ๊ฒฝ์ฐ์๋ ํ๋๋ง ๋ฐํํฉ๋๋ค.
cookieStore.get('key')
// ์ธ์๋ก ์ ๋ฌํ ์ฟ ํค ์ด๋ฆ๊ณผ ๋งค์นญ๋ ์ฟ ํค๊ฐ๋ค์ ๋ฐํํฉ๋๋ค.
// get ๋ฉ์๋์๋ ๋ค๋ฅด๊ฒ ๋งค์นญ๋ ๋ชจ๋ ์ฟ ํค๊ฐ๋ค์ ์์๋ก ๊ฐ๋ ๋ฐฐ์ด๋ก ๋ฐํํฉ๋๋ค.
cookieStore.getAll('key')
// ์ธ์๋ก ์ ๋ฌํ ์ฟ ํค ์ด๋ฆ๊ณผ ๋งค์นญ๋ ์ฟ ํค๊ฐ ์กด์ฌ ์ฌ๋ถ๋ฅผ ๋ถ๋ฆฌ์ธ ๊ฐ์ผ๋ก ๋ฐํํฉ๋๋ค.
cookieStore.has('key')
// ,,,
}import { cookies } from 'next/headers'
export async function GET() {
// ์์ฒญ ๊ฐ์ฒด์ ๋ํ ์ฟ ํค
const cookieStore = await cookies()
// ์์ฒญ ๊ฐ์ฒด์ ์ฟ ํค๊ฐ์ setํ ์ ์์ต๋๋ค.
// ์ฃผ์ํ ์ ์ผ๋ก set ๋ฉ์๋๋ Server Action์ Route Handler์์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค.
cookieStore.set('key', 'value')
cookieStore.set({ name: 'key', value: 'value' })
// ์์ฒญ ๊ฐ์ฒด์ ์ฟ ํค๊ฐ์ ์ ๊ฑฐํ ์ ์์ต๋๋ค.
// ์ฃผ์ํ ์ ์ผ๋ก delete ๋ฉ์๋๋ Server Action์ Route Handler์์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค.
cookieStore.delete('key')
// ,,,
}"next/headers"๊ฐ ์ ๊ณตํ๋ headers ํจ์๋ RSC, Route Handler, Server Action์์ ์ฌ์ฉ ๊ฐ๋ฅํ ํจ์๋ก ์์ฒญ ํค๋ ๊ฐ์ ์ฝ์ ์ ์์ต๋๋ค.
headers ํจ์๊ฐ ๋ฐํํ๋ ํค๋ ๊ฐ์ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก set, delete์ ๊ฐ์ ๋์์ ํ ์ ์์ต๋๋ค.
import { headers } from 'next/headers'
export default async function Page() {
// ์์ฒญ ๊ฐ์ฒด์ ๋ํ ํค๋
const headersList = await headers()
// headers ๊ฐ์ key, value๋ก ๊ฐ๋ ์ดํฐ๋ ์ดํฐ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
headersList.entries()
// ์ธ์๋ก ์ ๋ฌํ ์ฝ๋ฐฑ์ key, value๋ก ๊ฐ๋ ๊ฐ์ฒด๋ฅผ ์ธ์๋ก ์ ๋ฌ๋ฐ์ ์คํ๋ฉ๋๋ค.
headersList.forEach()
// ์ธ์๋ก ์ ๋ฌํ key์ ๋งค์นญ๋ value๋ฅผ ๋ฐํํฉ๋๋ค.
headersList.get();
// ์ธ์๋ก ์ ๋ฌํ key์ ๋งค์นญ๋ value ์ฌ๋ถ๋ฅผ ๋ถ๋ฆฌ์ธ ๊ฐ์ผ๋ก ๋ฐํํฉ๋๋ค.
headersList.has();
// key ๊ฐ์ ๊ฐ๋ ์ดํฐ๋ ์ดํฐ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
headersList.keys();
// value ๊ฐ์ ๊ฐ๋ ์ดํฐ๋ ์ดํฐ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
headersList.values();
}Next.js๋ Web fetch API๋ฅผ ํ์ฅํ์ฌ ์ ๊ณตํฉ๋๋ค. fetch ์์ฒด๋ Web API๋ก ์๋ฒ ์ฌ์ด๋์์ ์ฌ์ฉํ ์ ์์ง๋ง Next์์๋ RSC, Server Actions, Route Handlers ๋ฑ ๋ชจ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
export default async function Page() {
// no-store: ๊ธฐ๋ณธ ์บ์ฑ ๋์์ด๋ฉฐ ํญ์ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค (Data Cache ๋นํ์ฑ)
await fetch('https://,,,', { cache: 'no-store' })
// force-cache: ์ด์ ์ ์์ฒญํ์ฌ ๋ฐ์ ์๋ต ๋ฐ์ดํฐ๊ฐ Next ์๋ฒ์ ์บ์ฑ๋์ด ์๋ค๋ฉด ์ด๋ฅผ ์ฌ์ฌ์ฉํฉ๋๋ค (Data Cache ํ์ฑ)
// ์ด๋ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ํ์ง ์์ ์ ์์ต๋๋ค.
await fetch('https://,,,', { cache: 'force-cache' })
// revalidate ์ต์
์ ํตํด cache life time์ ๋ช
์ํ ์ ์์ต๋๋ค.
// ์ด ๋จ์ ์ซ์๊ฐ์ ์์ฑํ์ฌ cache life time์ ์ง์ ํ ์ ์์ต๋๋ค. ์ฆ, revalidate ๊ฐ์ 0์ no-store์ด๋ฉฐ ์์๊ฐ์ ์์ฑํ ๊ฒฝ์ฐ force-cache๋ฅผ ์์ํฉ๋๋ค.
await fetch('https://,,,', { next: { revalidate: 10 } })
// ,,,
}์ฆ, Next๋ fetch ์์ฒญ์ cache ์ต์ ์ ๋ช ์ํ์ง ์์ ๊ฒฝ์ฐ ๊ธฐ๋ณธ์ ์ผ๋ก "no-store"๊ฐ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ ์ฉ๋์ด Data Cache๋ฅผ ์ฌ์ฉํ์ง ์์ต๋๋ค.
fetch ์์ฒญ์ Data Cache ํ์ฑ์ ์ค์ ํ๊ธฐ ์ํ ๋ฐฉ๋ฒ์ผ๋ก๋ ์๋์ ๊ฐ์ต๋๋ค.
-
fetch์ cache ์ต์ ์ "force-cache"๋ก ์ค์
-
Route Segment Config์ dynamic์ "force-static"์ผ๋ก ์ค์
-
Route Segment Config์ fetchCache๋ฅผ "force-cache", "only-cache" ํน์ "default-cache"๋ก ์ค์
๋ง์ฝ dynamic ์ต์ ์ "force-dynamic"์ผ๋ก ์ค์ ํ๊ณ fetchCache๋ฅผ "force-cache", "only-cache" ํน์ "default-cache"๋ก ์ค์ ํ๋ฉด ํ์ด์ง๋ ๋งค๋ฒ ๋์ ์ผ๋ก ์์ฑ๋์ง๋ง fetch ์์ฒญ์ Next ์๋ฒ์ ์บ์ฑ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ ํ์ด์ง๋ฅผ ๋ง๋ค๊ฒ ๋๊ณ , dynamic ์ต์ ์ "force-static" fetchCache์ต์ ์ "force-no-store", "only-no-store" ํน์ "default-no-store"๋ก ์ค์ ํ๋ฉด ํ์ด์ง๋ ์ ์ ์ผ๋ก ์์ฑ๋์ง๋ง fetch ์์ฒญ ๋ฐ์ดํฐ๋ ์ต์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
"next/cache"๊ฐ ์ ๊ณตํ๋ revalidatePath ํจ์๋ ํน์ ๋ผ์ฐํธ์ ๋ํ Data Cache์ Full Route Cache ๋ชจ๋ ๋ฌดํจํํ๊ณ ์ฌ๊ฒ์ฆํฉ๋๋ค.
revalidatePath๋ฅผ ํธ์ถํ๋ฉด ํด๋น ๊ฒฝ๋ก์์ Next ์๋ฒ์ ์บ์ฑ๋ fetch ์๋ต ๋ฐ์ดํฐ์ ๋น๋ ํ์๋ ์์ฑ๋์ด ์บ์ฑ๋ ํ์ด์ง๋ฅผ ๋ฌดํจํ์ํต๋๋ค. ์ดํ ๋ค์ ๋ฒ์ ์ฌ์ฉ์๊ฐ ํด๋น ๊ฒฝ๋ก๋ฅผ ์์ฒญํ ๊ฒฝ์ฐ Next๊ฐ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ ํ์ด์ง๋ฅผ ๋ค์ ์์ฑํ์ฌ ์๋ต์ผ๋ก ์ ๋ฌํด์ค๋๋ค. ์ด ๊ณผ์ ์์ ์๋กญ๊ฒ ์์ฑ๋ ์ ์ ํ์ผ๊ณผ ๋ฐ์ดํฐ๊ฐ ์บ์์ ์ ์ฅ๋ฉ๋๋ค.
revalidatePath ํจ์๋ Route Handlers๋ Server Actions์์ ํธ์ถ ๊ฐ๋ฅํฉ๋๋ค.
import { NextRequest, NextResponse } from 'next/server'
import { revalidatePath } from 'next/cache'
export async function POST(request: NextRequest) {
const { path } = request.nextUrl.searchParams('path')
if (path) {
revalidatePath(path, 'page')
return NextResponse.json({ revalidated: true, now: Date.now() })
} else {
return NextResponse.json({ revalidated: false, now: Date.now() })
}
}revalidatePath ํจ์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก๋ ๋ฌดํจํํ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๋ฅผ ์์ฑํ๊ณ , ๋ ๋ฒ์งธ ์ธ์๋ก๋ "layout" ํน์ "page"๋ฅผ ์ ๋ฌํฉ๋๋ค.
-
"layout": "layout" ์ ๋ฌ ์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํ ๋ผ์ฐํธ๋ฟ๋ง ์๋๋ผ ํ์ ๋ผ์ฐํธ๊น์ง ๋ชจ๋ ์ฌ๊ฒ์ฆํ๊ฒ ๋ฉ๋๋ค.
-
"page"(default): "page" ์ ๋ฌ ์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํ ๋ผ์ฐํธ๋ง ์ฌ๊ฒ์ฆํ๊ฒ ๋ฉ๋๋ค,
์ฆ, "layout"์ด "page"๋ณด๋ค ์ฌ๊ฒ์ฆํ๋ ๋ฒ์๊ฐ ๋ ํฝ๋๋ค.
"next/navigation"์ด ์ ๊ณตํ๋ useParams ํ ์ ๋์ ๋ผ์ฐํ ํ๋ ๊ฒฝ์ฐ ๋์ ์ผ๋ก ๊ฒฐ์ ๋ path๊ฐ์ ๊ฐ์ฒด ํํ๋ก ๋ฐํํฉ๋๋ค.
'use client'
import { useParams } from 'next/navigation'
export default function ClientComponent() {
const params = useParams()
// ,,,
}"next/navigation"์ด ์ ๊ณตํ๋ usePathname ํ ์ ํ์ฌ URL์ path ๊ฐ์ string์ผ๋ก ๋ฐํํฉ๋๋ค.
'use client'
import { usePathname } from 'next/navigation'
export default function ClientComponent() {
const pathanem = usePathname()
// ,,,
}"next/navigation"์ด ์ ๊ณตํ๋ useSearchParams ํ ์ ํ์ฌ URL์ ์ฟผ๋ฆฌ์คํธ๋ง ์ ๋ณด๋ฅผ ์ฝ๊ธฐ ์ ์ฉ์ธ URLSearchParams ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
'use client'
import { useSearchParams } from 'next/navigation'
export default function ClientComponent() {
const searchParams = useSearchParams()
// ์ฟผ๋ฆฌ์คํธ๋ง์ value ๊ฐ์ ์์๋ก ๊ฐ๋ ๋ฐฐ์ด์ ๋ฐํํฉ๋๋ค.
searchParams.getAll()
// ์ฟผ๋ฆฌ์คํธ๋ง์ key ๊ฐ์ ์์๋ก ๊ฐ๋ ์ดํฐ๋ ์ดํฐ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
searchParams.keys()
// ์ฟผ๋ฆฌ์คํธ๋ง์ value ๊ฐ์ ์์๋ก ๊ฐ๋ ์ดํฐ๋ ์ดํฐ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
searchParams.values()
// ์ฟผ๋ฆฌ์คํธ๋ง์ key, value ๊ฐ์ ์์๋ก ๊ฐ๋ ๋ฐฐ์ด์ ์์๋ก ๊ฐ๋ ์ดํฐ๋ ์ดํฐ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
searchParams.entries()
// ์ธ์๋ก ์ ๋ฌํ ์ฝ๋ฐฑ์ key, value ๊ฐ์ ์์ฐจ์ ์ผ๋ก ์ ๋ฌ๋ฐ์ผ๋ฉฐ ์คํ๋ฉ๋๋ค.
searchParams.forEach((key, value) => {
// ,,,
})
// ,,,
}"next/navigation"์ด ์ ๊ณตํ๋ useSelectedLayoutSegment ํ ์ ํ์ฌ ํ์ฑํ๋ ํ ๋จ๊ณ ํ์ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ ๋ฐํํฉ๋๋ค. useSelectedLayoutSegments ํ ์ ํ์ฌ ํ์ฑํ๋ ๋ชจ๋ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํฌ ๊ฐ์ ์์๋ก ๊ฐ๋ ๋ฐฐ์ด์ ๋ฐํํฉ๋๋ค.
์๋ฅผ ๋ค์ด, ํ์ฌ URL path ๊ฐ์ด "/blog/hello-world"์ธ ๊ฒฝ์ฐ useSelectedLayoutSegment ํ ์ "hello-world"๋ฅผ ๋ฐํํ๊ณ , useSelectedLayoutSegments ํ ์ ["blog", "hello-world"]๋ฅผ ๋ฐํํฉ๋๋ค.
Server Actions์ React์ ๋ด์ฅ๋ ๊ธฐ๋ฅ์ด๋ฉฐ form ์ ์ถ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. Server Actions์ ํจ์์ด๋ฉฐ ์ปดํฌ๋ํธ ๋ด ์ง์ ์ ์ํ๊ฑฐ๋ ๋ณ๋์ ํ์ผ๋ก ๋ถ๋ฆฌํ์ฌ importํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค. Server Actions ํจ์๋ฅผ form ํ๊ทธ์ action ์ดํธ๋ฆฌ๋ทฐํธ์ ์ ๋ฌํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Server Actions ํจ์๋ ์ธ์๋ก FormData ๊ฐ์ฒด๋ฅผ ์ ๋ฌ๋ฐ์ต๋๋ค. ์ฃผ์ํ ์ ์ผ๋ก formData ๊ฐ์ฒด๋ก ํผ ๋ฐ์ดํฐ์ ์ ๊ทผํ๊ธฐ ์ํด์๋ form ๋ด๋ถ ๊ฐ input๋ค์ name ์ดํธ๋ฆฌ๋ทฐํธ๋ฅผ ๊ฐ๊ณ ์์ด์ผ ํฉ๋๋ค. ํผ ๋ฐ์ดํฐ๋ค์ ์ ๊ทผํ ๋ input์ name ์ดํธ๋ฆฌ๋ทฐํธ ๊ฐ์ผ๋ก ์ ๊ทผํฉ๋๋ค.
์ถ๊ฐ์ ์ผ๋ก Server Action ํจ์ async ํจ์๋ก ์ ์๋์ด์ผ ํ๋ฉฐ, ํจ์ ์ฝ๋ ๋ธ๋ก ์ต์๋จ์ "use server" ์ ์ธ๋ฌธ์ ์์ฑํด์ฃผ์ด์ผ ํ๋ฉฐ ๋ง์ฝ ๋ถ๋ฆฌ๋ ํ์ผ๋ก ์ ์๋ ๊ฒฝ์ฐ ํ์ผ ์ต์๋จ์ "use server"๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
Server Actions ํจ์๋ Next ์๋ฒ์์ ์คํ๋๋ ํจ์์ด๋ฏ๋ก ํด๋ผ์ด์ธํธ์ธก ๋ก์ง์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ Server Action ํจ์ ์์ฒด๋ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ ๋ด์์๋ ์ ์ํ ์ ์์ต๋๋ค.
export default function Page() {
// Server Action
async function create(formData: FormData) {
'use server'
// ,,,
}
return <form action={create}>,,,</form>
}useFormStatus ํ ์ "react-dom"์ด ์ ๊ณตํ๋ ํด๋ผ์ด์ธํธ ํ ์ผ๋ก form ์ ์ถ์ ๋ํ ์ ๋ณด๋ฅผ ์ ๊ณตํฉ๋๋ค. useFormStatus ํ ์ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ๋ form ํ๊ทธ๋ฅผ ๊ฐ๋ ์ปดํฌ๋ํธ ์์์ผ๋ก ์์ฑ๋์ด์ผ ํฉ๋๋ค.
'use client'
import { useFormState } from 'react-dom'
export default function SubmitButton() {
const { pending } = useFormState()
return (
<button type='submit' disabled={pending}>
Add
</button>
)
}useActionState ํ ์ "react"๊ฐ ์ ๊ณตํ๋ ํด๋ผ์ด์ธํธ ํ ์ผ๋ก Server Actions๊ฐ ๋ฐํํ๋ ๊ฐ์ ์ ๊ทผํ ์ ์์ต๋๋ค. useFormStatus ํ ๊ณผ๋ ๋ค๋ฅด๊ฒ form ํ๊ทธ๋ฅผ ๊ฐ๋ ์ปดํฌ๋ํธ ๋ด ์์ฑํ ์ ์์ต๋๋ค.
useActionState ํ ์ฒซ ๋ฒ์งธ ์ธ์๋ก๋ Server Actions ํจ์๋ฅผ ์ ๋ฌํ๊ณ ๋ ๋ฒ์งธ ์ธ์๋ก๋ Server Actions๊ฐ ๋ฐํํ๋ ๊ฐ์ ์ด๊ธฐ๊ฐ์ ์ ๋ฌํด์ฃผ์ด์ผ ํฉ๋๋ค. ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํ๋ Server Actions์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ด์ Server Actions๊ฐ ๋ฐํํ ๊ฐ์ ์ ๋ฌ๋ฐ์ผ๋ฉฐ, ๋ ๋ฒ์งธ ์ธ์๋ก๋ FormData ๊ฐ์ฒด๋ฅผ ์ ๋ฌ๋ฐ์ต๋๋ค.
useActionState ํ ์ ๋ฐฐ์ด์ ๋ฐํํ๋ฉฐ ๋ฐฐ์ด์ ์ฒซ ๋ฒ์งธ ์์๋ Server Actions๊ฐ ๋ฐํํ๋ ๊ฐ, ๋ ๋ฒ์งธ ์์๋ React๊ฐ ์ ์ดํ๋ Server Actions๋ฅผ ๋ฐํํฉ๋๋ค. ์ด๋ ๋ ๋ฒ์งธ ์์๋ก ๋ฐํํ Server Action์ form ํ๊ทธ์ action ์ดํธ๋ฆฌ๋ทฐํธ๋ก ์ ๋ฌํ๋ฉด React๊ฐ Server Actions๊ฐ ๋ฐํํ๋ ๊ฐ์ ์ ๊ทผํ ์ ์๊ฒ ๋ฉ๋๋ค.
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
const initState = {
message: ''
}
export default function SignUp() {
const [state, formAction] = useActionState(createUser, initState);
return <form action={formAction}>,,,</form>
}Request Memoization์ ๋์ผํ ๋ผ์ฐํธ์์ ๋์ผํ ์ค์ ์ ๊ฐ๋ fetch Request๋ฅผ ์ค๋ณต ์์ฒญํ์ง ์๊ณ ๋จ์ผ ์์ฒญ์ผ๋ก ์ ๋ฌํ๊ฒ ๋ฉ๋๋ค. ์ฆ, ๋์ผํ ๋ผ์ฐํธ ๋ด ์ฌ๋ฌ ๋ค๋ฅธ ์๋ฒ ์ปดํฌ๋ํธ์์ ๋์ผํ ์ค์ ์ ๊ฐ๋ fetch๋ฅผ ํธ์ถํ๋๋ผ๋ ๋จ์ผ ์์ฒญ์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค. ์ด๋ ํ์ด์ง ์ฒซ ๋ ๋๋ง ์์ ์๋ง ์ด๋ฃจ์ด์ง๋๋ค.
์๋ฅผ ๋ค์ด, Layout๊ณผ Page ์ปดํฌ๋ํธ๊ฐ ์๋ฒ ์ปดํฌ๋ํธ๋ก ์์ฑ๋์๊ณ , ๋ ์ปดํฌ๋ํธ ๋ชจ๋ ๋์ผํ ์ค์ ์ ๊ฐ๋ fetch ํจ์๋ฅผ ํธ์ถํ๊ฒ ๋๋ฉด ์์ฒญ์ด ๋ ๋ฒ ์ ๋ฌ๋์ง ์๊ณ ๋จ์ผ ์์ฒญ์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค.
Request Memoization์ GET ๋ฉ์๋์ธ fetch์๋ง ์ ์ฉ๋๋ฉฐ ์๋ฒ ์ปดํฌ๋ํธ์์์ fetch์๋ง ํด๋น๋ฉ๋๋ค. ์ฆ, Route Handlers์์์ fetch๋ ํด๋น๋์ง ์์ต๋๋ค.
Data Cache๋ Next๊ฐ fetch ํจ์๋ฅผ ํตํด ์๋ฒ์์ ๊ฐ์ ธ์จ ์๋ต ๋ฐ์ดํฐ๋ฅผ Next ์๋ฒ์ธก์ ์บ์ฑํ๊ณ ์๋ต ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฌ์ฉํฉ๋๋ค. ์ฆ, Next ์๋ฒ์์ ๋ฐฑ์๋๋ก ๋ณด๋ด๋ ์์ฒญ์ ๋ํด์ Next ์๋ฒ๊ฐ ํด๋น ์์ฒญ์ ๋ํ ์๋ต ๋ฐ์ดํฐ๋ฅผ ์บ์ฑํ๊ฒ ๋ฉ๋๋ค.
๋ช ์์ ์ผ๋ก Next ์๋ฒ์ ์บ์ฑ๋ ์๋ต ๋ฐ์ดํฐ๋ฅผ ๋ฌดํจํํ๊ณ ์ฌ๊ฒ์ฆํ๊ธฐ ์ํด ์๋์ ๊ฐ์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
-
Route Segment Config: layout.tsx, page.tsx, Route Handlers์ dynamic, revalidate, fetchCache ์ต์ ์ ์ฌ์ฉํ์ฌ ๋ผ์ฐํธ ์ ์ฒด์ ๋ํ Data Cache ์ค์ ํ ์ ์์ต๋๋ค.
-
revalidatePath('/,,,', 'page' | 'layout'): Route Handlers๋ Server Actions์์ ํน์ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๋ฅผ ์ ๋ฌํ์ฌ Data Cache๋ฅผ ๋ฌดํจํํ๊ณ ์ฌ๊ฒ์ฆํ ์ ์์ต๋๋ค.
-
fetch('https://,,,', { cache: 'no-store' }): cache ์ต์ ์ "no-store"๋ฅผ ์ค์ ํ์ฌ Data Cache๋ฅผ ๋นํ์ฑํ ์ ์์ต๋๋ค.
-
fetch('https://,,,', { next: { revalidate: number }}): next.revalidate ์ต์ ์ผ๋ก Data Cache์ life time์ ์ด๋จ์๋ก ์์ฑํ ์ ์์ต๋๋ค.
์ด๋ ํ ์ค์ ๋ ํ์ง ์์ ๊ฒฝ์ฐ fetch์ Data Cache๋ ๋นํ์ฑ์ด ๊ธฐ๋ณธ๊ฐ์ ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก Next๋ ๋น๋ ํ์๋ ํ์ด์ง๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํ๊ณ ๋ ๋๋ง๋ ๊ฒฐ๊ณผ๋ฅผ Next ์๋ฒ์ ์บ์ฑํฉ๋๋ค. ์ดํ ํด๋ผ์ด์ธํธ๊ฐ ํ์ด์ง๋ฅผ ์์ฒญํ๋ฉด Next ์๋ฒ์ ์บ์ฑ๋ ๋ ๋๋ง ๊ฒฐ๊ณผ๋ฅผ ์ ๋ฌํ๊ฒ ๋ฉ๋๋ค.
๋ง์ฝ ํ์ด์ง ์์ฑ ๋ฐฉ์์ ๋ช ์์ ์ผ๋ก ๋ณ๊ฒฝํ๊ณ ์ถ๋ค๋ฉด ์๋์ ๊ฐ์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
-
Route Segments Config: layout.tsx, page.tsx, Route Handlers์ dynamic ํน์ revalidate ์ต์ ์ ์ฌ์ฉํ์ฌ ํ์ด์ง ์์ฑ ๋ฐฉ์์ ์ค์ ํ ์ ์์ต๋๋ค.
-
revalidatePath('/,,,', 'page' | 'layout'): Route Handlers๋ Server Actions์์ ํน์ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๋ฅผ ์ ๋ฌํ์ฌ ํ์ด์ง ์์ฑ ์์ ์ ์ค์ ํ ์ ์์ต๋๋ค.
Dynamic Functions(cookies, headers), searchParams prop ํน์ Data Cache๋ฅผ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ ํด๋น ํ์ด์ง๋ Full Route Cache๊ฐ ๋์ํ์ง ์๊ฒ ๋ฉ๋๋ค. ์ฆ, Full Route Cache๊ฐ ๋์ํ๋ ๊ฒฝ์ฐ๋ Data Cache๊ฐ ์ฌ์ฉํ๋ฉฐ Dynamic Functions๋ searchParams porp์ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ์๋ง Full Route Cache๊ฐ ๋์ํ๊ฒ ๋ฉ๋๋ค.
Next๋ ํด๋ผ์ด์ธํธ์ธก ์ธ๋ฉ๋ชจ๋ฆฌ์ page.tsx๊ฐ export defaultํ Page ์ปดํฌ๋ํธ๋ค์ ์บ์ฑํ๊ณ ์์ต๋๋ค. ์ฆ, Route Cache๋ Next ์๋ฒ๊ฐ ์๋ ํด๋ผ์ด์ธํธ์ธก์์ ์ด๋ฃจ์ด์ง๋ ์บ์ฑ์ ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก Route Cache๋ ๋นํ์ฑ ์ํ์ด๋ฉฐ ์ด๋ฅผ ํ์ฑํ์ํค๊ธฐ ์ํด์๋ ์๋์ ๊ฐ์ ์ค์ ์ด ์ถ๊ฐ์ ์ผ๋ก ํ์ํฉ๋๋ค.
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
staleTimes: {
dynamic: 30,
static: 180,
},
},
}
module.exports = nextConfig-
dynamic: Link์ prefetch ์์ฑ์ด ์ง์ ๋์ง ์์๊ฑฐ๋ false๋ก ์ค์ ๋ Page ์ปดํฌ๋ํธ์ Route Cache ์๊ฐ์ ์ด๋จ์๋ก ์์ฑํฉ๋๋ค.
-
static: Link์ prefetch ์์ฑ์ด true๋ก ์ค์ ๋์๊ฑฐ๋ router.prefetchํ Page ์ปดํฌ๋ํธ์ Route Cache ์๊ฐ์ ์ด๋จ์๋ก ์์ฑํฉ๋๋ค.
"next/image"๊ฐ ์ ๊ณตํ๋ Image ์ปดํฌ๋ํธ๋ฅผ ํตํด ์ด๋ฏธ์ง๋ฅผ ์ต์ ํํ ์ ์์ต๋๋ค.
-
์ฌ์ด์ฆ ์ต์ ํ: Next ์๋ฒ๋ ์๋์ผ๋ก ์ด๋ฏธ์ง ํ์ผ์ ํฌ๊ธฐ๋ ํฌ๋งท์ ์ต์ ํํ์ฌ ์ ๊ณตํด์ค๋๋ค.
-
CLS ๋ฐฉ์ง: ์ด๋ฏธ์ง๊ฐ ๋ก๋๋ ๋ layout shift ํ์์ ๋ฐฉ์ง์์ผ์ค๋๋ค.
-
lazy loading: ์ ๋ฐ์ ์ผ๋ก ์ด๋ฏธ์ง ๋ก๋๋ lazy load๊ฐ ์ ์ฉ๋์ด ํ์ด์ง ๋ก๋ ์๊ฐ์ ๋จ์ถ์ํต๋๋ค. ์ฆ, ์ด๋ฏธ์ง๊ฐ viewport์ ํ์๋ ๋๋ง ์ด๋ฏธ์ง๋ฅผ ๋ก๋ํ๊ณ ๋ ๋๋งํฉ๋๋ค.
ํ๋ก์ ํธ์ public ํด๋ ๋ด ํ์ผ๋ค์ ๋น๋๋ ์ดํ ๋ฃจํธ ๊ฒฝ๋ก์ ์กด์ฌํ๊ฒ ๋ฉ๋๋ค.
์๋ฅผ ๋ค์ด, "public/images/logo.png"๋ผ๋ ํ์ผ์ ๋น๋๋ ์ดํ์๋ "images/logo.png" ๊ฒฝ๋ก์ ์กด์ฌํ๊ฒ ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ฏ๋ก ์ค์ src props์ ์์ฑ๋ ๊ฒฝ๋ก ๋ํ "images/logo.png"๋ก ์์ฑํด์ฃผ์ด์ผ ํฉ๋๋ค.
Remote Images๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ ๋ฐ๋์ width์ height๋ฅผ ๋ช ์ํด์ฃผ์ด์ผ ํฉ๋๋ค. ์ด๋ ๋น๋ ํ์์ ํด๋น ์ด๋ฏธ์ง์ width, height ๊ฐ์ Next๋ ์์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ์ง์ ๋ช ์ํด์ฃผ์ด์ผ ํฉ๋๋ค.
์ธ๋ถ ์ด๋ฏธ์ง ๊ฒฝ๋ก๋ฅผ ์์ฑํ๋ ๊ฒฝ์ฐ์๋ width, height ๊ฐ์ ๋ช ์ํด์ฃผ์ด์ผ ํ๋ฉฐ ์ถ๊ฐ์ ์ผ๋ก next.config.jsํ์ผ์ ์ธ๋ถ ๊ฒฝ๋ก์ ๋๋ฉ์ธ์ ๋ช ์ํด์ฃผ์ด์ผ ํฉ๋๋ค.
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 's3.amazonaws.com',
port: '',
pathname: '/my-bucket/**'
}
]
}
};์ด๋ฏธ์ง ํ์ผ์ importํ์ฌ src์ ์์ฑํ ๊ฒฝ์ฐ Next๊ฐ ๋น๋ ํ์์ ์๋์ผ๋ก width, height ๊ฐ์ ๊ณ์ฐํ์ฌ ์ ์ฉํ๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก width, height ๊ฐ์ ๋ช ์ํด์ค ํ์๊ฐ ์์ต๋๋ค.
-
src: ์ด๋ฏธ์ง ๊ฒฝ๋ก๋ importํ ํ์ผ์ ์์ฑํฉ๋๋ค.
-
alt: ๋์ฒด ํ ์คํธ๋ฅผ ์์ฑํฉ๋๋ค.
-
width: ๋ ๋๋ง๋ ์ด๋ฏธ์ง์ ๊ฐ๋ก ์ฌ์ด์ฆ(px)๋ฅผ ์์ฑํฉ๋๋ค.
-
height: ๋ ๋๋ง๋ ์ด๋ฏธ์ง์ ์ธ๋ก ์ฌ์ด์ฆ(px)๋ฅผ ์์ฑํฉ๋๋ค.
fill props๊ฐ true ํน์ Local Images๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ width, height๋ฅผ ์๋ตํ ์ ์์ต๋๋ค.
- fill: ๋ถ๋ชจ ์์ ํฌ๊ธฐ์ ๋ง๊ฒ ์๋์ผ๋ก ์ด๋ฏธ์ง ํฌ๊ธฐ ์กฐ์ ์ฌ๋ถ๋ฅผ ์์ฑํฉ๋๋ค. ์ด๋ ๋ถ๋ชจ ์์์๋ position ํ๋กํผํฐ ๊ฐ์ผ๋ก "relative", "absolute", "fixed" ๊ฐ ์ค ํ๋๋ฅผ ์์ฑํด์ฃผ์ด์ผ ํฉ๋๋ค.
fill props๋ก true๋ฅผ ์์ฑํ ๊ฒฝ์ฐ ํด๋น ์ด๋ฏธ์ง์ "position" ๊ฐ์ด ์๋์ผ๋ก "absolute"๋ก ์ค์ ๋๊ธฐ ๋๋ฌธ์ ๋ถ๋ชจ ์์์ "position"๊ฐ์ "relative", "absolute", "fixed" ๊ฐ ์ค ํ๋ ์์ฑํด์ฃผ์ด์ผ ํฉ๋๋ค.
-
szies: fill props์ด๋ ๋ฐ์ํ ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์ ์ ์ฉํ๊ฒ ์ฌ์ฉ๋๋ฉฐ Next๊ฐ ์๋์ผ๋ก ์์ฑํ srcset ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ก๋๋ ์ด๋ฏธ์ง ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ ํ๋๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
- ๊ฐ์ผ๋ก๋ ๋ฏธ๋์ด ์ฟผ๋ฆฌ ๊ฐ์ ์์ฑํ๋ฉฐ, ๋ํดํธ ๊ฐ์ผ๋ก๋ "100vw"๊ฐ ์ ์ฉ๋์ด ์์ต๋๋ค. ์ฆ, ๋๋ฐ์ด์ค์ viewport์ ๋๋น๊ฐ 100vw์ผ ๋์ srcset ์ ๋ณด๋ฅผ ํ์ธํ์ฌ ์ด๋ฏธ์ง๋ฅผ ๋ก๋ํฉ๋๋ค.
srcset์ ๋ํ ์ ๋ณด๋ Next๊ฐ ์๋์ผ๋ก ์ค์ ํ๊ฒ ๋๋ฉฐ, ์ด๋ฅผ ์๋์ผ๋ก ์ค์ ํ๊ธฐ ์ํด์๋ next.config.js์์ images.deviceSizes, images.imageSizes๋ก ์ค์ ํ ์ ์์ต๋๋ค. fill props ๊ฐ์ด false์ธ ๊ฒฝ์ฐ์๋ images.imageSizes๋ฅผ ํตํด ์์ฑํ๊ณ , true์ธ ๊ฒฝ์ฐ์๋ images.deviceSizes๋ฅผ ํตํด ์์ฑํฉ๋๋ค.
-
quality: ์ด๋ฏธ์ง์ ํ๋ฆฌํฐ๋ฅผ 1์์ 100์ฌ์ด ๊ฐ์ผ๋ก ์์ฑํฉ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ 75๋ก ์ ์ฉ๋์ด ์์ต๋๋ค.
-
priority: ์ด๋ฏธ์ง ๋ก๋ preload ์ฌ๋ถ๋ฅผ ์์ฑํฉ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ false์ด๋ฉฐ, true๋ก ์ค์ ํ ๊ฒฝ์ฐ lazy loading ๋ํ ๋นํ์ฑํ ๋ฉ๋๋ค.
-
placeholder: ์ด๋ฏธ์ง๊ฐ ๋ก๋๋๋ ๋์ ํ์ํ placeholder ์ค์ ์ ์์ฑํฉ๋๋ค.
-
๊ธฐ๋ณธ๊ฐ์ "empty"์ด๋ฉฐ, ๋ ๋๋ง๋๋ ๋์ ํด๋น ์์ญ์ ๋น ์์ญ์ผ๋ก ๋น์๋ก๋๋ค.
-
"blur"๋ฅผ ์์ฑํ ๊ฒฝ์ฐ์๋ blurDataURL props๋ ํจ๊ป ์์ฑํด์ฃผ์ด์ผ ํ๋ฉฐ blurDataURL์ base64๋ก ์ธ์ฝ๋ฉ๋ ์ด๋ฏธ์ง ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๋ฉด ํด๋น ์ด๋ฏธ์ง๋ฅผ ํ์ํด์ค๋๋ค.
-
-
blurDataURL: placeholder props๊ฐ์ด "blur"์ธ ๊ฒฝ์ฐ์ ์ฌ์ฉ๋๋ฉฐ base64๋ก ์ธ์ฝ๋ฉ๋ ์ด๋ฏธ์ง ๋ฐ์ดํฐ๋ฅผ ์์ฑํฉ๋๋ค. ์ด๋ ์ด๋ฏธ์ง๊ฐ ๋ก๋๋๋ ๋์ ํ์ํ ์ด๋ฏธ์ง๋ฅผ ์์ฑํฉ๋๋ค.
Next๋ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ค์ ํ๊ธฐ ์ํ Metadata API๋ฅผ ์ ๊ณตํฉ๋๋ค.
layout.tsx ํน์ page.tsx์์ metadata๋ผ๋ ์ด๋ฆ์ ๋ณ์๋ฅผ exportํ์ฌ ์ ์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
// page.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: ',,,',
description: ',,,'
};
export default function Page() {
// ,,,
}"next"๊ฐ ์ ๊ณตํ๋ generateMetadata ๋ผ๋ ํจ์๋ฅผ exportํจ์ผ๋ก์จ ๋์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค. generateMetadata ํจ์๋ async ํจ์๋ก ์ ์ํด์ผํ๋ฉฐ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ๊ฐ์ฒด๋ฅผ ์ ๋ฌ๋ฐ๊ณ ๋ ๋ฒ์งธ ์ธ์๋ก๋ ์์ ๊ฒฝ๋ก์ ๋ํ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌ๋ฐ์ต๋๋ค.
์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌ๋ฐ๋ ๊ฐ์ฒด์๋ params, searchParams ํ๋กํผํฐ๊ฐ ์กด์ฌํ๋ฉฐ ์ด๋ ๋์ ๋ผ์ฐํ ์ธ๊ทธ๋จผํธ์ ์ฟผ๋ฆฌ์คํธ๋ง์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ๊ณ ์์ต๋๋ค.
// page.tsx
import type { Metadata, ResolvingMetadata } from 'next'
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
type Props = {
params: Params,
searchParams: SearchParams
};
export async function generateMetadata({ params, searchParams }: Props, parent: ResolvingMetadata) {
return {
title: ',,,',
description: ',,,'
};
}
export default function Page({ params, searchParams }: Props) {
// ,,,
}