Static Site Setup for SEO and GEO shown as a technical planning workspace with code, documents, and search notes
Use the article as an implementation note: adapt the examples, verify the final HTML, and keep the page updated as the stack changes.

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.

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.txt points to the live sitemap.
  • /sitemap.xml contains only canonical public URLs.
  • /feed.xml or /feed/ returns a real RSS feed if the site advertises one.
  • /llms.txt returns 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.