wandres.dev
RSS, SITEMAP Y DATOS · feeds y datos derivados

robots.txt, canonical y Open Graph derivados del contenido

Cerrar el nivel de datos derivados con las señales para máquinas: generar robots.txt estático o por endpoint con su línea Sitemap, emitir una URL canonical por página desde Astro.site y Astro.url, derivar las etiquetas Open Graph del frontmatter del contenido con imágenes absolutas, y la regla de una única fuente de verdad.

⏱ 16 min

Las últimas proyecciones del contenido no son para humanos sino para máquinas: el robots.txt que guía a los rastreadores, la URL canonical que le dice al buscador cuál es la dirección verdadera de una página, y las etiquetas Open Graph que las redes sociales leen para dibujar una tarjeta. Ninguna se escribe a mano entrada por entrada; todas se derivan del contenido y de la configuración que ya tienes. Esta lección corona el nivel con la regla que lo unifica: los datos derivados se computan, no se copian, porque lo que se computa se rehace y no miente, y lo que se copia, tarde o temprano, sí.

🎯 Al terminar esta lección sabrás
  • Servir robots.txt estático o generado, con su línea Sitemap.
  • Emitir una URL canonical por página desde Astro.site y Astro.url.
  • Derivar las etiquetas Open Graph del frontmatter del contenido.
  • Aplicar la regla de una única fuente de verdad a los datos derivados.

robots.txt: estático o generado

Lo más simple es un fichero public/robots.txt, que Astro sirve tal cual en /robots.txt. Ahí declaras qué rastreadores pueden entrar con User-agent, qué rutas evitar con Disallow, y —lo más útil— una línea Sitemap con la URL absoluta de tu mapa. El problema del fichero estático es que no conoce tu site: la URL del sitemap queda escrita a mano, y si cambias de dominio, se queda mintiendo.

La alternativa es generarlo desde un endpoint src/pages/robots.txt.ts que devuelve texto plano y lee context.site. Así la URL del sitemap se deriva de la configuración en lugar de copiarse, y sigue siendo verdadera aunque cambies de dominio.

// src/pages/robots.txt.ts
import type { APIRoute } from 'astro';

const cuerpo = (site: string) => `
User-agent: *
Allow: /
Disallow: /admin/

Sitemap: ${new URL('sitemap-index.xml', site).href}
`.trim();

export const GET: APIRoute = ({ site }) => {
  return new Response(cuerpo(site!.href), {
    headers: { 'Content-Type': 'text/plain' },
  });
};

Como la ruta de endpoint gana, no necesitas el fichero estático: la línea Sitemap se construye con new URL contra site, y hay una sola fuente de verdad para el dominio. Cambiar site en la configuración actualiza el robots.txt, el sitemap y todo lo demás a la vez.

Conviene recordar qué es y qué no es el robots.txt: una sugerencia que los rastreadores educados respetan, no un muro. Disallow esconde una ruta del índice, pero no la protege —cualquiera puede leer el fichero y visitar justo lo que ocultas—, así que nunca es el sitio para secretos. Su valor real es orientar el rastreo hacia lo que importa y, sobre todo, anunciar el sitemap para que el buscador encuentre el mapa completo sin depender de tropezar con los enlaces.

canonical: una URL verdadera por página

Un buscador puede llegar a la misma página por varias direcciones —con barra final y sin ella, con parámetros de campaña, por http y por https— y tratarlas como contenido duplicado, repartiendo la autoridad entre copias. La etiqueta <link rel="canonical"> corta ese nudo nombrando la única dirección verdadera. Se construye combinando Astro.site, tu dominio configurado, con Astro.url.pathname, la ruta actual, en un solo new URL.

---
const canonical = new URL(Astro.url.pathname, Astro.site);
---
<link rel="canonical" href={canonical.href} />

Lo importante es derivar la URL en vez de escribirla. Una canonical a mano en cada página es una copia que se desincroniza; una derivada de Astro.site y Astro.url es correcta por construcción en cualquier entorno —desarrollo, pruebas, producción— porque se recalcula de la fuente. Colocar esta línea en el componente de cabecera que comparten todas las páginas garantiza que ninguna se quede sin ella.

Un matiz fácil de pasar por alto: la canonical debe casar con la forma real de tus URLs —con barra final o sin ella—, la misma que fija trailingSlash. Una canonical que apunta a una variante que tu sitio no sirve reparte de nuevo la autoridad que pretendía unificar. Derivarla de Astro.url en lugar de componerla a mano evita justo ese desajuste, porque toma la ruta tal como el sitio la resuelve, no como tú crees que se resuelve.

ℹ️
Astro.site y Astro.url son piezas distintas

Astro.site es tu dominio, el que fijaste en la configuración; Astro.url es la dirección de la petición actual, con su ruta y sus parámetros. La canonical nace de casar ambas: el dominio de la configuración más la ruta de la página, sin el dominio escrito a mano en ninguna plantilla. Esa separación es la que permite que el mismo componente de cabecera produzca la dirección correcta en cada entorno sin tocar una línea.

Open Graph derivado del frontmatter

