Next.js๋ app ๋๋ ํ ๋ฆฌ ๋ด์ ํด๋ ๊ตฌ์กฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก URL ๊ฒฝ๋ก๊ฐ ์๋์ผ๋ก ์์ฑ๋๋ค. layout, loading, error ๋ฑ์ ํน์ ํ์ผ์ ๊ฐ์ ๋๋ ํ ๋ฆฌ ๋ฐ ํ์ ๋๋ ํ ๋ฆฌ์ ์๋์ผ๋ก ์ ์ฉ๋๋ค.
์ ํํ ์ธ๊ทธ๋จผํธ ์ด๋ฆ์ ๋ฏธ๋ฆฌ ์์ง ๋ชปํ๊ณ ๋์ ๋ฐ์ดํฐ๋ก ๊ฒฝ๋ก๋ฅผ ์์ฑํ๊ณ ์ถ์ ๋๋ ๋์ ์ธ๊ทธ๋จผํธ๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
detail/[id]/page.tsxdetail/1/page.tsx, detail/2/page.tsx ๋ฑ ๋ค์ํ ๊ฒฝ๋ก ์์ฑ ๊ฐ๋ฅNext.js์์ generateStaticParams ํจ์๋ ๋์ ๋ผ์ฐํธ ์ค์์ ๋น๋ ์์ ์ ๋ฏธ๋ฆฌ ์์ฑํ ํน์ ๊ฒฝ๋ก๋ฅผ ์ง์ ํ๋ ๊ธฐ๋ฅ์ด๋ค.
// app/faq/[category]/page.js
export async function generateStaticParams() {
// FAQ ์นดํ
๊ณ ๋ฆฌ ๋ชฉ๋ก
const categories = [
{ slug: 'account-issues' },
{ slug: 'billing-questions' },
{ slug: 'product-features' },
{ slug: 'troubleshooting' }
];
return categories.map((category) => ({
category: category.slug,
}));
}
์ด๋ ๊ฒ ํ๋ฉด ๋น๋ ๊ณผ์ ์์:
1. Next.js๊ฐ generateStaticParams ํจ์๋ฅผ ์คํ
2. ๋ฐํ๋ ๋งค๊ฐ๋ณ์ ๋ชฉ๋ก(4๊ฐ์ง ์นดํ
๊ณ ๋ฆฌ)์ ํ์ธ
3. ๊ฐ ๋งค๊ฐ๋ณ์์ ๋ํด ํ์ด์ง๋ฅผ ๋ฏธ๋ฆฌ ๋ ๋๋งํ์ฌ HTML ํ์ผ ์์ฑ
4. ์ฌ์ฉ์ ์์ฒญ ์ ์๋ฒ์์ ๋ค์ ๋ ๋๋งํ ํ์ ์์ด ๋ฐ๋ก ์ ๊ณตํ์ฌ ๋ก๋ฉ ์๋ ํฅ์
ํ๋ผ๋ฏธํฐ ๊ฐ์ ๊ฐ์ ธ์ค๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค:
์ปดํฌ๋ํธ์ props๋ก ์ ๊ทผ:
export default function Page({ params }) {
const { id } = params;
return <div>ID: {id}</div>;
}
useParams ํ ์ฌ์ฉ:
'use client'
import { useParams } from 'next/navigation';
export default function Page() {
const params = useParams();
return <div>ID: {params.id}</div>;
}
๊ฐ์ธ์ ์ผ๋ก๋ ์๋ฒ ์ปดํฌ๋ํธ๋ฅผ ์ ์งํ ์ ์๋ ์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ(props)์ด ๋ ํจ์จ์ ์ด๋ผ๊ณ ์๊ฐํ๋ค. ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๋ก ๋ณํํ์ง ์์๋ ๋๊ณ , ์ถ๊ฐ import ์์ด ๊ฐ๊ฒฐํ๊ฒ ์ฝ๋๋ฅผ ์ ์งํ ์ ์๋ค.
ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ค๋น๊ฒ์ด์ ๋ฐฉ์์ผ๋ก ํ์ด์ง๋ฅผ ์ด๋ํ๋ ๋ฐฉ๋ฒ:
<Link href="/detail">์์ธ ํ์ด์ง๋ก</Link>
<a> ํ๊ทธ: ์ ์ฒด ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํ๋ฉฐ ์๋ฒ์์ ์ HTML ํ์ผ์ ๋ฐ์์ด<Link> ์ปดํฌ๋ํธ: ํ๋ฆฌํจ์น(๋ฏธ๋ฆฌ ๋ฐ์ดํฐ ๊ฐ์ ธ์ด) ๋ฐฉ์์ผ๋ก ๋ธ๋ผ์ฐ์ ๊ฐ ์ ์ฒด ์๋ก๊ณ ์นจ ์์ด ํ๋ฉด ์ ํ๊ดํธ () ๋ก ๊ฐ์ผ ํด๋๋ URL ๊ฒฝ๋ก์ ํฌํจ๋์ง ์์ผ๋ฉฐ, ํ์ ํด๋๋ง ๊ฒฝ๋ก์ ํฌํจ๋๋ค.
app/
โโโ (shop)/ # URL์ ํฌํจ๋์ง ์์
โ โโโ layout.tsx # shop ๊ทธ๋ฃน ์ ์ฉ ๋ ์ด์์
โ โโโ products/ # /products ๊ฒฝ๋ก
โ โโโ categories/ # /categories ๊ฒฝ๋ก
โโโ layout.tsx # ๋ฃจํธ ๋ ์ด์์
์ฅ์ :
์๋ฅผ ๋ค์ด, /page-a์ /page-b ๊ฒฝ๋ก๊ฐ ์๋ก ๋ค๋ฅธ ๋ ์ด์์์ ์ฌ์ฉํ๋๋ก ํ ์ ์๋ค:
app/
โโโ (with-layout)/
โ โโโ layout.js # ์ด ๋ ์ด์์์ ์ด ํด๋ ์์ ํ์ด์ง์๋ง ์ ์ฉ
โ โโโ page-a/
โ โโโ page.js # /page-a ๊ฒฝ๋ก, ๋ ์ด์์ ์ ์ฉ
โโโ (without-layout)/
โ โโโ page-b/
โ โโโ page.js # /page-b ๊ฒฝ๋ก, ๋ค๋ฅธ ๋ ์ด์์ ์ ์ฉ
โโโ layout.js # ๋ฃจํธ ๋ ์ด์์
@ ๊ธฐํธ๋ก ์์ํ๋ ํด๋๋ก ํน์ ํ์ด์ง์ ๋ํ ๋ ์ด์์ ์ ์ฉ์ ์กฐ์ํ ์ ์๋ค:
app/
โโโ @with-layout/
โ โโโ layout.js
โ โโโ page-a/
โ โโโ page.js # /page-a, ๋ ์ด์์ ์ ์ฉ
โโโ @without-layout/
โ โโโ page-b/
โ โโโ page.js # /page-b, ๋ ์ด์์ ์ ์ฉ ์๋จ
โโโ layout.js # ์กฐ๊ฑด๋ถ๋ก ๋ ์ด์์ ์ ์ฉ ๋ก์ง ํฌํจ
๋ฃจํธ ๋ ์ด์์์์ ์กฐ๊ฑด๋ถ ๋ ๋๋ง:
// app/layout.js
'use client'
import { usePathname } from 'next/navigation';
export default function RootLayout({ children, withLayout, withoutLayout }) {
const pathname = usePathname();
if (pathname.startsWith('/page-b')) {
return <>{withoutLayout}</>;
}
return (
<html>
<body>
<Header />
<Sidebar />
<main>{withLayout || children}</main>
<Footer />
</body>
</html>
);
}
์ธ๋์ค์ฝ์ด(_)๋ก ์์ํ๋ ํด๋๋ URL ๊ฒฝ๋ก์์ ์์ ํ ์ ์ธ๋๋ค. ์ด๋ ๊ณต๊ฐ์ ์ผ๋ก ์ ๊ทผํ ์ ์๋ ์ ํธ๋ฆฌํฐ๋ ์ปดํฌ๋ํธ๋ฅผ ์ ์ฅํ๋ ๋ฐ ์ ์ฉํ๋ค.
app/
โโโ _utils/ # URL ๊ฒฝ๋ก์์ ์ ์ธ๋จ
โโโ _components/ # URL ๊ฒฝ๋ก์์ ์ ์ธ๋จ
โโโ products/ # /products ๊ฒฝ๋ก ์ฌ์ฉ ๊ฐ๋ฅ
Next.js๋ ๋ค ๊ฐ์ง ์ฃผ์ ๋ ๋๋ง ๋ฐฉ์์ ์ง์ํ๋ค:
๋น๋ ์ ํ๋ฆฌ๋ ๋๋ง ํ, ์ผ์ ์๊ฐ์ด ์ง๋๋ฉด ํ์ด์ง ์ฌ์์ฑ
์ค์ ๋ฐฉ๋ฒ:
1) Route Segment Config ์ฌ์ฉ:
export const revalidate = 5; // 5์ด ๊ฐ๊ฒฉ์ผ๋ก ์ฌ๊ฒ์ฆ
2) fetch ์ต์ ์ฌ์ฉ:
fetch('https://api.example.com/data', { next: { revalidate: 5 } })
์ฌ์ฉ์ ์์ฒญ๋ง๋ค ์๋ฒ ์ปดํฌ๋ํธ๊ฐ ์คํ๋์ด HTML ์์ฑ
์ค์ ๋ฐฉ๋ฒ:
1) Route Segment Config ์ฌ์ฉ:
export const dynamic = 'force-dynamic'
2) fetch ์ต์ ์ฌ์ฉ:
fetch('https://api.example.com/data', { cache: 'no-store' })
3) ๋ค์์ ๊ฒฝ์ฐ ์๋์ผ๋ก SSR ์ ์ฉ:
/detail/[id]/page.tsx)์ด๋ ์๊ธฐ ์๋ HTML ํ๋ ์์ ์๋ฐ์คํฌ๋ฆฝํธ ์คํ์ ํตํด ์๋๊ฐ์ ๋ถ์ด๋ฃ๋ ๊ณผ์ ์ผ๋ก ๋ณผ ์ ์๋ค.