Пошук уроків, статей та іншого контенту
Реалізуєте sitemap для великої кількості сторінок, генеруючи URL динамічно з бази або зовнішнього API.
Sitemap — це XML-файл зі списком URL, які пошукові системи можуть сканувати. Для невеликого сайту його можна створити вручну, але для блогу, каталогу або маркетплейсу список сторінок часто змінюється.
У Next.js з App Router sitemap створюється спеціальним файлом:
app/sitemap.tsNext.js автоматично обробляє цей файл і робить його доступним за адресою:
/sitemap.xmlФункція sitemap може:
отримувати сторінки з бази даних;
завантажувати дані із зовнішнього API;
формувати URL на основі slug;
додавати дату останньої зміни;
оновлювати дані через певний інтервал.
Next.js надає тип MetadataRoute.Sitemap. Кожен елемент sitemap може містити:
url — абсолютна адреса сторінки;
lastModified — дата останньої зміни;
changeFrequency — приблизна частота змін;
priority — пріоритет URL.
Наприклад:
import type { MetadataRoute } from "next";
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: "https://example.com",
lastModified: new Date(),
changeFrequency: "yearly",
priority: 1,
},
];
}Для динамічної sitemap функція може бути асинхронною та виконувати запит до API або бази даних.
Припустімо, CMS повертає сторінки у такому форматі:
{
"pages": [
{
"slug": "about",
"updatedAt": "2026-01-15T10:00:00.000Z"
},
{
"slug": "contact",
"updatedAt": "2026-01-20T12:30:00.000Z"
}
]
}Створимо файл app/sitemap.ts:
import type { MetadataRoute } from "next";
type CmsPage = {
slug: string;
updatedAt: string;
};
type CmsResponse = {
pages: CmsPage[];
};
const siteUrl = process.env.SITE_URL;
if (!siteUrl) {
throw new Error("SITE_URL environment variable is not defined");
}
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const response = await fetch(`${process.env.CMS_URL}/api/pages`, {
next: {
revalidate: 3600,
},
});
if (!response.ok) {
throw new Error("Failed to fetch pages for sitemap");
}
const data = (await response.json()) as CmsResponse;
return data.pages.map((page) => ({
url: new URL(`/pages/${page.slug}`, siteUrl).toString(),
lastModified: new Date(page.updatedAt),
changeFrequency: "weekly",
priority: 0.7,
}));
}У файлі .env.local потрібно вказати адресу сайту та API:
SITE_URL=https://example.com
CMS_URL=https://cms.example.comПісля запуску Next.js сформує sitemap за адресою:
https://example.com/sitemap.xmlУ прикладі використано:
next: {
revalidate: 3600,
}Це означає, що результат запиту може кешуватися протягом 3600 секунд. Протягом цього часу Next.js не буде виконувати зовнішній запит для кожного звернення до sitemap.
Це важливо, оскільки пошукові роботи можуть часто перевіряти sitemap, а дані в ній зазвичай не потрібно оновлювати щосекунди.
Значення revalidate слід обирати відповідно до частоти змін контенту:
300 — для часто оновлюваного каталогу;
3600 — для більшості сайтів;
86400 — для контенту, який змінюється рідко.
Якщо sitemap повинна формуватися під час кожного запиту, можна не використовувати кешування:
const response = await fetch(`${process.env.CMS_URL}/api/pages`, {
cache: "no-store",
});Однак для великої кількості сторінок це може створювати зайве навантаження на API або базу даних.
URL у sitemap має бути абсолютним:
https://example.com/pages/aboutВідносний URL:
/pages/aboutдля sitemap не підходить.
Для безпечного формування адреси зручно використовувати URL:
const url = new URL(`/pages/${page.slug}`, siteUrl).toString();Це краще, ніж конкатенація рядків:
const url = `${siteUrl}/pages/${page.slug}`;За допомогою URL не виникають проблеми з подвійними слешами, якщо значення SITE_URL задане з кінцевим /.
Часто сайт містить кілька типів динамічних сторінок: статті, товари та категорії. У такому разі їх можна об'єднати в одному sitemap:
import type { MetadataRoute } from "next";
type Article = {
slug: string;
updatedAt: string;
};
type Product = {
slug: string;
updatedAt: string;
};
type ApiResponse = {
articles: Article[];
products: Product[];
};
const siteUrl = process.env.SITE_URL;
if (!siteUrl) {
throw new Error("SITE_URL environment variable is not defined");
}
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const response = await fetch(`${process.env.CMS_URL}/api/sitemap-data`, {
next: {
revalidate: 3600,
},
});
if (!response.ok) {
throw new Error("Failed to fetch sitemap data");
}
const data = (await response.json()) as ApiResponse;
const articleUrls: MetadataRoute.Sitemap = data.articles.map((article) => ({
url: new URL(`/articles/${article.slug}`, siteUrl).toString(),
lastModified: new Date(article.updatedAt),
changeFrequency: "monthly",
priority: 0.8,
}));
const productUrls: MetadataRoute.Sitemap = data.products.map((product) => ({
url: new URL(`/products/${product.slug}`, siteUrl).toString(),
lastModified: new Date(product.updatedAt),
changeFrequency: "weekly",
priority: 0.6,
}));
return [
{
url: siteUrl,
lastModified: new Date(),
changeFrequency: "daily",
priority: 1,
},
...articleUrls,
...productUrls,
];
}Головну сторінку зазвичай додають окремо, оскільки вона не надходить із динамічного API.
Один XML sitemap має обмеження:
не більше 50 000 URL;
розмір файлу — не більше 50 МБ без стиснення.
Якщо сайт містить більше сторінок, потрібно створити кілька sitemap-файлів та індекс sitemap. Next.js підтримує це через generateSitemaps.
Наприклад, нехай у базі є 120 000 товарів. Їх можна розділити на частини по 50 000:
sitemap/0.xml
sitemap/1.xml
sitemap/2.xmlФайл app/product-sitemap.xml/route.ts для такого підходу не потрібен. У Next.js можна використати спеціальний файл:
app/product-sitemap.tsПриклад:
import type { MetadataRoute } from "next";
type Product = {
slug: string;
updatedAt: string;
};
const siteUrl = process.env.SITE_URL;
const productsPerSitemap = 50_000;
if (!siteUrl) {
throw new Error("SITE_URL environment variable is not defined");
}
async function getProductCount(): Promise<number> {
const response = await fetch(`${process.env.CMS_URL}/api/products/count`, {
next: {
revalidate: 3600,
},
});
if (!response.ok) {
throw new Error("Failed to fetch product count");
}
const data = (await response.json()) as { count: number };
return data.count;
}
async function getProducts(
offset: number,
limit: number,
): Promise<Product[]> {
const params = new URLSearchParams({
offset: String(offset),
limit: String(limit),
});
const response = await fetch(
`${process.env.CMS_URL}/api/products?${params.toString()}`,
{
next: {
revalidate: 3600,
},
},
);
if (!response.ok) {
throw new Error("Failed to fetch products");
}
const data = (await response.json()) as { products: Product[] };
return data.products;
}
export async function generateSitemaps() {
const count = await getProductCount();
const sitemapCount = Math.ceil(count / productsPerSitemap);
return Array.from({ length: sitemapCount }, (_, id) => ({
id,
}));
}
export default async function sitemap({
id,
}: {
id: number;
}): Promise<MetadataRoute.Sitemap> {
const products = await getProducts(
id * productsPerSitemap,
productsPerSitemap,
);
return products.map((product) => ({
url: new URL(`/products/${product.slug}`, siteUrl).toString(),
lastModified: new Date(product.updatedAt),
changeFrequency: "weekly",
priority: 0.6,
}));
}Для такого файлу Next.js генерує окремі sitemap на основі ідентифікатора. Точний формат URL залежить від версії Next.js, але концепція залишається однаковою: generateSitemaps визначає кількість частин, а функція sitemap завантажує дані лише для конкретної частини.
На практиці краще використовувати запас за лімітом, наприклад обмежити одну частину 40 000 URL, щоб залишити місце для можливих змін.
До sitemap варто включати:
публічні сторінки;
сторінки, доступні без авторизації;
канонічні URL;
сторінки, які потрібно індексувати.
Не потрібно включати:
сторінки адміністративної панелі;
сторінки входу та реєстрації;
результати пошуку;
URL із параметрами сортування;
дублікати сторінок;
сторінки з помилкою 404;
приватний або чернетковий контент.
Фільтрувати такі сторінки найкраще ще на рівні запиту до API:
const response = await fetch(
`${process.env.CMS_URL}/api/pages?status=published`,
{
next: {
revalidate: 3600,
},
},
);Це ефективніше, ніж завантажувати всі записи, а потім видаляти непотрібні у Next.js.
Після запуску застосунку перевірте:
http://localhost:3000/sitemap.xmlУ відповіді має бути XML приблизно такого вигляду:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/pages/about</loc>
<lastmod>2026-01-15T10:00:00.000Z</lastmod>
</url>
</urlset>Перевірте також:
Усі URL використовують правильний домен.
У sitemap немає сторінок із помилкою 404.
Дати lastModified мають коректний формат.
Чернетки та приватні сторінки не потрапили до результату.
Кількість URL не перевищує обмеження одного sitemap.
API доступний під час генерації sitemap.
Неправильно:
{
url: `/articles/${article.slug}`,
}Правильно:
{
url: new URL(`/articles/${article.slug}`, siteUrl).toString(),
}Якщо SITE_URL не задана, застосунок може генерувати некоректні адреси або завершитися з помилкою.
Значення має містити протокол:
SITE_URL=https://example.comа не:
SITE_URL=example.comЯкщо API повертає опубліковані, чернеткові та видалені записи разом, sitemap може містити недоступні сторінки. Фільтруйте дані на стороні API або бази даних.
Для великого каталогу запит, який повертає сотні тисяч записів одночасно, може:
використати забагато пам'яті;
перевищити час виконання;
перевищити обмеження sitemap;
створити значне навантаження на API.
Використовуйте пагінацію та generateSitemaps, якщо кількість URL наближається до ліміту.
Генерація sitemap під час кожного запиту може бути неефективною. Якщо контент змінюється раз на годину, немає потреби щоразу звертатися до бази даних.
priority як гарантії позиціїПоле priority лише повідомляє пошуковій системі про відносну важливість URL. Воно не гарантує вищу позицію в результатах пошуку.
Динамічна sitemap у Next.js створюється у файлі app/sitemap.ts.
Функція sitemap може асинхронно отримувати сторінки з API або бази даних.
URL у sitemap повинні бути абсолютними.
revalidate допомагає не виконувати запит до джерела даних при кожному зверненні.
До sitemap потрібно додавати лише публічні канонічні сторінки.
Один sitemap може містити максимум 50 000 URL.
Для дуже великих сайтів використовуйте generateSitemaps і розділяйте URL на кілька файлів.
Після реалізації перевірте /sitemap.xml та переконайтеся, що всі адреси доступні й коректні.