Architecture & Build System

The site is plain HTML and CSS plus one Node script with no dependencies. You write a page with just a title, a description, and your content; node build.mjs inlines the shared partials and generates everything a machine needs — the full SEO head, JSON-LD structured data, Open Graph tags, an llms.txt, a sitemap, and a robots file — then ships complete, JavaScript-free HTML to public/. The author writes content; the build handles the machinery.

src/ → build.mjs → public/: how the LLM Results build pipeline works
The full pipeline: source files go in, complete JavaScript-free HTML comes out

Why no framework?

The whole point of a GEO site is that crawlers and language models read the content without running code. A framework that renders in the browser hides your content behind JavaScript, so a crawler that doesn't execute JS sees an empty shell. We use none: every shipped page is finished HTML that's complete on first byte. The only build dependency is Node itself — there's nothing to npm install, nothing to keep patched, and nothing that can break at runtime.

Why this matters for GEO

Because the build ships complete HTML with no JavaScript required to render, every AI crawler and language model sees the full page content on first byte — no empty shell, no deferred rendering, no hidden text.

How is the project laid out?

At the repo root: src/ (what you edit), build.mjs (the assembler), public/ (generated output you deploy), site.config.json (site-wide values), and tools/ (the image generator). Inside src/ are the pages, a partials/ folder for shared chrome and ad slots, and assets/ for CSS, images, and the dev-only include loader. Section folders like guides/, handbook/, and services/ become URL paths; files beginning with _ (templates) are skipped by the build.

How do shared parts get reused?

Each page drops a placeholder where shared markup belongs, for example <div data-include="/partials/header.html"></div>. During development you can serve src/ directly and a tiny include.js fills those placeholders at runtime with fetch + innerHTML. For production, build.mjs does the same substitution ahead of time — recursively, so partials can include partials — and bakes the result into the page, so the shipped HTML has no placeholders and needs no JavaScript to render.

How is the SEO head generated?

This is the part that turned the build from an assembler into a system. You never hand-write meta tags. From each page's <title>, description, and path — plus the constants in site.config.json — the build derives the entire head: the canonical URL, Open Graph and Twitter Card tags, a robots directive, a theme-color, and an inline SVG favicon. Because it's all derived, it can't fall out of sync as the site grows, and changing one value in site.config.json updates every page at once.

What about structured data?

Every page gets a JSON-LD @graph with an Organization and WebSite node plus a node whose type is inferred from where the page lives: the home page is a WebSite, a section's index is a CollectionPage, a page inside a section is an Article, and a top-level page is a WebPage — with a BreadcrumbList tying it into the site. A page can override its type in a small <!--meta--> block, which is how the handbook chapters become TechArticles and the services pages emit proper Service schema with a provider and service type. We sell structured data, so ours is generated correctly rather than guessed.

Where do the share images come from?

A separate Python script in tools/ renders a branded 1200×630 Open Graph card for every page, pulling each page's headline and section label so the card reads well, and emits the default card and the logo. The build then wires each page to its card by convention — /assets/img/og/<slug>.png — falling back to the default if one doesn't exist. It's a separate step because image libraries are the one thing we don't want as a core build dependency.

What machine-facing files does it produce?

In the same pass, the build writes three files derived from the same page metadata, so they never drift: llms.txt (a markdown map of the site for language models), sitemap.xml (every indexable page with a last-modified date), and robots.txt (which welcomes the major AI crawlers and points to both the sitemap and the llms.txt). It also fills an automated "latest content" feed on the home page. Add a page and it appears in its section index, the feed, the sitemap, and the llms map with no extra steps.

How do we add a page?

Copy the relevant template, rename it (the filename becomes the URL), write a title, a description, and the content, then run python3 tools/og-images.py and node build.mjs. New top-level files become root URLs; files inside a section folder become paths under it. Everything else — head, schema, image wiring, sitemap, llms.txt — happens automatically.

How is it deployed?

Commit and push. Any static host (Netlify, Cloudflare Pages, GitHub Pages, S3 + CloudFront) serves it; set the publish directory to public/ and the build command to node build.mjs. We author links as root-absolute, but the build rewrites them to page-relative paths, so the output is portable — it works served from a domain root or any subpath. There's no server to run and no runtime to patch, and rolling back is just redeploying an earlier commit.

The short version

Author with includes and write only a title, description, and content; the build inlines the shared parts and generates the head, structured data, images, llms.txt, sitemap, and robots; then ships flat HTML the machines can read. One script, no dependencies, complete pages, and a head that's correct by construction.