학습 자료

동적 라우트 — params 를 await 한다


Article

블로그 글이 백 개라면 page.js를 백 개 만들 수는 없다. 주소의 일부가 변수여야 한다.

대괄호가 변수를 만든다

폴더 이름을 대괄호로 감싼다.

[id] 는 무엇이든 받는다
  • app/posts/[id]/page.js
    • /posts/1id = "1"
    • /posts/2id = "2"
    • /posts/hello-worldid = "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/999id는 문자열 "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>;
}

searchParamsawait 한다. 그리고 page.js에서만 받을 수 있다. 레이아웃은 못 받는다.

params 와 searchParams

params

주소의 일부

  • /posts/[id]id
  • 폴더 구조가 정한다
  • page · layout 둘 다 받는다

searchParams

물음표 뒤

  • ?page=2page
  • 사용자가 정한다
  • page 만 받는다

여러 조각을 받는다

[...slug]는 남은 주소를 전부 받는다.

폴더주소받는 값
[id]/posts/1id = "1"
[...slug]/docs/a/b/cslug = ["a", "b", "c"]
[...slug]/docs404 — 최소 하나는 필요
[[...slug]]/docsslug = undefined
[[...slug]]/docs/aslug = ["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 파일을 미리 만들어둔다.

빌드 시점에 벌어지는 일
  1. generateStaticParams[{id:"1"}, {id:"2"}…]
  2. 각각 렌더링Post({params}) 를 100번
  3. HTML 100개서버에 저장
  4. 요청만들어둔 걸 그냥 보낸다

목록에 없는 주소로 들어오면 어떻게 될까. 기본값은 그때 만들어서 보여주고 저장해둔다. 다음 사람은 저장된 걸 받는다.

메타데이터도 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강의 keymap이 그대로 쓰인다. 자바스크립트 편 5강의 템플릿 리터럴도 보인다.

정리하면

  • 폴더 이름을 대괄호로 감싸면 그 자리가 변수가 된다. [id]params.id
  • Next.js 16부터 paramssearchParams는 Promise다. 반드시 await 한다.
  • cookies(), headers(), draftMode()도 마찬가지다. 동기 접근이 완전히 사라졌다.
  • params의 값은 항상 문자열이다. 숫자로 쓰려면 Number()로 바꾼다.
  • searchParamspage.js에서만 받을 수 있고, 쓰는 순간 그 페이지는 동적이 된다.
  • [...slug]는 여러 조각을, [[...slug]]는 없어도 되는 여러 조각을 받는다.
  • generateStaticParams빌드 시점에 미리 만들 주소 목록을 알려준다.

다음 강의에서는 페이지 사이를 오가는 법을 자세히 본다. <Link> 말고도 방법이 있다.

동적 라우트 — params 를 await 한다 — 디코드랩(DCODELAB)