Динамические маршруты
У каждой статьи свой адрес: /posts/hello-nextjs, /posts/server-components. Создавать папку под каждую статью нельзя, поэтому используют динамический сегмент — папку с именем в квадратных скобках. Она подходит для любого значения в этой части адреса.
Данные блога
Сначала вынесем статьи в отдельный файл. Создайте lib/posts.ts в корне проекта:
// lib/posts.ts
export type Post = {
slug: string;
title: string;
body: string;
};
const posts: Post[] = [
{ slug: 'hello-nextjs', title: 'Привет, Next.js', body: 'Первая статья блога.' },
{ slug: 'server-components', title: 'Серверные компоненты', body: 'Как они работают.' },
];
export async function getPosts(): Promise<Post[]> {
return posts;
}
export async function getPost(slug: string): Promise<Post | undefined> {
return posts.find((post) => post.slug === slug);
}Функции асинхронные, хотя пока читают массив: позже в них появится запрос к базе данных, а код страниц не изменится. slug — короткое имя статьи для адреса.
Папка [slug]
Создайте папку app/posts/[slug] и в ней page.tsx:
// app/posts/[slug]/page.tsx
import { getPost } from '@/lib/posts';
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) {
return <p>Статья не найдена</p>;
}
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}Откройте /posts/hello-nextjs — появится заголовок «Привет, Next.js». По адресу /posts/abc будет текст «Статья не найдена».
Как работает params
Next.js передаёт странице свойство params — объект с частями адреса. Имя ключа совпадает с именем папки: папка [slug] даёт params.slug. В Next.js 16 params — это промис, поэтому его получают через await, а компонент страницы объявляют async. Почему страница может быть асинхронной, объясняет урок о серверных компонентах.
Вместо ручного типа можно использовать готовый помощник PageProps<'/posts/[slug]'>: Next.js создаёт его сам при npm run dev и npm run build, импортировать его не нужно. В курсе тип пишется вручную, чтобы было видно, что внутри.
Список со ссылками
Страница списка теперь берёт данные из того же файла:
// app/posts/page.tsx
import Link from 'next/link';
import { getPosts } from '@/lib/posts';
export default async function PostsPage() {
const posts = await getPosts();
return (
<ul>
{posts.map((post) => (
<li key={post.slug}>
<Link href={`/posts/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
);
}Ссылки со страницы /posts теперь ведут на настоящие статьи.
Другие виды сегментов
| Папка | Подходит для | Значение |
|---|---|---|
[slug] | /posts/a | 'a' |
[...slug] | /docs/a/b | ['a', 'b'] |
[[...slug]] | /docs и /docs/a/b | undefined или массив |
Частая ошибка
Читать params.slug без await, как в статьях о Next.js 14. В Next.js 15 это ещё работало с предупреждением, а в Next.js 16 синхронный доступ удалён: params.slug будет undefined, и страница не найдёт статью. Всегда пишите const { slug } = await params.