Astro se ha convertido en una de las opciones favoritas para webs de contenido que quieren cargar rápido y posicionar bien, pero que un sitio sea estático no garantiza que su SEO esté resuelto. Esta guía recorre todo lo necesario para optimizar un proyecto en Astro: configuración base, metaetiquetas, datos estructurados, sitemaps, rendimiento, sitios multilingües, redirecciones y preparación para agentes de IA. Parte de la guía de SEO para Astro de Joost de Valk, fundador de Yoast, y la amplía con configuración práctica, ejemplos de código propios y algunos matices que conviene tener en cuenta en 2026.
Las claves de SEO en Astro en 20 segundos
- Configura
siteytrailingSlashantes que nada: de ellos dependen canónicas, sitemap y enlaces. - Centraliza metaetiquetas y JSON-LD en un solo componente.
- Valida el contenido en cada build con colecciones y Zod.
- llms.txt e IndexNow ayudan a agentes y a Bing, no a Google.
El enfoque de Joost de Valk tiene un punto de partida interesante: compara su primera guía de SEO para WordPress, de 2008, centrada en colocar palabras clave, con la forma en que funciona hoy la búsqueda. Los buscadores entienden temas y significado, y los sistemas de IA extraen párrafos concretos para citarlos. Por eso la estructura, los datos legibles por máquinas y la velocidad pesan tanto como el texto.
1. La configuración base que muchas guías pasan por alto
Antes de instalar ninguna integración conviene fijar tres opciones de astro.config.mjs. La primera es site, la URL definitiva del proyecto. Sin ella, integraciones como @astrojs/sitemap no generan el mapa del sitio y Astro.site no está disponible para construir URL absolutas en canónicas u Open Graph.
La segunda es trailingSlash, que acepta 'always', 'never' o 'ignore' (el valor por defecto). Dejarlo en 'ignore' suele acabar en enlaces internos mezclados, con y sin barra final, que generan redirecciones innecesarias o duplicados. La tercera, build.format, decide si cada página se genera como /ruta/index.html ('directory', por defecto) o como /ruta.html ('file'). Lo importante es que las tres opciones sean coherentes entre sí y con el servidor o CDN que sirve la web.
// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://www.ejemplo.com',
trailingSlash: 'always',
build: { format: 'directory' },
integrations: [sitemap()],
});
Con trailingSlash: 'always' y formato de directorio, todas las URL terminan en barra. Conviene que el enlazado interno, las canónicas y el sitemap sigan ese mismo patrón, y que el alojamiento redirija con un 301 la versión sin barra.
2. Un único componente para todas las metaetiquetas
La recomendación central de Joost de Valk es reunir en un solo componente el título, la descripción, la canónica, Open Graph, las tarjetas de X (antes Twitter), hreflang y el JSON-LD. Así se evita que cada plantilla implemente su propia versión y que aparezcan incoherencias. Él usa su paquete @jdevalk/astro-seo-graph y su componente <Seo>, que además aplica varias buenas prácticas por defecto:
- Elimina los parámetros de consulta de la canónica, para que una URL con UTM no se declare como página distinta.
- Añade
max-snippet:-1, max-image-preview:large, max-video-preview:-1a la etiqueta robots para permitir fragmentos e imágenes grandes en resultados. - Omite la canónica cuando la página es
noindex, siguiendo la recomendación de Google de no enviar señales contradictorias. - No duplica las etiquetas de X cuando coinciden con las de Open Graph, porque X usa estas como respaldo.
No es imprescindible usar ese paquete. Otras librerías conocidas, como astro-seo, cubren lo básico, y un componente propio sencillo resuelve la mayoría de los casos sin dependencias. Este es un ejemplo mínimo que aplica las mismas reglas:
---
// src/components/SeoHead.astro
const { title, description, image = '/og/default.jpg', noindex = false } = Astro.props;
// Astro.url.pathname no incluye la query: la canónica queda sin UTM
const canonical = new URL(Astro.url.pathname, Astro.site);
const ogImage = new URL(image, Astro.site);
---
<title>{title}</title>
<meta name="description" content={description} />
{noindex
? <meta name="robots" content="noindex, follow" />
: <>
<link rel="canonical" href={canonical} />
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
</>}
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonical} />
<meta property="og:image" content={ogImage} />
<meta name="twitter:card" content="summary_large_image" />
Imágenes para redes generadas en el build
Joost genera una imagen Open Graph para cada página en tiempo de compilación, con satori (que convierte HTML y CSS en SVG) y sharp (que la pasa a JPEG). Las sirve desde una ruta dinámica del tipo /og/[...slug].jpg e incorpora la imagen destacada cuando existe. Usa 1200 × 675 píxeles y JPEG, porque las redes sociales no siempre aceptan bien formatos modernos como WebP o AVIF. La medida más extendida sigue siendo 1200 × 630; cualquiera de las dos funciona mientras el texto importante quede en la zona central.
Validar el SEO en cada compilación
Una de las ideas más útiles de la guía original es convertir el SEO técnico en comprobaciones automáticas. La integración de Joost revisa en cada build que haya un único H1 por página, que no se repitan títulos ni descripciones, que el JSON-LD sea válido, que las imágenes tengan texto alternativo, que títulos (30 a 65 caracteres) y descripciones (70 a 200) estén dentro de rango y que los enlaces internos apunten a rutas que existen. Esta última comprobación detecta tanto erratas como fallos de barra final.
Aunque no se use su paquete, la lección es aplicable: cualquier error que pueda detectarse al compilar no debería llegar a producción. Un script que recorra la carpeta dist tras el build puede hacer buena parte de estas comprobaciones.
3. Datos estructurados: un grafo, no bloques sueltos
En lugar de pegar fragmentos independientes de JSON-LD, la guía propone un único @graph por página en el que cada entidad (WebSite, Organization o Person, WebPage, BlogPosting, BreadcrumbList, ImageObject) tiene un identificador @id y se enlaza con las demás. Así un buscador o un agente puede recorrer las relaciones: este artículo pertenece a este sitio, lo firma este autor y lo publica esta organización.
Joost añade también señales de confianza como publishingPrinciples, copyrightHolder o knowsAbout, y un SearchAction si el sitio tiene buscador. En Astro, el grafo se imprime con set:html:
---
const base = 'https://www.ejemplo.com/';
const url = new URL(Astro.url.pathname, Astro.site).href;
const graph = {
'@context': 'https://schema.org',
'@graph': [
{ '@type': 'Organization', '@id': base + '#organizacion', name: 'Ejemplo', logo: base + 'logo.png' },
{ '@type': 'WebSite', '@id': base + '#website', url: base, name: 'Ejemplo',
publisher: { '@id': base + '#organizacion' } },
{ '@type': 'BlogPosting', '@id': url + '#articulo', headline: title,
datePublished: publishDate, dateModified: updatedDate ?? publishDate,
isPartOf: { '@id': base + '#website' },
publisher: { '@id': base + '#organizacion' } },
],
};
---
<script type="application/ld+json" set:html={JSON.stringify(graph)} />
Conviene validar el resultado con la prueba de resultados enriquecidos de Google y con el validador de Schema.org, y revisar que todos los @id referenciados existan en el grafo.
4. Contenido: temas, estructura y colecciones validadas
La búsqueda actual funciona con representaciones vectoriales del significado, así que la posición exacta de una palabra clave importa menos que cubrir bien un tema. La guía de Joost insiste en escribir para personas y, a la vez, para sistemas que extraen fragmentos:
- Empezar cada párrafo con la idea principal, porque es lo que suelen citar los sistemas de IA.
- Una idea por párrafo y frases de menos de 20 palabras cuando sea posible.
- Conectores claros, como «porque», «sin embargo» o «por ejemplo».
- Eliminar muletillas que no aportan nada.
Astro permite además imponer reglas al propio contenido. Las colecciones de contenido validan el frontmatter de cada archivo con Zod, y si falta un campo o un título es demasiado largo, el build falla. Un esquema básico podría ser este:
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
schema: z.object({
title: z.string().min(5).max(70),
description: z.string().min(50).max(160),
publishDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
category: z.enum(['seo-tecnico', 'contenidos', 'analitica']),
noindex: z.boolean().default(false),
}),
});
export const collections = { blog };
Los límites concretos son una decisión editorial. El esquema de Joost usa entre 5 y 120 caracteres para el título y entre 15 y 160 para la descripción; lo relevante es que existan y que se apliquen automáticamente.
5. Arquitectura: una taxonomía, migas de pan y enlazado interno
Joost recomienda elegir entre categorías o etiquetas, no usar ambas. Dos taxonomías paralelas suelen generar páginas de archivo casi idénticas y navegación confusa, un problema muy habitual en sitios que vienen de WordPress. En su web solo usa categorías.
Las migas de pan deben existir tanto en el HTML como en el grafo, y cada elemento de la BreadcrumbList puede apuntar por @id a la entidad que representa. Para el enlazado interno, sugiere analizar el contenido como un grafo de conocimiento para descubrir oportunidades, pero decidir cada enlace de forma intencionada. Automatizar la detección tiene sentido; automatizar la inserción de enlaces, bastante menos.
6. Rendimiento: lo que Astro da hecho y lo que hay que ajustar
Astro parte con ventaja: por defecto genera HTML estático y no envía JavaScript al navegador salvo que se añada de forma explícita. Aun así, hay varios ajustes que marcan diferencia en las Core Web Vitals:
- Imágenes: el componente
<Image>genera versiones optimizadas consrcset, carga diferida y dimensiones, y<Picture>permite servir AVIF y WebP con respaldo. - Imagen principal: la imagen que suele ser el LCP no debe cargarse en diferido; conviene marcarla con
loading="eager"yfetchpriority="high". - Fuentes: Joost precarga la fuente principal en el
<head>. Desde Astro 6, además, la API de fuentes integrada permite configurar fuentes locales o de proveedores, con precarga y fuentes de respaldo, desde la propia configuración. - Navegación: con
<ClientRouter />y la estrategia de precarga por viewport, los enlaces visibles se precargan antes del clic. - Caché: los recursos con hash de
/_astro/pueden servirse conCache-Control: public, max-age=31536000, immutable. - Parámetros de campaña: la cabecera
No-Vary-Searchindica al navegador que ignore parámetros como los UTM al reutilizar respuestas en caché. Chrome la admite y el resto de navegadores simplemente la ignora.
---
import { Picture } from 'astro:assets';
import portada from '../assets/portada.jpg';
---
<Picture
src={portada}
formats={['avif', 'webp']}
alt="Descripción real de lo que muestra la imagen"
loading="eager"
fetchpriority="high"
/>
Para seguir la evolución de estas métricas en el tiempo, puede ser útil visualizar el historial de Core Web Vitals y no quedarse solo con una medición puntual.
7. Sitemaps, indexación, RSS y robots.txt
La integración oficial @astrojs/sitemap genera un índice y los mapas del sitio. Joost usa su opción chunks para dividirlo por tipo de contenido (artículos, vídeos, páginas), lo que facilita detectar problemas de indexación por sección en Google Search Console y Bing Webmaster Tools. La opción filter excluye rutas que no deben aparecer y i18n añade las alternativas de idioma:
sitemap({
filter: (page) => !page.includes('/borradores/'),
chunks: {
articulos: (item) => (item.url.includes('/blog/') ? item : undefined),
paginas: (item) => (!item.url.includes('/blog/') ? item : undefined),
},
i18n: {
defaultLocale: 'es',
locales: { es: 'es-ES', en: 'en-US' },
},
}),
Para la fecha lastmod, la guía original propone usar la fecha del último commit de cada archivo, obtenida con git log -1 --format="%cI" dentro de la función serialize, y recurrir a la fecha del frontmatter cuando el historial de git no es fiable, por ejemplo tras una migración. Lo que no conviene es poner la fecha del build a todas las URL, porque le quita valor a la señal.
El resto del apartado de indexación incluye tres piezas:
- IndexNow: avisa a los buscadores de cada URL nueva o cambiada justo después del build. Lo usan Bing, Yandex y otros, pero no Google, que sigue dependiendo del rastreo y del sitemap.
- RSS: con
@astrojs/rss, y con el contenido completo en lugar de extractos, porque lo consumen lectores, agregadores y agentes. - robots.txt: generado como ruta dinámica, para que siempre apunte al sitemap correcto del dominio.
// src/pages/robots.txt.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = ({ site }) => {
const sitemap = new URL('sitemap-index.xml', site);
const body = `User-agent: *
Allow: /
Sitemap: ${sitemap}
`;
return new Response(body, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
};
Si además se quiere controlar qué rastreadores de IA acceden al contenido, es útil saber cómo verificar si GPTBot o PerplexityBot son realmente quienes dicen ser antes de tomar decisiones en robots.txt.
8. Webs multilingües: rutas i18n y hreflang
La guía original menciona hreflang de pasada, pero para muchos proyectos en español es un punto clave, porque es habitual publicar también en inglés, catalán o portugués. Astro incluye enrutado internacional desde la configuración, con un idioma por defecto y la opción de que ese idioma lleve o no prefijo en la URL (/es/).
Tres reglas evitan la mayoría de problemas. Cada versión debe enlazar a todas las demás con <link rel="alternate" hreflang="...">, incluida ella misma. Conviene añadir una versión x-default para usuarios sin idioma coincidente. Y la canónica de cada página debe apuntar a su propia versión de idioma, no a la principal. El sitemap con la opción i18n refuerza esas relaciones, pero no sustituye a las etiquetas en el HTML.
9. Preparar la web para agentes de IA, con expectativas realistas
Es la parte más novedosa de la guía de Joost y también la que más matices necesita. Propone cuatro mecanismos:
- Versiones en Markdown: cada página tiene una alternativa
.md, anunciada con<link rel="alternate" type="text/markdown">, porque los agentes procesan Markdown con más fiabilidad que HTML. Con una regla en Cloudflare se puede servir esa versión cuando la petición pideAccept: text/markdown. - llms.txt: un resumen del sitio en
/llms.txt, siguiendo la propuesta de llmstxt.org. - Endpoints de schema y schema map: todo el JSON-LD de cada colección publicado en rutas como
/schema/post.jsony listado en/schemamap.xml, enlazado desde robots.txt. - NLWeb: una etiqueta
<link rel="nlweb">que apunta a un punto de acceso conversacional, según el protocolo impulsado por Microsoft.
Conviene separar lo que ayuda en buscadores de lo que es preparación para agentes. Google ha dejado claro que llms.txt no es necesario para aparecer en sus funciones de IA; John Mueller llegó a compararlo con la metaetiqueta keywords. Sin embargo, la herramienta Lighthouse de Chrome incorporó en 2026 una auditoría experimental de navegación con agentes que sí lo revisa. El schema map y NLWeb son propuestas emergentes con adopción todavía limitada. Implementarlos cuesta poco en Astro, pero no deberían venderse como una mejora de posicionamiento.
En Seocretos ya explicamos qué es llms.txt y cómo se estructura, y herramientas como AgentReady.md permiten revisar si una web está preparada para agentes. Para medir el efecto real, lo más útil sigue siendo medir la visibilidad de la marca en buscadores con IA.
10. Redirecciones y errores 404
Toda URL antigua debe redirigir con un 301 a su nueva ubicación, sobre todo tras una migración desde WordPress u otro CMS. Aquí hay un detalle técnico importante que la guía original no menciona: la opción redirects de astro.config.mjs, en un sitio estático sin adaptador, genera una redirección de cliente con <meta http-equiv="refresh"> y no un 301 real. Para redirecciones permanentes de verdad hay que usar la configuración del alojamiento: el archivo _redirects en Cloudflare Pages o Netlify, o vercel.json en Vercel.
# public/_redirects (Cloudflare Pages o Netlify)
/blog/articulo-antiguo/ /guias/articulo-nuevo/ 301
/categoria/seo/* /seo-tecnico/:splat 301
Joost añade en la página 404 un componente que compara la URL solicitada con las del sitemap y redirige automáticamente si la similitud supera el 85 %, o muestra sugerencias si no. Es útil para erratas, pero conviene usarlo con cuidado: la redirección ocurre en el navegador, después de que el servidor haya devuelto el 404, así que no transmite señales de enlace como un 301. Las URL antiguas con enlaces externos deben seguir teniendo su redirección explícita.
11. Medición: qué herramientas usar
Joost usa Plausible para la analítica, porque no requiere banner de cookies y su script es ligero, Google Search Console para la indexación y el rendimiento en Google, y Bing Webmaster Tools, cuyo índice alimenta a Copilot y a las búsquedas de ChatGPT. Los mapas del sitio divididos por colección facilitan ver en ambas herramientas qué sección tiene problemas. Para el JSON-LD, la prueba de resultados enriquecidos de Google y el validador de Schema.org permiten comprobar que el grafo es válido y que todos los @id se resuelven.
Lista de comprobación de SEO en Astro
| Área | Qué comprobar | Herramienta en Astro |
|---|---|---|
| Configuración | site, trailingSlash y build.format coherentes | astro.config.mjs |
| Metaetiquetas | Título, descripción, canónica sin parámetros, robots y Open Graph | Componente de cabecera único |
| Contenido | Frontmatter obligatorio y límites de longitud | Colecciones de contenido con Zod |
| Datos estructurados | Grafo JSON-LD enlazado con @id | Script application/ld+json |
| Rendimiento | LCP sin carga diferida, AVIF/WebP, fuentes precargadas, caché inmutable | <Picture>, API de fuentes |
| Indexación | Sitemap por secciones, lastmod real, robots.txt | @astrojs/sitemap, rutas .ts |
| Idiomas | hreflang recíproco, x-default, canónica por idioma | Enrutado i18n |
| Redirecciones | 301 reales para URL antiguas | _redirects o vercel.json |
| Agentes | Markdown alternativo, llms.txt, RSS completo | Endpoints en src/pages |
Preguntas frecuentes
¿Es Astro bueno para el SEO?
Sí, porque genera HTML estático y no envía JavaScript por defecto, lo que favorece la velocidad y el rastreo. Pero las metaetiquetas, los datos estructurados, el sitemap y las redirecciones hay que configurarlos.
¿Qué paquete usar para el SEO en Astro?
Joost de Valk usa @jdevalk/astro-seo-graph, que reúne metaetiquetas, JSON-LD, validaciones, IndexNow y llms.txt. También existen alternativas como astro-seo, o un componente propio sin dependencias.
¿Sirve llms.txt para posicionar en Google?
No. Google ha indicado que no es necesario para sus funciones de IA. Puede ser útil para agentes y herramientas como Lighthouse, pero no es un factor de posicionamiento.
¿Las redirecciones de astro.config.mjs son 301?
Solo si hay un adaptador o renderizado bajo demanda. En un sitio estático sin adaptador generan una redirección con meta refresh, así que conviene definir los 301 en el alojamiento.
Fuentes:
