Я бачив цей патерн у щонайменше п'яти Next.js кодових базах:
// app/sitemap.ts
export default function sitemap() {
return pages.map((page) => ({
url: `https://example.com${page.path}`,
lastModified: new Date(), // ← Баг
}))
}
При кожному білді кожна сторінка отримує сьогоднішню дату як lastModified. Деплоїш у понеділок — Google думає, що всі 50 сторінок змінились у понеділок. Деплоїш у вівторок — Google думає, що всі 50 сторінок змінились у вівторок.
Це не просто неправильно — це активно шкодить твоєму SEO.
Чому це важливо
Google використовує lastmod як сигнал пріоритету сканування. Коли ти кажеш Google, що сторінка змінилась, він пріоритизує її повторне сканування. Якщо кожна сторінка стверджує, що змінилась при кожному деплої, Google навчається, що твої значення lastmod ненадійні, і починає їх ігнорувати.
З документації Google: «Ми ігноруємо значення <lastmod>, які явно неточні (наприклад, якщо ви встановлюєте всі дати на поточну дату).»
Щойно Google перестає довіряти твоєму lastmod, ти втрачаєш можливість сигналізувати, які сторінки реально змінились. Справжнє оновлення контенту — новий пост у блозі, оновлена сторінка цін — отримує той самий пріоритет сканування, що й усе інше.
Правильний підхід
lastmod має відображати, коли контент тієї сторінки реально змінився. Для різних типів сторінок:
Статичні сторінки (about, контакти, послуги)
Вони змінюються рідко. Встанови lastmod на дату, коли ти їх востаннє редагував, як статичний рядок:
{
url: 'https://example.com/about',
lastModified: '2026-06-15', // Коли ти реально востаннє редагував цю сторінку
}
Або краще: виведи з git-історії файлу:
import { execSync } from 'child_process'
function getLastModified(filePath: string): string {
const date = execSync(
`git log -1 --format=%cI -- ${filePath}`
).toString().trim()
return date || new Date().toISOString()
}
// В sitemap.ts
{
url: 'https://example.com/about',
lastModified: getLastModified('app/about/page.tsx'),
}
Це дає реальну дату останнього коміту файлу — точно й автоматично.
Пости в блозі
У постів є поле date у frontmatter. Використовуй його:
const posts = getAllPosts('uk')
const blogEntries = posts.map((post) => ({
url: `https://example.com/uk/blog/${post.slug}`,
lastModified: post.date, // З frontmatter
}))
Якщо оновлюєш пост після початкової публікації, додай поле lastUpdated у frontmatter і використовуй його замість:
lastModified: post.lastUpdated || post.date,
Динамічні сторінки (товари інтернет-магазину, контент, згенерований користувачами)
Для сторінок, підкріплених базою даних, зберігай мітку часу updatedAt і використовуй її:
const products = await db.products.findMany({
select: { slug: true, updatedAt: true },
})
const productEntries = products.map((product) => ({
url: `https://example.com/products/${product.slug}`,
lastModified: product.updatedAt,
}))
Сторінки, які не можна датувати
Для сторінок, де ти справді не знаєш, коли контент змінився востаннє: взагалі пропусти lastmod. Відсутній lastmod кращий за неправильний. Google використає власні дані сканування для визначення свіжості.
{
url: 'https://example.com/some-page',
// Без lastModified — і це нормально
}
Повний патерн sitemap
Ось патерн sitemap, який я використовую на клієнтських проєктах:
// app/sitemap.ts
import { getAllPosts } from '@/lib/blog'
export default function sitemap() {
const staticPages = [
{ url: 'https://example.com', lastModified: '2026-07-01' },
{ url: 'https://example.com/about', lastModified: '2026-06-15' },
{ url: 'https://example.com/services', lastModified: '2026-07-10' },
// Оновлюй ці дати, коли редагуєш сторінки
]
const posts = getAllPosts('uk')
const blogPages = posts.map((post) => ({
url: `https://example.com/uk/blog/${post.slug}`,
lastModified: post.date,
}))
return [...staticPages, ...blogPages]
}
Просто. Кожна сторінка має точну дату. Жодного new Date().
Пастка деплою
Причина, чому new Date() настільки поширений: це те, що ти б написав, якщо думаєш «сторінка щойно зібрана, значить щойно змінена». Але білд і зміна — різні речі.
Деплой перезбирає кожну сторінку. Але це не означає, що контент кожної сторінки змінився. HTML може бути побайтово ідентичним учорашньому білду. lastmod має відстежувати зміни контенту, а не мітки часу білдів.
Як перевірити
Після деплою отримай свій sitemap і перевір дати:
curl -s https://yoursite.com/sitemap.xml | head -30
Якщо кожен <lastmod> показує сьогоднішню дату — у тебе баг. Якщо дати різняться (деякі старі, деякі свіжі) — все правильно.
Я впіймав саме цей баг на клієнтському проєкті, де sitemap повертав HTML замість XML. Баг lastmod тонший — sitemap працює, він просто бреше Google.
Виправлення в один рядок
Якщо зараз використовуєш new Date() і немає часу на повне виправлення: просто видали lastModified з записів, де немає точної дати.
// До (неправильно)
{ url: '...', lastModified: new Date() }
// Після (правильно, якщо не знаєш реальну дату)
{ url: '...' }
Пропустити lastmod краще, ніж брехати. Google використовує власну історію сканування як запасний варіант, що точніше за мітку часу білду.
Хочеш, щоб технічне SEO було правильним від першого дня? Напиши — я ловлю ці тихі SEO-баги до того, як вони коштуватимуть тобі crawl budget.