Cuando alguien comparte tu página en una red social, un rastreador lee las etiquetas Open Graph del <head> para componer la tarjeta: título, descripción, imagen. Lo natural es derivarlas del mismo frontmatter que ya gobierna la página —og:title del title, og:description de la description, og:url de la canonical— en el componente de cabecera que recibe esas props. Una sola plantilla, alimentada por el contenido, y todas las páginas quedan cubiertas.

---
interface Props {
  title: string;
  description: string;
  image?: string;
}
const { title, description, image = '/og-por-defecto.png' } = Astro.props;
const canonical = new URL(Astro.url.pathname, Astro.site);
const ogImage = new URL(image, Astro.site);
---
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonical.href} />
<meta property="og:image" content={ogImage.href} />
<meta property="og:type" content="website" />

Las etiquetas twitter:card y sus hermanas replican esta información para la tarjeta de X, y a menudo basta con declarar twitter:card y dejar que reutilice las og:. Conviene que la imagen sea de proporción ancha —1200 por 630 es el estándar— para que ninguna red la recorte. Un paso más allá, los datos estructurados en JSON-LD, un bloque <script type="application/ld+json"> con el artículo, su autor y su fecha, le dan al buscador la misma verdad en un formato que entiende para los resultados enriquecidos. Todo, otra vez, derivado del mismo frontmatter: una fuente, muchas etiquetas.

---
const jsonLd = {
  '@context': 'https://schema.org',
  '@type': 'Article',
  headline: title,
  datePublished: Astro.props.date,
  author: { '@type': 'Person', name: Astro.props.author },
};
---
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
🤖

robots.txt

Guía a los rastreadores y anuncia el sitemap. Generado desde un endpoint, su línea Sitemap se deriva de site.

🎯

canonical

Nombra la URL verdadera de cada página. Nace de Astro.site más Astro.url, nunca escrita a mano.

🖼️

Open Graph

La tarjeta social. Sus etiquetas salen del frontmatter, con la imagen resuelta a URL absoluta.

⚠️
Una og:image relativa rompe la vista previa

El error clásico de Open Graph es una og:image con ruta relativa. Los rastreadores de las redes no resuelven rutas relativas: necesitan una URL absoluta para descargar la imagen, y si no la tienen, la tarjeta sale sin foto. Resuélvela siempre contra Astro.site con new URL(image, Astro.site), igual que la canonical. La misma regla vale para cualquier recurso que un consumidor externo deba encontrar por su cuenta: si sale de tu sitio, va absoluto.

flowchart LR
FM[frontmatter del contenido] --> HEAD[componente de cabecera]
SITE[config site] --> HEAD
HEAD --> CAN[canonical]
HEAD --> OG[etiquetas open graph]
SITE --> ROB[robots txt y sitemap]
CAN --> M[buscadores y redes]
OG --> M
ROB --> M
style FM fill:#89b4fa,color:#11111b
style SITE fill:#f9e2af,color:#11111b
style M fill:#a6e3a1,color:#11111b
Autor una vez, deriva todo lo demás

Todo lo que has construido en este nivel —el feed RSS, el sitemap, los endpoints JSON, los datos y assets globados, el robots, la canonical, las etiquetas Open Graph— es la misma cosa vista siete veces: datos derivados, proyecciones de una única fuente hacia la representación que cada consumidor necesita. La fuente es tu contenido tipado más un puñado de configuración, sobre todo site. Y el principio que lo unifica, el que de verdad quiero que te lleves, es la única fuente de verdad: nunca escribas dos veces el mismo hecho, nunca fijes a mano una URL que site ya conoce, nunca copies un título en una etiqueta Open Graph cuando el frontmatter ya lo tiene. Un dato derivado debe computarse, no autorarse, porque una copia autorada empieza a divergir de su original en el instante en que uno de los dos cambia, y a partir de ahí miente en silencio hasta que alguien lo descubre roto; una proyección computada se rehace de la fuente en cada build y coincide con ella por construcción, sin disciplina ni memoria de por medio. Por eso el mismo contenido se convierte en una página HTML para humanos, un feed XML para lectores, un endpoint JSON para programas, un sitemap para rastreadores, una tarjeta Open Graph para redes y una canonical para buscadores: seis audiencias, una sola verdad, ninguna copia que mantener. Cuando interiorizas que un sitio web no es una pila de páginas sino un grafo de contenido proyectado en tantas representaciones como consumidores tenga, dejas de mantener cada representación a mano y empiezas a derivarlas todas del único lugar donde el hecho de verdad vive. Ese es el nivel entero en una frase, y la disciplina que separa un sitio que envejece con gracia de uno que se pudre por dentro: autor una vez, deriva todo lo demás.

⚔️ Deriva todas tus señales
  1. Genera robots.txt desde un endpoint con la línea Sitemap construida con new URL contra context.site.
  2. Añade un <link rel="canonical"> en la cabecera derivado de Astro.site y Astro.url.
  3. Emite las etiquetas Open Graph en el componente de cabecera desde el frontmatter, resolviendo og:image a una URL absoluta contra Astro.site.
  4. Cambia el dominio en la configuración site y razona por qué el feed, el sitemap, el robots, la canonical y las tarjetas se actualizan solos sin tocar una sola página.