astro-seo-metaetiquetas-sitemap

SEO y Metaetiquetas en Astro

  • 5 min

Astro tiene una gran ventaja en el mundo del SEO: la velocidad. Los Core Web Vitals de un sitio hecho en Astro suelen ser buenos por defecto, y eso a Google le encanta.

Sin embargo, el rendimiento es solo la mitad de la batalla. La otra mitad es la comunicación con los rastreadores (crawlers).

Necesitamos decirle a Google, Bing, Facebook y Twitter exactamente qué título mostrar, qué imagen usar en la previsualización y cuál es la URL “oficial” de cada contenido.

En este artículo veremos como optimizar nuestro sitio para SEO sin morir en el intento.

Gestión de metadatos

En proyectos pequeños, lo normal es definir las metaetiquetas directamente en el archivo Layout.astro:

<title>Mi Blog</title>
<meta name="description" content="Un blog sobre tecnología..." />
Copied!

Pero, ¿qué pasa cuando estás en un artículo específico? Quieres que el título sea el del post, no “Mi Blog”. Quieres que la imagen de Twitter sea la portada del artículo, etc…

Si empiezas a poner condicionales if/else dentro del <head> del Layout, acabarás con un código difícil de entender. La solución es crear un Componente SEO.

Creando el componente <SEO />

Vamos a crear un componente dedicado exclusivamente a inyectar las etiquetas meta. Lo guardaremos en src/components/SEO.astro.

Lo primero es definir qué datos pueden cambiar de una página a otra.

---
// src/components/SEO.astro
interface Props {
  title: string;
  description: string;
  image?: string;
  canonicalURL?: URL | string;
  type?: 'website' | 'article'; // Para Open Graph
}

const { 
  title, 
  description, 
  image = '/img/default-og.png', // Imagen por defecto
  canonicalURL = new URL(Astro.url.pathname, Astro.site),
  type = 'website' 
} = Astro.props;

// Aseguramos que la imagen sea una URL absoluta (necesario para OG tags)
const absoluteImageUrl = new URL(image, Astro.site);
---

<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonicalURL} />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />

<meta property="og:type" content={type} />
<meta property="og:url" content={canonicalURL} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={absoluteImageUrl} />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={absoluteImageUrl} />
Copied!

Importante: Configurar Astro.site

Para que new URL(path, Astro.site) funcione y genere URLs absolutas (ej: https://miweb.com/imagen.png), es obligatorio definir tu dominio en astro.config.mjs:

// astro.config.mjs
export default defineConfig({
  site: 'https://www.luisllamas.es', // Tu dominio real
});
Copied!

Usando el componente en el Layout

Ahora limpiamos nuestro Layout.astro y usamos nuestro nuevo componente.

---
// src/layouts/Layout.astro
import SEO from '../components/SEO.astro';

interface Props {
  title: string;
  description?: string;
  image?: string;
}

const { title, description, image } = Astro.props;
---

<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <SEO 
      title={title} 
      description={description || "Descripción por defecto de la web"}
      image={image}
    />
    <ViewTransitions /> 
  </head>
  <body>
    <slot />
  </body>
</html>
Copied!

Y en una página de blog ([...slug].astro), simplemente pasamos los datos:

---
const { post } = Astro.props;
---
<Layout 
  title={post.data.title}
  description={post.data.description}
  image={post.data.cover}
>
  </Layout>
Copied!

URLs Canónicas (Canonical)

Las etiquetas canónicas (rel="canonical") son imprescindibles para evitar que Google te penalice por contenido duplicado.

A veces, una misma página es accesible desde varias URLs:

  • https://miweb.com/blog/post-1
  • https://miweb.com/blog/post-1?utm_source=twitter

Para Google, son dos páginas distintas con el mismo texto, y eso esta penalizadísimo. En nuestro componente SEO ya lo hemos automatizado:

canonicalURL = new URL(Astro.url.pathname, Astro.site)
Copied!

Esto coge la ruta limpia (/blog/post-1), ignora los parámetros de consulta (?utm...) y construye la URL oficial. Astro se encarga de normalizarla (por ejemplo, quitando o poniendo la barra final / según tu configuración).

Generación de Sitemap (sitemap.xml)

Un sitemap es un archivo XML que lista todas las páginas de tu sitio para que los buscadores las encuentren rápido. Hacer esto a mano es imposible en sitios dinámicos.

Astro tiene una integración oficial maravillosa para esto.

npx astro add sitemap
Copied!

Esto instalará @astrojs/sitemap y actualizará tu configuración.

La integración leerá automáticamente todas tus rutas estáticas (src/pages) y las rutas generadas por getStaticPaths.

El archivo se generará en dist/sitemap-index.xml (o sitemap-0.xml) cuando hagas el build.

El sitemap requiere que la propiedad site esté configurada en astro.config.mjs. Si no pones tu dominio, la generación del sitemap fallará, porque los sitemaps exigen URLs absolutas.

Datos estructurados (JSON-LD)

Si quieres ir a por nota, puedes inyectar Schema.org para que Google muestre resultados enriquecidos (estrellitas, recetas, eventos).

Podemos añadir esto a nuestro componente SEO.astro o directamente en el layout del artículo:

---
// En el layout de un post
const schema = {
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": title,
  "image": absoluteImageUrl,
  "author": {
    "@type": "Person",
    "name": "Luis Llamas"
  },
  "datePublished": pubDate.toISOString()
};
---

<script type="application/ld+json" set:html={JSON.stringify(schema)} />
Copied!

Usamos set:html para inyectar el string JSON directamente dentro de la etiqueta script sin que Astro intente escaparlo.