· Jose Antonio López  · 6 min lectura

SEO en Astro - Guía para Configurar Metadatos, Open Graph y JSON-LD

Aprende a implementar una estrategia de SEO técnico en Astro. Configura metadatos dinámicos, etiquetas Open Graph, Twitter Cards, datos estructurados con JSON-LD y tipado con schema-dts para mejorar la indexación y visibilidad en Google.

Aprende a implementar una estrategia de SEO técnico en Astro. Configura metadatos dinámicos, etiquetas Open Graph, Twitter Cards, datos estructurados con JSON-LD y tipado con schema-dts para mejorar la indexación y visibilidad en Google.

Objetivo

Proporcionar una guía técnica real sobre cómo configurar el SEO en un proyecto web con Astro y enfocado a blog. Técnicamente se explica lo siguiente:

CaracterísticaDescripción
Metadatos GlobalesGestión centralizada de etiquetas meta para todo el sitio.
Open Graph & TwitterConfiguración de tarjetas sociales para compartir contenido.
JSON-LDImplementación de datos estructurados para Google
Type SafetyUso de TypeScript

¿Por qué escribo este artículo?

La mayoría de sitios añade etiquetas <title>, <meta name="description"> y poco más. Sin embargo, en aplicaciones reales se necesita estructurar las páginas con datos enriquecidos.

En este artículo, detallo cómo construir un sistema que permita reusar metadatos globales, sobrescribirlos en páginas y generar datos estructurados (JSON-LD) sin ensuciar la lógica de los componentes.

Prerequisitos

Para seguir este artículo necesitas tener instalado:

Herramienta/PasoEnlace de descarga
Node.js (v18.17.1+)Descargar Node.js
Astro CLIInstalar Astro
VS CodeDescargar VS Code

Dependencias

Trabajaremos con las siguientes librerías:

DependenciaDescripción
@astrolib/seoUna librería para manejar las etiquetas meta de SEO, Open Graph y Twitter de forma declarativa.
schema-dtsProporciona definiciones de TypeScript para todos los esquemas de Schema.org, permitiendo crear JSON-LD
lodash.mergeUtilizada para combinar los objetos de metadatos globales con los específicos de cada página.
package.json
{
  "dependencies": {
    "@astrolib/seo": "1.0.0-beta.8",
    "schema-dts": "^1.1.2",
    "lodash.merge": "^4.6.2"
  }
}

Decisiones de diseño

Estructura del proyecto

A continuación puedes ver los archivos de la capa de SEO y datos estructurados:

src
├── config.yaml              // Configuración global del sitio
├── components
│   └── common
│       ├── CommonMeta.astro     // i18n y etiquetas base
│       └── Metadata.astro       // Lógica con @astrolib/seo
├── ld-json
│   ├── pages
│   │   └── blog.ts              // Esquemas de página de blog
│   ├── types.ts                 // Definiciones de tipos
│   └── utils.ts                 // Generadores de JSON-LD
├── layouts
│   ├── Layout.astro             // Layout genérico
│   └── BlogEntryLayout.astro    // Layout especializado para posts
└── pages
    └── [...blog]
        └── index.astro          // Implementación final del post

Datos Estructurados Tipados

En lugar de escribir JSON plano se usa schema-dts y se garantiza que no se cometan errores en los nombres de las propiedades (por ejemplo, escribir headline en lugar de name donde no corresponde).

Configuración del sitio cargada con integración de Astrowind

Para evitar el hardcoding de valores se usa un archivo de configuración llmado src/config.yaml. Mediante una integración de AstroWind, los valores se exponen en un módulo virtual astrowind:config y el componente de metadatos accede a la configuración global.

Esta es la forma más fácil que he encontrado para mi sitio web ya que carga la configuración y los datos vienen tipados en un solo lugar dentro de las variables SITE y METADATA.

Si necsitas algo más customizado y quieres una referencia puedes visitar el repositorio oficial de AstroWind en la sección “integration”.

SEO Global

Configuración

La configuración del sitio web está almacenada en el archivo src/config.yaml:

config.yaml

site:
  name: josealopez.dev
  site: https://josealopez.dev


metadata:
  title:
    default: Blog sobre desarrollo web
    template: '%s | Jose López'
  description: Blog de desarrollo web, tutoriales, guías y recursos sobre backend y frontend.

  robots:
    index: true
    follow: true
  openGraph:
    site_name: josealopez.dev
    images:
      - url: '~/assets/images/whatever.png'
        width: 1200
        height: 628
    type: website
  twitter:
    handle: '@josealopez_dev'
    site: '@josealopez_dev'
    cardType: summary_large_image

Metadatos

La pieza central es el componente Metadata.astro. El componente recibe las propiedades de SEO y las inyecta en el <head>. Este componente viene ya de Astrowind en un principio y ha sido adaptado a mis necesidades.

src/components/common/Metadata.astro

import merge from 'lodash.merge';
import { AstroSeo } from '@astrolib/seo';
import { SITE, METADATA } from 'astrowind:config';
import type { MetaData } from '~/types';

export interface Props extends MetaData {}

const {
  title,
  canonical,
  description,
  openGraph = {},
} = Astro.props;

const seoProps = merge(
  {
    titleTemplate: METADATA?.title?.template || '%s',
    description: METADATA?.description,
    canonical: canonical,
    openGraph: {
      site_name: SITE?.name,
      type: 'website',
    },
  },
  {
    title: title,
    description: description,
    openGraph: { url: canonical, ...openGraph },
  }
);
---

