App Router generuje jedno i drugie z typowanego kodu — koniec z przeterminowanym statycznym XML-em:
// app/sitemap.ts
import type { MetadataRoute } from "next";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getPosts();
const postEntries = posts.map((post) => ({
url: `https://example.com/en/blog/${post.slug}`,
lastModified: post.updatedAt,
alternates: {
languages: {
pl: `https://example.com/pl/blog/${post.plSlug}`,
ru: `https://example.com/ru/blog/${post.ruSlug}`,
},
},
}));
return [
{ url: "https://example.com", lastModified: new Date(), priority: 1 },
...postEntries,
];
}
// app/robots.ts
import type { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
return {
rules: [{ userAgent: "*", allow: "/", disallow: ["/api/", "/admin/"] }],
sitemap: "https://example.com/sitemap.xml",
};
}
Detale, które mają znaczenie:
lastModifiedpowinien być prawdziwą datą aktualizacji treści, a nienew Date()przy każdym buildzie — kłamanie o świeżości uczy Google ignorować to pole.- Ponad 50 000 URL-i? Użyj
generateSitemaps(), żeby podzielić na kilka plików. - Nie blokuj CSS/JS w robots.txt — Google potrzebuje ich do wyrenderowania strony.
- Disallow w
robots.txtnie usuwa stron z indeksu — tylko zatrzymuje crawlowanie. Żeby strona nie trafiała do wyników, użyjnoindexprzez metadane (robots: { index: false }) i nie blokuj jej w robots.txt (zablokowanej strony nie da się odczytać, więc noindexu nikt nie zobaczy).
Checklista:
-
sitemap.tszawiera tylko kanoniczne, indeksowalne adresy ze statusem 200 -
lastModifiedodzwierciedla prawdziwe zmiany - Sitemap zgłoszona w Google Search Console i Bing Webmaster Tools
- noindex vs robots.txt używane właściwie (rozwiązują różne problemy)