Static Site Generation

Lesson 7 — Deployment & static generation

The Problem

The web application runs FastAPI with server-side template rendering and JSON API endpoints. GitHub Pages serves only static files — no Python, no server-side logic. We need to pre-generate every page and every API response as files.

The Approach

scripts/build_static.py uses FastAPI's TestClient to request every page and API endpoint from the running application, then writes the responses to files:

HTML pages  → _site/occupation/11-1011/index.html
API JSON    → _site/api/occupations/11-1011.json

A JavaScript "fetch shim" is injected into every HTML page's <head>. It intercepts fetch() calls to /api/ URLs and redirects them to the corresponding .json files. The page's JavaScript doesn't know it's running on a static site — it fetches data the same way it would from a live server.

How the Fetch Shim Works

Page JS calls:  fetch('/api/occupations/11-1011/wages?geo_type=state')
Shim maps to:   fetch('/api/occupations/11-1011/wages-state.json')

For search, the shim downloads the full occupation index once, caches it, and filters client-side — no server needed.

For geography comparison trends, the shim maps the soc_code query parameter to a filename:

fetch('/api/trends/compare/geography?soc_code=11-1011')
→ fetch('/api/trends/compare/geography-11-1011.json')

The Limitation: Combinatorial Endpoints

The /api/trends/compare/occupations?soc_codes=11-1011,13-1011,15-1211 endpoint accepts arbitrary combinations of SOC codes. With ~870 occupations, the number of possible combinations is astronomical. You cannot pre-generate all of them.

Key insight: Static site generation is inherently limited for endpoints with combinatorial query parameters. The static site accepts graceful degradation here — comparison features have reduced functionality compared to the live server.

Path Rewriting for GitHub Pages

GitHub Pages serves this site at /jobclass/ (a subpath), not at the root. Every absolute URL in the HTML (/api/, /static/, /occupation/, /trends/) must be rewritten to include the base path (/jobclass/api/, etc.). The rewrite_paths() function handles this with string replacements before the shim is injected.

The shim itself does NOT need path rewriting — it extracts the base path from the URL at runtime using u.indexOf('/api/').

Adding New Routes to the Static Site

Every new route or API endpoint in the web app must also be added to build_static.py. The checklist:

  1. Add HTML page generation (the write_html() calls)
  2. Add API JSON generation (the write_json() calls)
  3. Add path rewriting entries for new URL prefixes
  4. Update the fetch shim if the endpoint uses query parameters that need filename mapping
  5. Rebuild and deploy