Паттерн · TypeScript
Динамический sitemap.xml в Next.js (app/sitemap.ts)
Единый sitemap со статическими и контентными URL; согласование с collectIndexableUrls и каноникалом.
Уровень: среднийОценка времени: ~40 мин
Генератор sitemap собирает стабильные пути и slug’и из CMS/файлов; lastmod берёт из даты контента, если она есть.
- Каждый URL в sitemap должен совпадать с каноническим
- не включайте noindex-страницы
- при большом каталоге используйте разбиение или sitemap-индекс
app/sitemap.tsв App Router возвращает массив URL. Соберите в один проход статические маршруты и динамические slug’и (блог, библиотека, кейсы).
Код
Ниже — фактическая логика из app/sitemap.ts этого проекта (статические пути, кейсы, посты, паттерны библиотеки):
import type { MetadataRoute } from 'next'
import { caseSlugOrder } from '@/data/cases'
import { SITE_STATIC_PATHS } from '@/data/site-static-paths'
import { getAllPosts, getPostModifiedDate } from '@/lib/blog'
import { getAllLibraryEntries, getLibraryModifiedDate } from '@/lib/library'
import { getStaticPagesLastModified } from '@/lib/site-static-lastmod'
import { getSiteUrl } from '@/lib/site'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const site = getSiteUrl()
const base = site.replace(/\/$/, '')
const staticLastMod = getStaticPagesLastModified()
const posts = await getAllPosts()
const library = await getAllLibraryEntries()
const entries: MetadataRoute.Sitemap = [
...SITE_STATIC_PATHS.map((path) => ({
url: `${base}${path}`,
lastModified: staticLastMod,
changeFrequency: 'weekly' as const,
priority: path === '/' ? 1 : 0.7,
})),
...caseSlugOrder.map((slug) => ({
url: `${base}/kejsy/${slug}/`,
lastModified: staticLastMod,
changeFrequency: 'monthly' as const,
priority: 0.65,
})),
...posts.map((p) => ({
url: `${base}/blog/${p.slug}/`,
lastModified: new Date(getPostModifiedDate(p)),
changeFrequency: 'monthly' as const,
priority: 0.6,
})),
...library.map((e) => ({
url: `${base}/biblioteka/${e.slug}/`,
lastModified: new Date(getLibraryModifiedDate(e)),
changeFrequency: 'monthly' as const,
priority: 0.62,
})),
]
return entries
}
Проверка
Что именно проверить:
-
Файл открывается. В браузере откройте
https://ваш-домен/sitemap.xml— должна загрузиться XML-страница без HTML-ошибки 404. Если видите приложение Next вместо XML, проверьте, что вapp/естьsitemap.ts(илиsitemap.js) и что он экспортирует функцию по умолчанию. -
Search Console. В Google Search Console раздел «Файлы Sitemap» → добавьте URL вида
https://ваш-домен/sitemap.xml. Статус «Не получено» или ошибки парсинга значит, что файл недоступен роботу или XML некорректен. -
Совпадение с canonical. Выберите несколько URL из файла, откройте эти страницы, в исходном коде найдите
<link rel="canonical" ...>. Адрес должен совпадать с URL в sitemap (включая завершающий слэш/, если именно так задан каноникал на сайте). -
Согласованность с noindex. Страницы с
robots: { index: false }или<meta name="robots" content="noindex">обычно не включают в sitemap — иначе вы даёте поиску противоречивые сигналы.
Первоисточники
Другие паттерны
- Индексация
robots.txt и метаданные robots в Next.js App Router
Статический robots через app/robots.ts и точечный noindex через generateMetadata.
Открыть паттерн - Next.js
Канонический URL и метаданные страницы в Next.js App Router
Паттерн generateMetadata с canonical и Open Graph — основа SEO-гигиены.
Открыть паттерн
Материалы блога
- SEO
Мультиязычность для B2B: почему виджет-переводчик не равно экспорт в поиск
Переводчик на сайте и индексируемые языковые версии — разные задачи. Разбираем URL, мета, доверие и лестницу зрелости для поставок и экспорта без лишней магии.
Читать статью - SEO
SEO-архитектура: почему сайт не продает без структуры
Разбираем, как правильная структура сайта влияет на продажи и позиции в поиске. Почему дизайн вторичен, а семантика первична.
Читать статью
Нужно внедрить под ваш домен и стек?
Короткая форма: имя, телефон и сайт. После отправки — ответ с порядком работ и ориентиром по этапам; задачу уточняют при контакте.