Next.js
Robots, sitemap y manifest
Explica las convenciones robots, sitemap y manifest, qué controlan, cómo generarlas por entorno y qué URLs, fechas e iconos deben incluir.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Next.js
Explica las convenciones robots, sitemap y manifest, qué controlan, cómo generarlas por entorno y qué URLs, fechas e iconos deben incluir.
robots, sitemap y manifest son convenciones distintas: robots comunica reglas de rastreo, sitemap enumera URLs que quieres facilitar a buscadores y manifest describe cómo el navegador puede presentar una aplicación instalable. Ninguno protege contenido privado ni garantiza indexación o instalación.
app/robots.txt o app/robots.ts
app/sitemap.xml o app/sitemap.ts
app/manifest.json o app/manifest.tsLas funciones TypeScript retornan tipos MetadataRoute y actúan como Route Handlers especiales. Normalmente se cachean salvo que dependan de Dynamic APIs o configuración dinámica.
Estático:
User-agent: *
Allow: /
Disallow: /dashboard/
Sitemap: https://nicoo.dev/sitemap.xmlGenerado:
import type { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
const isProduction = process.env.APP_ENV === "production";
if (!isProduction) {
return {
rules: {
userAgent: "*",
disallow: "/",
},
};
}
return {
rules: [
{
userAgent: "*",
allow: "/",
disallow: ["/dashboard/", "/api/", "/preview/"],
},
],
sitemap: "https://nicoo.dev/sitemap.xml",
host: "https://nicoo.dev",
};
}Comunica una preferencia a crawlers que respetan el estándar.
Contenido privado necesita authentication; contenido que no debe indexarse usa noindex y puede requerir removal tools.
Si CSS, JavaScript o imágenes necesarias están bloqueadas, el crawler puede interpretar mal la página. Evita reglas amplias como Disallow: /_next/.
Preview deployments no deberían competir con production:
preview
→ robots disallow all
→ page metadata noindex
→ optional access protection
production
→ allow intended public routesNo bases el dominio canónico en Host sin validar. Usa configuración del entorno.
import type { MetadataRoute } from "next";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const projects = await listPublishedProjects();
return [
{
url: "https://nicoo.dev",
lastModified: new Date("2026-07-20"),
changeFrequency: "monthly",
priority: 1,
},
{
url: "https://nicoo.dev/portfolio",
lastModified: new Date("2026-07-20"),
changeFrequency: "weekly",
priority: 0.8,
},
...projects.map((project) => ({
url: `https://nicoo.dev/portfolio/${project.slug}`,
lastModified: project.updatedAt,
changeFrequency: "monthly" as const,
priority: 0.7,
})),
];
}Incluye URLs:
No incluyas:
404 o recursos eliminados.noindex.El sitemap es inventario, no una forma de obligar indexación.
Debe reflejar una modificación significativa del contenido, no new Date() en cada request. Fechas falsas hacen que la señal pierda valor y pueden provocar rastreo inútil.
Para contenido CMS usa updatedAt. Para páginas estáticas, una fecha de release o Git puede ser más honesta.
Son señales opcionales que buscadores pueden ignorar. priority: 1 no coloca la URL primero en Google; solo compara URLs dentro de tu propio sitemap según consumidores que la consideren.
No gastes esfuerzo ajustando decimales sin una estrategia de contenido/rastreo.
Para grandes volúmenes usa generateSitemaps:
export async function generateSitemaps() {
const total = await countProducts();
const size = 50_000;
return Array.from(
{ length: Math.ceil(total / size) },
(_, id) => ({ id }),
);
}
export default async function sitemap({
id,
}: {
id: Promise<string>;
}): Promise<MetadataRoute.Sitemap> {
const page = Number(await id);
const products = await listProductsForSitemap(page);
return products.map(toSitemapEntry);
}En Next.js 16, id llega como Promise. Cada sitemap debe respetar límites del protocolo en número y tamaño.
Un catálogo cambia por eventos. Puedes cachear la consulta y etiquetarla:
async function getSitemapProducts() {
"use cache";
cacheLife("hours");
cacheTag("sitemap:products");
return listPublishedProducts();
}Al publicar/despublicar, invalida la tag. No regeneres millones de URLs en cada request.
Para sitios multilingües puedes incluir alternates por entrada o sitemaps separados. Las URLs deben coincidir con hreflang/canonicals y responder en el idioma declarado.
No generes locales que redirigen a uno solo.
import type { MetadataRoute } from "next";
export default function manifest(): MetadataRoute.Manifest {
return {
name: "Nicoo Portfolio",
short_name: "Nicoo",
description: "Portafolio y wiki de desarrollo de Nicolás Garzón.",
start_url: "/",
display: "standalone",
background_color: "#09090b",
theme_color: "#09090b",
icons: [
{
src: "/icons/icon-192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "/icons/icon-512.png",
sizes: "512x512",
type: "image/png",
purpose: "any maskable",
},
],
};
}El manifest mejora identidad e instalación, pero una PWA completa también necesita:
No añadas service worker por inercia; puede servir HTML/chunks incompatibles después de deploy.
start_url determina dónde abre la aplicación. No incluyas tokens ni parámetros personales. scope limita qué URLs pertenecen a la experiencia instalada.
Para multi-tenant, un manifest por request puede crear problemas de caché; define si realmente necesitas personalización.
Un icono maskable incluye zona segura para que Android lo recorte en distintas formas. No marques un icono normal como maskable sin diseñarlo; podría perder contenido.
Robots:
Sitemap:
Manifest:
/robots.txt, /sitemap.xml, /manifest.webmanifest.Los atacantes pueden ignorarlo y además revela paths.
El crawler no puede leer la directiva.
Todas las URLs parecen cambiar siempre.
Crea duplicados y crawl space.
Expone URLs no publicadas.
Falta service worker/estrategia offline.
Instalación inconsistente.
Disallow no protege /dashboard?noindex?priority?id en generateSitemaps de Next.js 16?id como Promise que debe esperarse.Open Graph images y generación dinámica transforma metadata de una URL en previews visuales consistentes y cacheables.