El App Router genera ambos a partir de código tipado: se acabó el XML estático caducado:
// 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", 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",
};
}
Detalles que importan:
lastModifieddebe ser la fecha real de actualización del contenido, nonew Date()en cada build: mentir sobre la frescura enseña a Google a ignorar el campo.- ¿Más de 50 000 URL? Use
generateSitemaps()para dividir en varios archivos. - No bloquee CSS/JS en robots.txt: Google los necesita para renderizar la página.
- Un Disallow en
robots.txtno saca páginas del índice: solo detiene el rastreo. Para que una página no aparezca en los resultados, usenoindexmediante los metadatos (robots: { index: false }) y no la bloquee en robots.txt (una página bloqueada no se puede leer, así que nadie verá el noindex).
Checklist:
-
sitemap.tscontiene solo direcciones canónicas e indexables con estado 200 -
lastModifiedrefleja cambios reales - Sitemap enviado a Google Search Console y Bing Webmaster Tools
- noindex frente a robots.txt usados correctamente (resuelven problemas distintos)