<AstroSeo {...{ ...seoProps, openGraph: await adaptOpenGraphImages(seoProps?.openGraph, Astro.site) }} />

Explicación del código

  • merge: Combina astrowind:config con las props del componente. Los valores de la derecha sobrescriben a los de la izquierda.
  • titleTemplate: Permite que todas las páginas tengan un formato coherente (ej: “Mi Post | Jose Lopez”) sin tener que escribir el nombre del sitio cada vez.
  • AstroSeo: Componente de la librería que se encarga de las etiquetas <title>, <meta name="description">, og: y twitter:.

Tip

El uso de un titleTemplate con %s es una buena práctica de SEO para mantener la consistencia de marca en los resultados de búsqueda de Google.

SEO para Datos Estructurados

Para las entradas de blog, necesitaba un esquema para generar JSON-LD. El objetivo es que los buscadores puedan entender la estructura y contenido del sitio sin necesidad de leer todo el HTML. Mejor poner las cosas fáciles a los bots que indexan antes de que se vayan sin haber indexado lo importante.

Generador de JSON-LD

Se crea una utilidad que transforma un objeto post en un objeto BlogPosting válido dentro de schema.org.

src/ld-json/utils.ts
import type { BlogPosting, WithContext } from 'schema-dts';
import type { Post } from '~/types';

export function createArticleJsonLd(post: Post): WithContext<BlogPosting> {
  return {
    '@context': 'https://schema.org',p
    '@type': 'BlogPosting',
    headline: post.title,
    description: post.excerpt,
    datePublished: post.publishDate.toISOString(),
    dateModified: post.updateDate?.toISOString() || post.publishDate.toISOString(),
    image: post.image,
    author: {
      '@type': 'Person',
      name: 'Jose Antonio López',
      url: 'https://josealopez.dev'
    },
    mainEntityOfPage: {
      '@type': 'WebPage',
      '@id': post.metadata?.canonical
    }
  };
}

Explicación del código

  • WithContext<BlogPosting>: Proporciona el tipado necesario de schema-dts, incluyendo el campo @context.
  • headline y description: Son campos críticos para que Google entienda el resumen del artículo.
  • datePublished / dateModified: Ayudan a Google a saber si el contenido es fresco o ha sido actualizado recientemente.

Post type

Para referencia puedes ver las variables dentro del tipo Post.

src/ld-json/utils.ts
export interface Post {
  id: string;
  publishDate: Date;
  updateDate?: Date;
  title: string;
  excerpt?: string;
  image?: string;
  metadata?: MetaData;
}

Integración Final en la Página

Finalmente, juntamos todo en la página del blog:

src/pages/[...blog]/index.astro
---
import type { InferGetStaticPropsType, GetStaticPaths } from 'astro';

import merge from 'lodash.merge';
import type { ImageMetadata } from 'astro';
import { createArticleJsonLd } from '~/ld-json/utils';

export const getStaticPaths = (async () => {
  return await getStaticPathsBlogPost();
}) satisfies GetStaticPaths;

type Props = InferGetStaticPropsType<typeof getStaticPaths>;

const { post } = Astro.props as Props;

const postPath = getBlogPermalink(post.permalink, language);
const url = getCanonical(getPermalink(postPath, 'post'));
const image = (await findImage(post.image)) as ImageMetadata | string | undefined;


const metadata = merge(
  {
    title: post.title,
    description: post.excerpt,
    robots: {
      index: blogPostRobots?.index,
      follow: blogPostRobots?.follow,
    },
    openGraph: {
      type: 'article',
      ...(image
        ? { images: [{ url: image, width: (image as ImageMetadata)?.width, height: (image as ImageMetadata)?.height }] }
        : {}),
    },
  },
  { ...(post?.metadata ? { ...post.metadata, canonical: post.metadata?.canonical || url } : {}) }
) as MetaData;

const structuredData = createArticleJsonLd(post, language);

---

<PageLayout metadata={metadata} structuredData={structuredData}, >
  <SinglePost post={{ ...post, image: image }} url={url}>
    {post.Content ? <post.Content /> : <Fragment set:html={post.content || ''} />}
  </SinglePost>
</PageLayout>

Explicación del código

Astro tiene en este momento el archivo del post cargado para construirlo mediante SSG(Server Side Generation). El objetivo es usar los datos que necesites en ese momento. Este es el momento adecuado para construir toda el contenido SEO y pasarlo a otro Layout con la estructura del documento.

  • metadata: Se pasa al Layout que estés usando.
  • structuredData: Se pasa al Layout que estés usando.

Importante

Nunca olvides que el JSON-LD debe ser inyectado como un string JSON válido dentro de una etiqueta script. Astro permite hacer esto de forma segura con la directiva set:html.

Resumen de la Arquitectura SEO

  1. Layout: Recibe metadatos y datos estructurados.
  2. Metadata.astro: Procesa y renderiza las etiquetas meta tradicionales.
  3. JSON-LD: Mejora la visibilidad con fragmentos enriquecidos (Rich Snippets).
  4. TypeScript: Asegura que toda la cadena de datos sea válida y coherente.

Fuentes consultadas

  • Astro
  • SEO
  • TypeScript
Compartir: