동적 라우트 — params 를 await 한다
Article
블로그 글이 백 개라면 page.js를 백 개 만들 수는 없다. 주소의 일부가 변수여야 한다.
대괄호가 변수를 만든다
폴더 이름을 대괄호로 감싼다.
- app/posts/[id]/page.js
- /posts/1
- /posts/2
- /posts/hello-world
파일 하나가 무한한 주소를 처리한다. 폴더 이름 [id]의 id가 곧 변수 이름이다.
params 는 Promise 다
Next.js 16에서 가장 많이 부딪히는 변경점이다.
export default async function Post({ params }) {
const { id } = await params;
return <h1>{id}번 글</h1>;
}
params를 그냥 쓰지 않고 await 한다. 그래서 컴포넌트도 async여야 한다.
옛날 코드는 이렇게 생겼었다.
export default function Post({ params }) {
return <h1>{params.id}번 글</h1>;
}
Next.js 15까지는 이것도 동작했다. 경고만 띄우고 넘어갔다. 16부터는 완전히 막혔다.
- Error: Route "/posts/[id]" used `params.id`. `params` should be awaited before using its properties.
| 무엇이 | Next 15 까지 | Next 16 부터 |
|---|---|---|
| params | 동기 접근 가능 (경고) | await 필수 |
| searchParams | 동기 접근 가능 (경고) | await 필수 |
| cookies() | 동기 접근 가능 (경고) | await 필수 |
| headers() | 동기 접근 가능 (경고) | await 필수 |
| draftMode() | 동기 접근 가능 (경고) | await 필수 |
인터넷의 예제 대부분이 아직 옛 문법이다. params.id를 그냥 쓰는 코드를 보면 Next 15 이전 글이라고 생각하면 된다.
값은 항상 문자열이다
export default async function Post({ params }) {
const { id } = await params;
console.log(typeof id);
const post = await getPost(Number(id));
}
- 'string'
/posts/1이든 /posts/999든 id는 문자열 "1", "999"다. URL에 타입이라는 게 없기 때문이다.
자바스크립트 편 7강에서 본 함정이 여기서 나온다. 숫자로 다루려면 Number()로 바꾼다.
searchParams — 물음표 뒤의 것
/posts?page=2&sort=new 같은 주소의 뒷부분이다.
export default async function Posts({ searchParams }) {
const { page = "1", sort = "new" } = await searchParams;
const posts = await getPosts({ page: Number(page), sort });
return <ul>…</ul>;
}
searchParams도 await 한다. 그리고 page.js에서만 받을 수 있다. 레이아웃은 못 받는다.
params
주소의 일부
/posts/[id]의id- 폴더 구조가 정한다
- page · layout 둘 다 받는다
searchParams
물음표 뒤
?page=2의page- 사용자가 정한다
- page 만 받는다
여러 조각을 받는다
[...slug]는 남은 주소를 전부 받는다.
| 폴더 | 주소 | 받는 값 |
|---|---|---|
| [id] | /posts/1 | id = "1" |
| [...slug] | /docs/a/b/c | slug = ["a", "b", "c"] |
| [...slug] | /docs | 404 — 최소 하나는 필요 |
| [[...slug]] | /docs | slug = undefined |
| [[...slug]] | /docs/a | slug = ["a"] |
대괄호를 두 겹으로 치면 없어도 된다는 뜻이다. 문서 사이트처럼 깊이를 알 수 없는 경로에 쓴다.
미리 만들어둘 주소를 알려준다
/posts/1부터 /posts/100까지가 자주 바뀌지 않는 글이라면, 요청마다 만들 이유가 없다. 빌드할 때 미리 만들어두면 훨씬 빠르다.
generateStaticParams가 그 목록을 알려주는 함수다.
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map(post => ({ id: String(post.id) }));
}
export default async function Post({ params }) {
const { id } = await params;
const post = await getPost(id);
return <article>{post.title}</article>;
}
빌드할 때 Next.js가 generateStaticParams를 부르고, 반환된 목록만큼 Post를 실행해 HTML 파일을 미리 만들어둔다.
- generateStaticParams[{id:"1"}, {id:"2"}…]
- 각각 렌더링Post({params}) 를 100번
- HTML 100개서버에 저장
- 요청만들어둔 걸 그냥 보낸다
목록에 없는 주소로 들어오면 어떻게 될까. 기본값은 그때 만들어서 보여주고 저장해둔다. 다음 사람은 저장된 걸 받는다.
메타데이터도 params 를 받는다
각 글마다 다른 제목을 <title>에 넣으려면 generateMetadata를 쓴다.
export async function generateMetadata({ params }) {
const { id } = await params;
const post = await getPost(id);
return {
title: post.title,
description: post.summary,
};
}
여기서도 await params다. 16강에서 메타데이터를 제대로 다룬다.
링크로 이동한다
동적 주소로 가는 링크는 문자열을 만들어 넣는다.
import Link from "next/link";
export default async function Posts() {
const posts = await getPosts();
return (
<ul>
{posts.map(post => (
<li key={post.id}>
<Link href={`/posts/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
);
}
React 편 11강의 key와 map이 그대로 쓰인다. 자바스크립트 편 5강의 템플릿 리터럴도 보인다.
정리하면
- 폴더 이름을 대괄호로 감싸면 그 자리가 변수가 된다.
[id]→params.id - Next.js 16부터
params와searchParams는 Promise다. 반드시await한다. cookies(),headers(),draftMode()도 마찬가지다. 동기 접근이 완전히 사라졌다.params의 값은 항상 문자열이다. 숫자로 쓰려면Number()로 바꾼다.searchParams는page.js에서만 받을 수 있고, 쓰는 순간 그 페이지는 동적이 된다.[...slug]는 여러 조각을,[[...slug]]는 없어도 되는 여러 조각을 받는다.generateStaticParams로 빌드 시점에 미리 만들 주소 목록을 알려준다.
다음 강의에서는 페이지 사이를 오가는 법을 자세히 본다. <Link> 말고도 방법이 있다.