🧠 Implementación de SEO dinámica en Astro con Markdown y páginas .astro

En este artículo aprenderás a implementar un sistema escalable para SEO técnico en tu proyecto Astro. Utilizaremos contenido en Markdown y páginas .astro, conectando todo con un componente dinámico que genera metaetiquetas automáticamente para Open Graph, Twitter Card y más.


📚 Índice


1. Desde un archivo .md (Markdown Content Collection)

Supongamos que tienes un archivo en:

src/content/blog/ejemplo.md

Con este frontmatter:

---
title: "Título del artículo"
description: "Descripción optimizada para SEO"
pubDate: "2025-07-19"
author: "Leandro Fuentes"
image: "/blog/ejemplo.jpg"
tags: ["markdown", "astro", "seo"]
---

✔️ ¿Qué sucede?

  • El frontmatter se pasa automáticamente como props al Layout.astro cuando usas un archivo como [slug].astro.
  • El layout luego pasa estos datos al componente <Seo />.

2. Desde una página .astro

Supongamos que tienes esta página:

src/pages/ejemplo.astro
---
import Layout from "../layouts/Layout.astro";
---

<Layout 
  title="Página de ejemplo"
  description="Esta es una página construida con Astro"
  pubDate="2025-07-19"
  author="Leandro Fuentes"
  image="/blog/ejemplo.jpg"
>
  <h1>Contenido de la página</h1>
</Layout>

✔️ ¿Qué sucede?

  • Estás pasando los props manualmente al Layout.astro
  • El layout los redirige al componente <Seo />

3. ¿Qué hace Layout.astro?

---
import Seo from "../components/Seo.astro";
const { title, description, pubDate, author, image } = Astro.props;
---

<html lang="es">
  <head>
    <Seo 
      title={title}
      description={description}
      pubDate={pubDate}
      author={author}
      image={image}
      url="https://leandrofuentes.com"
    />
    <!-- Otros metatags, favicon, fuentes, etc. -->
  </head>
  <body>
    <slot />
  </body>
</html>

✅ Esto genera automáticamente todas las metaetiquetas SEO, incluyendo Open Graph y Twitter Card para compartir en redes sociales.


4. ¿Qué pasa si falta un campo?

El componente Seo.astro incluye valores por defecto, así que:

  • Si no pasas author, se usará "Leandro Fuentes"
  • Si no pasas image, se usará "/default-og.jpg"
  • Si no pasas url, se usará "https://leandrofuentes.com"

🛡 Esto evita errores o previews incompletos.


5. Componente Seo.astro completo

Guarda este archivo como:

src/components/Seo.astro
---
// src/components/Seo.astro
export interface Props {
  title: string
  description: string
  pubDate?: string
  author?: string
  image?: string
  url?: string
}

const {
  title,
  description,
  pubDate = "",
  author = "Leandro Fuentes",
  image = "/default-og.jpg",
  url = "https://leandrofuentes.com"
} = Astro.props;
---

<!-- SEO Meta Tags -->
<title>{title}</title>
<meta name="description" content={description} />
<meta name="author" content={author} />
<meta name="robots" content="index, follow" />

<!-- Open Graph -->
<meta property="og:type" content="article" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={url + image} />
<meta property="og:url" content={Astro.url.origin + Astro.url.pathname} />
<meta property="og:site_name" content="Leandro Fuentes - Developer & Designer" />
{pubDate && <meta property="article:published_time" content={pubDate} />}

<!-- Twitter Card -->
<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={url + image} />

6. Cómo usarlo en tu Layout.astro

Dentro del <head> de tu layout principal:

---
import Seo from '../components/Seo.astro';
const { title, description, pubDate, author, image } = Astro.props;
---

<html lang="es">
  <head>
    <Seo 
      title={title}
      description={description}
      pubDate={pubDate}
      author={author}
      image={image}
      url="https://leandrofuentes.com"
    />
    <!-- Otros metatags, fuentes, favicon, etc. -->
  </head>

7. ✅ Resultado final

Este sistema:

  • Extrae metadatos desde el frontmatter o props
  • Genera etiquetas SEO válidas y completas
  • Automatiza previews en WhatsApp, Facebook, LinkedIn, X (Twitter)
  • Escala en todos los artículos del blog con una sola fuente de configuración

¿Quieres más plantillas o integraciones con JSON-LD, Schema.org o Favicon dinámico? ¡Pídelo y lo agregamos!


⚠️ Advertencia: por qué tu metaetiqueta puede no estar funcionando

Es posible que, aunque hayas definido correctamente el title y description en el frontmatter de tu .md, al revisar el HTML generado veas algo como esto:

<meta property="og:title" content="Leandro Fuentes - Developer & Designer | Marketing Digital" />
<title>Leandro Fuentes - Developer & Designer | Marketing Digital</title>

En lugar de ver el título real de tu artículo.

🧠 ¿Por qué ocurre esto?

Esto pasa cuando en el layout (Layout.astro) estás usando valores por defecto así:

const { 
  title = "Leandro Fuentes - Developer & Designer | Marketing Digital", 
  description = "Sitio web personal que muestra mi trabajo y experiencia", 
  pubDate, author, image, url 
} = Astro.props;

En ese caso, aunque el .md tenga title y description, el layout ya les está asignando un valor por defecto y no se actualizan.


✅ ¿Cómo solucionarlo?

Haz lo siguiente:

  1. Asegúrate de que el post tenga el layout bien referenciado (esto ya lo tienes).

  2. En el Layout.astro, NO asignes valores por defecto en title ni description. Usa:

const { title, description, pubDate, author, image, url } = Astro.props;
  1. Si quieres tener un fallback para casos donde no se pase nada, hazlo dentro del componente Seo.astro, no en el layout.

🎯 Resultado

Esto asegura que el título y descripción definidos en tu Markdown se apliquen correctamente en las etiquetas <title> y og:title, sin que sean sobrescritos por valores por defecto del layout.