🏗️

GEO con SSGs: Guía para Hugo, Jekyll y Astro

intermediate

Los generadores de sitios estáticos (Hugo, Jekyll, Astro) son ideales para GEO: generan HTML puro sin JavaScript necesario para los crawlers de IA. Implementa GEO en el template base para meta tags y JSON-LD para todo el sitio. Los SSGs generan sitemaps XML con lastmod automáticamente. Astro añade cero overhead de JavaScript por defecto.

GEO con SSGs: Guía para Hugo, Jekyll y Astro

Los generadores de sitios estáticos (Hugo, Jekyll, Astro) son ideales para GEO: generan HTML puro sin JavaScript necesario para los crawlers de IA. Implementa GEO en el template base para meta tags y JSON-LD para todo el sitio. Los SSGs generan sitemaps XML con lastmod automáticamente. Astro añade cero overhead de JavaScript por defecto.

Los SSGs tienen la mejor posición de partida para GEO: cada página es HTML estático pre-renderizado, servido directamente desde un CDN. Los crawlers de IA reciben contenido completo sin necesidad de ejecutar JavaScript ni esperar hidratación.

Hugo

Template base (layouts/_default/baseof.html)

<!DOCTYPE html>
<html lang="{{ .Site.Language.Lang }}">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <!-- TÍTULO -->
  <title>
    {{- if .IsHome -}}
      {{ .Site.Title }}
    {{- else -}}
      {{ .Title }} | {{ .Site.Title }}
    {{- end -}}
  </title>

  <!-- DESCRIPCIÓN -->
  {{ with .Description }}
    <meta name="description" content="{{ . }}">
  {{ else }}
    <meta name="description" content="{{ .Site.Params.description }}">
  {{ end }}

  <meta name="author" content="{{ .Params.author | default .Site.Params.author }}">
  <link rel="canonical" href="{{ .Permalink }}">
  <meta name="robots" content="index, follow">

  <!-- OPEN GRAPH -->
  {{ if .IsPage }}
    <meta property="og:type" content="article">
    <meta property="article:published_time" content="{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}">
    <meta property="article:modified_time" content="{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}">
    {{ with .Params.author }}
      <meta property="article:author" content="{{ . }}">
    {{ end }}
    {{ range .Params.tags }}
      <meta property="article:tag" content="{{ . }}">
    {{ end }}
  {{ else }}
    <meta property="og:type" content="website">
  {{ end }}

  <meta property="og:title" content="{{ .Title }}">
  <meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}">
  <meta property="og:url" content="{{ .Permalink }}">
  <meta property="og:site_name" content="{{ .Site.Title }}">
  <meta property="og:locale" content="{{ .Site.Language.Lang | replace "-" "_" }}">
  {{ with .Params.image }}
    <meta property="og:image" content="{{ . | absURL }}">
  {{ end }}

  <!-- JSON-LD -->
  {{ if .IsPage }}
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": {{ .Title | jsonify }},
    "description": {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | jsonify }}{{ end }},
    "author": {
      "@type": "Person",
      "name": {{ .Params.author | default .Site.Params.author | jsonify }}
    },
    "publisher": {
      "@type": "Organization",
      "name": {{ .Site.Title | jsonify }},
      "logo": {
        "@type": "ImageObject",
        "url": {{ .Site.Params.logoUrl | absURL | jsonify }}
      }
    },
    "datePublished": {{ .Date.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
    "dateModified": {{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": {{ .Permalink | jsonify }}
    }
  }
  </script>
  {{ end }}
</head>
<body>
  {{ block "main" . }}{{ end }}
</body>
</html>

robots.txt (static/robots.txt)

User-agent: GPTBot
Allow: /

User-agent: OAI-SearchBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: Claude-User
Allow: /

User-agent: Claude-SearchBot
Allow: /

User-agent: PerplexityBot
Allow: /

User-agent: Google-Extended
Allow: /

User-agent: BingBot
Allow: /

User-agent: *
Allow: /

Sitemap: https://misitio.com/sitemap.xml
Sitemap: https://misitio.com/llms.txt

Hugo genera sitemap.xml automáticamente con <lastmod> del front matter lastmod de la página o el tiempo de modificación del archivo. Activa enableRobotsTXT = true en config.toml.

Jekyll

_layouts/default.html

<!DOCTYPE html>
<html lang="{{ site.lang | default: 'es' }}">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>
    {%- if page.title -%}
      {{ page.title }} | {{ site.title }}
    {%- else -%}
      {{ site.title }}
    {%- endif -%}
  </title>

  <meta name="description" content="{{ page.description | default: site.description | escape }}">
  <meta name="author" content="{{ page.author | default: site.author }}">
  <link rel="canonical" href="{{ page.url | absolute_url }}">
  <meta name="robots" content="index, follow">

  <meta property="og:type" content="article">
  <meta property="og:title" content="{{ page.title | escape }}">
  <meta property="og:description" content="{{ page.description | default: site.description | escape }}">
  <meta property="og:url" content="{{ page.url | absolute_url }}">
  <meta property="og:site_name" content="{{ site.title }}">

  {% if page.date %}
  <meta property="article:published_time" content="{{ page.date | date_to_xmlschema }}">
  {% endif %}
  {% if page.last_modified_at %}
  <meta property="article:modified_time" content="{{ page.last_modified_at | date_to_xmlschema }}">
  {% endif %}

  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": {{ page.title | jsonify }},
    "description": {{ page.description | default: site.description | jsonify }},
    "author": { "@type": "Person", "name": {{ page.author | default: site.author | jsonify }} },
    "publisher": {
      "@type": "Organization",
      "name": {{ site.title | jsonify }},
      "logo": { "@type": "ImageObject", "url": {{ "/assets/logo.png" | absolute_url | jsonify }} }
    },
    "datePublished": {{ page.date | date_to_xmlschema | jsonify }},
    "dateModified": {{ page.last_modified_at | default: page.date | date_to_xmlschema | jsonify }},
    "mainEntityOfPage": { "@type": "WebPage", "@id": {{ page.url | absolute_url | jsonify }} }
  }
  </script>
</head>
<body>
  {{ content }}
</body>
</html>

El gem jekyll-sitemap genera sitemap.xml con <lastmod> del front matter last_modified_at.

Astro

src/layouts/BaseLayout.astro

---
interface Props {
  title: string
  description: string
  datePublished: string
  dateModified: string
  author?: string
  image?: string
}

const {
  title,
  description,
  datePublished,
  dateModified,
  author = 'Mi Empresa',
  image,
} = Astro.props

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

const schema = {
  '@context': 'https://schema.org',
  '@type': 'Article',
  headline: title,
  description,
  author: { '@type': 'Organization', name: author },
  publisher: {
    '@type': 'Organization',
    name: 'Mi Sitio',
    logo: { '@type': 'ImageObject', url: new URL('/logo.png', Astro.site).href },
  },
  datePublished,
  dateModified,
  mainEntityOfPage: { '@type': 'WebPage', '@id': canonicalURL.href },
}
---

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{title} | Mi Sitio</title>
  <meta name="description" content={description}>
  <meta name="author" content={author}>
  <link rel="canonical" href={canonicalURL}>
  <meta name="robots" content="index, follow">

  <meta property="og:type" content="article">
  <meta property="og:title" content={title}>
  <meta property="og:description" content={description}>
  <meta property="og:url" content={canonicalURL}>
  <meta property="og:site_name" content="Mi Sitio">
  {image && <meta property="og:image" content={image}>}

  <meta property="article:published_time" content={datePublished}>
  <meta property="article:modified_time" content={dateModified}>

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

Sitemap de Astro

Usa la integración @astrojs/sitemap:

npx astro add sitemap
// astro.config.mjs
import { defineConfig } from 'astro/config'
import sitemap from '@astrojs/sitemap'

export default defineConfig({
  site: 'https://misitio.com',
  integrations: [sitemap()],
})

La integración genera sitemap-index.xml con fechas <lastmod> automáticamente de los tiempos de modificación de páginas.

Checklist GEO para SSGs

  • Template base: title, description, canonical, author, robots
  • Template base: Open Graph con og:type=article, og:title, og:description, og:url
  • Template base: article:published_time y article:modified_time desde front matter
  • Template base: schema JSON-LD Article con publisher y fechas
  • public/ o static/: robots.txt con los 8 crawlers de IA
  • public/ o static/: llms.txt con descripción del sitio y listado de páginas
  • Sitemap: generado con fechas lastmod (integrado en Hugo/Astro, plugin para Jekyll)
  • Front matter: date y lastmod/last_modified_at en todas las páginas
  • Contenido: estructura en pirámide invertida en archivos Markdown
  • Core Web Vitals: LCP < 2.5s, INP < 200ms, CLS < 0.1