Пошук уроків, статей та іншого контенту
Реалізуєте маршрути для довільної кількості сегментів за допомогою catch-all і optional catch-all синтаксису.
Динамічний сегмент маршруту відповідає одному сегменту URL:
app/products/[id]/page.jsТакий маршрут може обробити:
/products/42Але він не обробить:
/products/42/reviewsCatch-all маршрут дає змогу приймати довільну кількість сегментів після певної частини шляху.
У Next.js App Router для цього використовують синтаксис:
[...param]Наприклад:
app/docs/[...slug]/page.jsЦей маршрут може відповідати таким URL:
/docs/getting-started
/docs/api/users
/docs/api/users/createЗначення slug буде масивом:
["api", "users", "create"][...param]Розглянемо структуру маршруту:
app/
└── docs/
└── [...slug]/
└── page.jsСегмент [...slug] називається catch-all сегментом. Він обов’язково має містити хоча б один сегмент URL.
| URL | Значення slug | |---|---| | /docs/getting-started | ["getting-started"] | | /docs/api/users | ["api", "users"] | | /docs/api/users/create | ["api", "users", "create"] | | /docs | маршрут не відповідає |
У компоненті сторінки параметри доступні через властивість params.
// app/docs/[...slug]/page.js
export default async function DocsPage({ params }) {
const { slug } = await params;
return (
<main>
<h1>Документація</h1>
<p>
Поточний шлях: /docs/{slug.join("/")}
</p>
<h2>Сегменти маршруту</h2>
<ul>
{slug.map((segment, index) => (
<li key={`${segment}-${index}`}>
{index + 1}. {segment}
</li>
))}
</ul>
</main>
);
}Для URL:
/docs/api/users/createкомпонент отримає:
{
slug: ["api", "users", "create"]
}Метод join("/") об’єднує масив назад у частину URL:
slug.join("/");Результат:
api/users/create[[...param]]Іноді потрібно, щоб маршрут відповідав не лише URL із сегментами, а й базовому URL без них.
Для цього використовується optional catch-all синтаксис:
[[...param]]Наприклад:
app/catalog/[[...parts]]/page.jsЦей маршрут відповідає таким URL:
/catalog
/catalog/books
/catalog/books/fiction
/catalog/books/fiction/fantasyГоловна відмінність від звичайного catch-all маршруту полягає в тому, що базовий шлях також дозволений.
| URL | Значення parts | |---|---| | /catalog | undefined | | /catalog/books | ["books"] | | /catalog/books/fiction | ["books", "fiction"] |
// app/catalog/[[...parts]]/page.js
export default async function CatalogPage({ params }) {
const { parts = [] } = await params;
const currentPath = parts.length > 0
? `/catalog/${parts.join("/")}`
: "/catalog";
return (
<main>
<h1>Каталог</h1>
<p>Поточний шлях: {currentPath}</p>
{parts.length === 0 ? (
<p>Ви перебуваєте на головній сторінці каталогу.</p>
) : (
<>
<p>Категорії та підкатегорії:</p>
<ul>
{parts.map((part, index) => (
<li key={`${part}-${index}`}>
{part}
</li>
))}
</ul>
</>
)}
</main>
);
}Значення за замовчуванням:
const { parts = [] } = await params;перетворює undefined на порожній масив. Завдяки цьому можна безпечно викликати:
parts.join("/");
parts.map(...);
parts.length;Без значення за замовчуванням спроба викликати map або join для undefined призведе до помилки.
app/docs/[...slug]/page.jsВідповідає:
/docs/api
/docs/api/usersНе відповідає:
/docsПараметр завжди є масивом, якщо маршрут було зіставлено:
["api", "users"]app/docs/[[...slug]]/page.jsВідповідає:
/docs
/docs/api
/docs/api/usersДля базового URL параметр може бути відсутнім:
undefinedДля URL із сегментами параметр є масивом:
["api", "users"]Назва параметра в квадратних дужках визначає ключ у params.
Маршрут:
app/files/[...path]/page.jsотримує параметри у форматі:
{
path: ["documents", "report.pdf"]
}Маршрут:
app/shop/[[...categories]]/page.jsотримує:
{
categories: ["electronics", "phones"]
}Назва може бути будь-якою допустимою назвою JavaScript-властивості, але бажано обирати зрозумілі імена:
slug — для шляхів до статей або документації;
path — для файлових або ієрархічних шляхів;
categories — для категорій;
segments — для універсального набору сегментів.
У старішому Pages Router синтаксис файлів такий самий, але файли розміщуються в директорії pages.
Звичайний catch-all маршрут:
pages/docs/[...slug].jsOptional catch-all маршрут:
pages/docs/[[...slug]].jsУ Pages Router параметри зазвичай читають через getServerSideProps або useRouter.
Приклад із getServerSideProps:
// pages/docs/[...slug].js
export default function DocsPage({ slug }) {
return (
<main>
<h1>Документація</h1>
<p>Шлях: /docs/{slug.join("/")}</p>
</main>
);
}
export function getServerSideProps({ params }) {
return {
props: {
slug: params.slug,
},
};
}Для optional catch-all маршруту значення params.slug може бути undefined, тому його потрібно нормалізувати:
const slug = params.slug ?? [];У нових застосунках із App Router використовуйте структуру app і параметр params у компоненті сторінки.
Catch-all маршрути зручні, коли кількість рівнів URL заздалегідь невідома.
Типові приклади:
документація з довільною вкладеністю;
файловий браузер;
ієрархія категорій;
сторінки з вкладеними розділами;
універсальний проксі-маршрут для шляхів.
Наприклад, структура документації може мати різну глибину:
/docs/javascript
/docs/javascript/functions
/docs/javascript/functions/closures
/docs/api
/docs/api/users
/docs/api/users/createДля всіх цих URL можна використати один маршрут:
app/docs/[...slug]/page.jsЯкщо маршрут має рівно один динамічний сегмент, використовуйте звичайний динамічний сегмент:
app/products/[id]/page.jsЯкщо маршрут може мати кілька сегментів, використовуйте catch-all:
app/products/[...path]/page.jsЯкщо має підтримуватися також базовий шлях без додаткових сегментів, використовуйте optional catch-all:
app/products/[[...path]]/page.jsПриклад вибору:
/products/[id]підходить для:
/products/42Але для таких шляхів краще використати:
/products/[...path]/products/42/reviews
/products/42/reviews/commentsCatch-all параметр є масивом рядків, а не одним рядком:
{
slug: ["api", "users", "create"]
}Тому не слід працювати з ним як із рядком:
slug.toUpperCase();Замість цього потрібно обробити окремі елементи або спочатку об’єднати їх:
const path = slug.join("/");
const upperPath = path.toUpperCase();Під час відображення сегментів зручно використовувати map:
slug.map((segment) => (
<span key={segment}>{segment}</span>
));Якщо параметр є optional catch-all, спочатку переконайтеся, що він існує, або задайте значення за замовчуванням:
const { slug = [] } = await params;Файл:
app/docs/[slug]/page.jsобробляє лише один сегмент після /docs:
/docs/apiВін не обробляє:
/docs/api/usersДля довільної кількості сегментів потрібен файл:
app/docs/[...slug]/page.jsНеправильно:
const { slug } = await params;
return <p>{slug.toUpperCase()}</p>;slug є масивом, тому потрібно спочатку перетворити його на рядок:
const path = slug.join("/");
return <p>{path.toUpperCase()}</p>;[...slug] для базового URLМаршрут:
app/docs/[...slug]/page.jsне відповідає /docs.
Якщо базовий URL також має бути доступним, використовуйте:
app/docs/[[...slug]]/page.jsundefinedВ optional catch-all маршруті параметр може бути відсутнім:
const { slug } = await params;Тому такий код небезпечний:
slug.map((item) => item);Безпечний варіант:
const { slug = [] } = await params;
slug.map((item) => item);Next.js розпізнає спеціальний синтаксис лише в назвах сегментів директорій маршруту.
Правильно:
app/docs/[...slug]/page.jsНеправильно:
app/docs/page-[...slug].js[...param] — catch-all маршрут для одного або кількох сегментів.
[[...param]] — optional catch-all маршрут, який також відповідає базовому шляху.
Значення catch-all параметра є масивом рядків.
Для optional catch-all параметр на базовому URL може бути undefined.
У App Router параметри сторінки доступні через params.
Для безпечної роботи з optional параметрами зручно використовувати значення за замовчуванням [].
Catch-all маршрути підходять для документації, категорій, файлових шляхів та інших URL із невідомою заздалегідь кількістю сегментів.