GEO for SSGs: Hugo, Jekyll, and Astro Guide
intermediateStatic site generators (Hugo, Jekyll, Astro) are ideal for GEO: they output pure HTML with no JavaScript required for AI crawlers. Implement GEO in the base template for site-wide meta tags and JSON-LD. SSGs generate XML sitemaps with lastmod automatically. Astro adds zero JavaScript overhead by default.
GEO for SSGs: Hugo, Jekyll, and Astro Guide
Static site generators (Hugo, Jekyll, Astro) are ideal for GEO: they output pure HTML with no JavaScript required for AI crawlers. Implement GEO in the base template for site-wide meta tags and JSON-LD. SSGs generate XML sitemaps with lastmod automatically. Astro adds zero JavaScript overhead by default.
SSGs have the best possible starting position for GEO: every page is pre-rendered static HTML, served directly from a CDN. AI crawlers get full content without needing to execute JavaScript or wait for hydration.
Hugo
Base Template (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">
<!-- TITLE -->
<title>
{{- if .IsHome -}}
{{ .Site.Title }}
{{- else -}}
{{ .Title }} | {{ .Site.Title }}
{{- end -}}
</title>
<!-- DESCRIPTION -->
{{ 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: /
Sitemap: {{ .Site.BaseURL }}sitemap.xml
Sitemap: {{ .Site.BaseURL }}llms.txt
llms.txt Template (layouts/llms.txt)
Create content/llms.md with layout: llms and outputs: [txt]:
---
layout: llms
outputs:
- txt
---
# {{ .Site.Title }}
> {{ .Site.Params.description }}
## Main Content
{{ range where .Site.RegularPages "Section" "guides" -}}
- [{{ .Title }}]({{ .Permalink }}): {{ .Description }}
{{ end }}
Hugo Sitemap
Hugo generates sitemap.xml automatically with <lastmod> from the page’s lastmod front matter or file modification time. Set enableRobotsTXT = true in config.toml.
Jekyll
_layouts/default.html
<!DOCTYPE html>
<html lang="{{ site.lang | default: 'en' }}">
<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>
Jekyll robots.txt
Create robots.txt in the project root (Jekyll copies it to _site/):
User-agent: GPTBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: *
Allow: /
Sitemap: {{ site.url }}/sitemap.xml
Jekyll’s jekyll-sitemap gem generates sitemap.xml with <lastmod> from last_modified_at front matter.
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 = 'My Company',
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: 'My Site',
logo: { '@type': 'ImageObject', url: new URL('/logo.png', Astro.site).href },
},
datePublished,
dateModified,
mainEntityOfPage: { '@type': 'WebPage', '@id': canonicalURL.href },
}
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title} | My Site</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="My Site">
{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>
Astro robots.txt
Create public/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: *
Allow: /
Sitemap: https://yoursite.com/sitemap-index.xml
Sitemap: https://yoursite.com/llms.txt
Astro Sitemap
Use @astrojs/sitemap integration:
npx astro add sitemap
// astro.config.mjs
import { defineConfig } from 'astro/config'
import sitemap from '@astrojs/sitemap'
export default defineConfig({
site: 'https://yoursite.com',
integrations: [sitemap()],
})
The integration generates sitemap-index.xml with <lastmod> dates automatically from page modification times.
GEO Checklist for SSGs
- Base template: title, description, canonical, author, robots
- Base template: Open Graph with og:type=article, og:title, og:description, og:url
- Base template: article:published_time and article:modified_time from front matter
- Base template: JSON-LD Article schema with publisher and dates
- public/ or static/: robots.txt with all 8 AI crawlers
- public/ or static/: llms.txt with site description and page listing
- Sitemap: generated with lastmod dates (built-in for Hugo/Astro, plugin for Jekyll)
- Front matter: date and lastmod/last_modified_at on all pages
- Content: inverted pyramid structure in Markdown files
- Core Web Vitals: LCP < 2.5s, INP < 200ms, CLS < 0.1