Implementation
Static Site Setup for SEO and GEO
How to set up a static content site for SEO and GEO with pre-rendered HTML, sitemap generation, robots.txt, RSS, llms.txt, schema, and responsive templates.
A static site is a strong fit for a technical content library. It can ship fast pre-rendered HTML, stable URLs, generated metadata, sitemap output, RSS, and llms.txt without relying on a database for every request.
Recommended file structure
site/
public/
assets/
css/
images/
js/
src/
site-data.mjs
scripts/
build.mjs
dist/
index.html
sitemap.xml
robots.txt
feed.xml
llms.txt
Content registry pattern
A content registry keeps pages, metadata, and sitemap output in sync. This site uses that pattern: page data is stored once, then the build script renders HTML, JSON-LD, sitemap, RSS, and llms.txt.
const article = {
slug: "metadata-canonical-url-examples",
title: "Metadata and Canonical URL Examples",
description: "Copy-ready examples for metadata and canonical URLs.",
date: "2026-05-30",
category: "SEO",
body: "<p>Article HTML goes here.</p>"
};
Build outputs to generate
- One static HTML file per public URL.
- A sitemap that lists canonical public URLs.
- A robots file that points to the sitemap.
- An RSS feed for article discovery.
- An llms.txt file that curates the best explanatory pages.
- JSON-LD on each page for publisher, page type, article, and breadcrumbs.
Why pre-rendered HTML helps
Pre-rendered HTML makes the page readable to browsers, crawlers, text extraction tools, and answer systems before optional JavaScript runs. It also makes local validation easier: you can inspect files in dist/ and confirm that the content is present.
Minimal build validation script
import { readFile, readdir } from "node:fs/promises";
import path from "node:path";
async function findHtmlFiles(dir) {
const entries = await readdir(dir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) files.push(...await findHtmlFiles(full));
if (entry.isFile() && entry.name.endsWith(".html")) files.push(full);
}
return files;
}
for (const file of await findHtmlFiles("dist")) {
const html = await readFile(file, "utf8");
if (!html.includes("<link rel=\"canonical\"")) {
throw new Error("Missing canonical: " + file);
}
if (!html.includes("application/ld+json")) {
throw new Error("Missing JSON-LD: " + file);
}
}
Deployment note
Whether the final site is served from a static host or published into a CMS, the principle stays the same: the public page should contain the article body, metadata, canonical URL, and structured data in the delivered HTML.
Deployment checklist
A static build is only useful if the deployed version matches the generated files. Check the live response after deployment, especially when a CMS, CDN, or hosting rule sits in front of the files.
- Every public URL returns 200 and the canonical URL matches the final path.
/robots.txtpoints to the live sitemap./sitemap.xmlcontains only canonical public URLs./feed.xmlor/feed/returns a real RSS feed if the site advertises one./llms.txtreturns plain text if the site publishes one.- Images referenced in HTML and Open Graph tags return 200.
Headers and redirects
Use one canonical host and one URL format. Redirect http to https, redirect the non-preferred host if needed, and avoid chains. A single clean redirect is easier to debug than several rules split between a CDN, host, and CMS.
Preferred:
http://example.com/page -> https://example.com/page
Avoid:
http://example.com/page -> https://www.example.com/page -> https://example.com/page/
Keep generated files in the same content registry
The sitemap, RSS feed, llms.txt, JSON-LD, and article cards should come from the same article list. When a URL changes, one data change should update the page, sitemap, feed, and documentation map together. That is the main operational advantage of a static content system.
Static output smoke test
Run a smoke test after every build. It should fail loudly when a required file or page signal is missing.
const requiredFiles = [
"dist/index.html",
"dist/sitemap.xml",
"dist/robots.txt",
"dist/feed.xml",
"dist/llms.txt"
];
for (const file of requiredFiles) {
await fs.access(file);
}
When publishing static content into WordPress
If the final host is WordPress, remember that WordPress owns the document head, sitemap, robots output, and social metadata unless you explicitly configure or override them. Publishing static article HTML into WordPress content is useful, but you still need to verify the live WordPress output for title, canonical, schema, featured image, and author fields.
Practical rollout notes
Use this guide when choosing how to maintain a small technical library. Even if the final site is WordPress, the static-site discipline helps keep content, metadata, sitemap, feed, and llms.txt generated from one source of truth.
Acceptance criteria
Page: Static Site Setup for SEO and GEO
Reader task: clear in the introduction
Implementation proof: examples, tables, commands, or checklist present
Trust proof: dates, author or publisher context, and source links where needed
Maintenance proof: revisit trigger documented
- The build generates all public files from the same content data.
- The live CMS output is checked separately from local static output.
- Images and metadata are rewritten to production URLs.
- Publishing does not leave stale pages, slugs, or redirects behind.
When to revisit
Revisit when the site changes host, CMS, permalink rules, or publishing automation.

