generateStaticParams와 params await
Next.js의 Dynamic Route에서는 params를 Promise로 전달받습니다.
export default async function Page({
params,
}: PageProps<'/blog/[slug]'>) {
const { slug } = await params;
return <div>{slug}</div>;
}
값이 필요하면 await params를 하면 되지만, Cache Components 사용 여부에 따라 await 위치의 의미가 달라집니다.
| 구분 | params 처리 방식 |
|---|---|
| Cache Components 미사용 | 최상단 await 가능, <Suspense> 불필요 |
Cache Components 사용 + generateStaticParams 없음 | <Suspense> 필요, Layout은 아래에서 await |
Cache Components 사용 + generateStaticParams 있음 | 빌드 시 prerender, 최상단 await 가능 |
다음 코드처럼 최상단에서 await params해도 됩니다.
export default async function Layout({
params,
children,
}: LayoutProps<'/blog/[slug]'>) {
const { slug } = await params;
return <>{children}</>;
}
<Suspense>는 느린 데이터 로딩을 나눠 보여주고 싶을 때 선택적으로 사용합니다.
Cache Components를 사용한다면
1. generateStaticParams가 없으면 params는 runtime data입니다
다음과 같은 Dynamic Route가 있다고 하겠습니다.
app/
└── blog/
└── [slug]/
└── page.tsx
generateStaticParams가 없다면 빌드 시점에는 실제 slug 값을 알 수 없습니다.
/blog/[slug]
빌드 시점
slug = ?
이 경우 params는 runtime data입니다.
Cache Components를 사용하는 경우, params가 필요한 부분은 <Suspense> 안에서 처리하고 그 밖의 정적인 UI는 먼저 prerender할 수 있습니다.
2. params가 필요한 곳에서만 await합니다
Page 최상단에서 바로 await params를 하지 않고, 필요한 부분에서만 값을 읽을 수 있습니다.
import { Suspense } from 'react';
export default function Page({ params }: PageProps<'/blog/[slug]'>) {
return (
<div>
<h1>Blog Post</h1>
<Suspense fallback={<div>Loading...</div>}>
{params.then(({ slug }) => (
<Content slug={slug} />
))}
</Suspense>
</div>
);
}
구조를 단순하게 보면 다음과 같습니다.
Page
├─ Blog Post
│ └─ params 필요 없음
│
└─ Suspense
└─ params 확인
└─ Content
Blog Post라는 제목은 slug가 없어도 만들 수 있습니다.
반면 Content는 어떤 게시물을 가져올지 알아야 하므로 slug가 필요합니다.
따라서 params와 관계없는 부분까지 기다리지 않고, 실제로 params가 필요한 부분에서만 값을 읽습니다.
3. Layout에서는 왜 더 중요할까?
Layout 최상단에서 runtime params를 await하면 Layout의 prerender를 막을 수 있습니다.
Layout
↓
바로 await params
↓
Layout 전체가 runtime params에 의존
대신 다음처럼 생각하는 것이 좋습니다.
Layout
├─ Header
├─ Navigation
│
└─ params가 필요한 컴포넌트
↓
await params
즉 기준은
params를 받을 수 있는 가장 위에서 읽는 것
이 아니라
params가 실제로 필요한 가장 가까운 곳에서 읽는 것
입니다.
이렇게 하면 Header나 Navigation처럼 params와 관계없는 부분은 prerender 가능한 상태로 남길 수 있습니다.
generateStaticParams가 있다면generateStaticParams로 특정 params를 빌드 시점에 미리 제공한 경우에는 상황이 다릅니다.
이 경우에는 Page 최상단에서 await params를 사용할 수 있습니다.
즉 await params 자체가 문제라기보다, 그 params가 runtime data인지와 어디에서 접근하는지를 함께 봐야 합니다.
단, generateStaticParams가 모든 값을 반환한다는 뜻은 아닙니다. 반환하지 않은 params는 첫 요청 시점에 처리될 수 있고, 그 경로에서 runtime API를 사용한다면 별도의 <Suspense> 경계가 필요할 수 있습니다.
정리
- Dynamic Route의
params는 Promise입니다. - Cache Components 미사용: Page나 Layout 최상단에서
await params할 수 있습니다. - Cache Components 사용 +
generateStaticParams없음: params는 runtime data이므로<Suspense>안에서 접근합니다. Layout에서는 실제로 필요한 컴포넌트까지 Promise를 전달합니다. generateStaticParams가 있으면 빌드 시점에 제공한 params로 prerender할 수 있습니다.
예상 질문
Server Component에서는 그냥 await params 하면 되는 것 아닌가요?
맞습니다. Cache Components를 사용하지 않는다면 Page나 Layout 최상단에서 await params해도 됩니다.
다만 Cache Components를 사용하고 generateStaticParams가 없다면, runtime params를 Layout 최상단에서 await할 경우 정적인 Layout까지 prerender하지 못할 수 있습니다.
왜 <Suspense>가 필요한가요?
Cache Components를 사용하는 경우, generateStaticParams가 없다면 params는 prerender 시점에 알 수 없는 runtime data이기 때문입니다.
params에 접근하는 부분을 <Suspense> 경계 안에서 처리합니다.
generateStaticParams가 있으면 최상단에서 await해도 되나요?
미리 제공한 params로 route를 빌드할 수 있기 때문에 Page 최상단에서 await params를 사용할 수 있습니다.
다만 generateStaticParams가 반환하지 않은 params는 첫 요청 시점에 처리될 수 있습니다. 그 경로에서 runtime API를 사용한다면 <Suspense> 경계가 필요할 수 있습니다.