Build Layer
Static Site Builds With Plain Python
Every site we run is written by one Python script that uses only the standard library and outputs plain HTML. A checker reads every page after each build and stops a broken link before a customer finds it.
- Year 1Website
- Year 2Map Listings
- Year 3Booking Tool
- Year 4Front Desk
- Year 5Payments
- Year 6Reports
Same six pieces.
Ten layers, already running.
The Short Version
- No framework means nothing to upgrade and nothing that breaks on its own.
- A checker tests links, anchors, forms and meta lengths after every build.
- One origin setting drives canonicals, schema and the sitemap, so they never disagree.
- Fingerprinted CSS and JS mean a visitor never sees an old style on a new page.
| Language | Python, standard library only |
|---|---|
| Output | Plain HTML, CSS and JS |
| Framework | None |
| Checks per build | Links, anchors, forms, meta |
| URLs | Clean, no .html |
| Cache busting | Content hash in file names |
- ContentPage dataPhotosFAQs
- Build ScriptTemplatesOrigin constantFingerprints
- Output FolderHTML pagesSitemapSchema
- CheckerLinks and anchorsFormsMeta lengths
- DeployEdge hosting
Why a Plain Script Beats a Framework Here
Most web tools want you to install a framework. That framework pulls in hundreds of packages written by strangers. A year later one of them has a security alert. Another one changed how it works. Now your site will not build, and you did not touch it.
A business site does not need that. It needs pages that say the right thing and load fast. So we write each site with one Python script. It uses only the standard library that ships with Python. There is no package list to go stale.
That script reads the page content and writes plain HTML files. You can open the output in any browser. You can read it in any text editor. If we vanished tomorrow, another developer could host those files anywhere in an hour.
One Origin Setting Drives Everything
A site states its own address in many places. The canonical tag on each page. The schema that tells Google who you are. The sitemap. The share cards. The feed.
If those are typed by hand, they drift. One page points to the www address and another points to the bare one. Google sees two sites and splits your ranking between them.
Our build has one origin constant at the top. Every canonical, every schema block and every sitemap line is built from it. Change it once, rebuild, and every page agrees. That is a whole class of SEO mistake that simply cannot happen.
The same goes for clean URLs. Each page is written as a folder with an index file inside. So the address is /about/, not /about.html. The link a customer shares today still works if we rebuild the site next year.
The Checker Runs After Every Build
A build that finishes is not a build that works. So right after the script writes the pages, a second script reads every one of them back. It looks for the mistakes a person misses.
- A link to a page that does not exist.
- A link to a spot on a page, like
#pricing, where that spot is gone. - A form with nowhere to send.
- A title or description too long for Google to show in full.
If it finds one, the deploy stops. Here is a trimmed version you can run on any folder of HTML.
import re
import sys
from pathlib import Path
OUT = Path("dist")
def url_for(path):
rel = path.relative_to(OUT).as_posix()
return "/" + rel.removesuffix("index.html")
pages = {url_for(p): p.read_text(encoding="utf-8") for p in OUT.rglob("*.html")}
problems = []
for url, page in pages.items():
for href in re.findall(r'href=["\'](/[^"\'#?]*)', page):
if href not in pages and not (OUT / href.lstrip("/")).is_file():
problems.append(f"{url} links to missing {href}")
title = re.search(r"<title>(.*?)</title>", page, re.S)
if not title or len(title.group(1)) > 60:
problems.append(f"{url} title missing or over 60 chars")
print("\n".join(problems) or "0 problems")
sys.exit(1 if problems else 0)The real one checks more, but the idea is the same. It reads the output, not the source, because the output is what customers get.
How Fingerprints Keep Styles Fresh
Browsers save a copy of your CSS and JS so the next page loads faster. That is good until you change the design. Then some visitors get the new page with the old style. Buttons end up in the wrong spot.
Our build puts a short hash of each file into its name. When the file changes, the name changes. Every browser has to fetch the new one. When it does not change, the browser keeps its saved copy for a year.
import hashlib
from pathlib import Path
def fingerprint(path: Path) -> str:
digest = hashlib.sha256(path.read_bytes()).hexdigest()[:10]
return f"{path.stem}.{digest}{path.suffix}"So site.css ships as something like site.3f9a1c07be.css, and every page points to that exact name.
Where the Content Lives
The words, photos and FAQs for each page sit in plain data files, apart from the layout. The build reads them and pours them into templates. That split does two things for you.
First, a copy change never touches the design. Fixing a price line or adding a service area is an edit to one data file. The layout cannot break because nobody opened it.
Second, the build can check the content itself. Every page must have a title and a description. Every photo must have alt text. Every FAQ that shows on the page also goes into FAQPage schema, and no schema goes out for a question the page does not show. Google's rules say schema has to match what the page shows, so the two are built from the same list.
Photos get the same care. The build resizes each one for phones and desktops, converts it to WebP, and keeps the location tag on the file. See search and indexing for why that tag matters.
What It Saves the Owner
You never pay for a framework upgrade. You never hear that your site needs a rebuild because a tool went out of date. You never find a dead link from a customer email.
A change goes like this. You tell us what to fix. We edit the content, run the build, and the checker passes or it does not. If it passes, it ships to the edge the same day. If it does not, we know which page and which link, before anyone else sees it.
This is the same build that turned out our design library of 210 website mockups across dozens of industries.
What We Learned the Hard Way
Fix the generator, not the output. It is tempting to patch one HTML file by hand. The next build writes over it and the bug comes back. Worse, it comes back weeks later when nobody remembers why.
Watch for stale generators too. A site can have an old script that still writes one section. You fix the main build and that section stays wrong. We now grep the output for the old text after a fix, to prove it is gone.
And check the page a customer sees, not the one you meant to write. The checker exists because reading your own source always looks right.
Next, see how we keep that output fast on a phone in the mobile speed score guide, or move down the stack to the database layer.
Questions People Ask
Can I still edit my own site if there is no WordPress?
You tell us what to change and we ship it the same day. The content lives in simple data files, so there is no admin panel to break or get hacked.
What happens if you stop working on my site?
You keep plain HTML files that run on any host. There is no framework or license tying them to us.
How do you make sure a new page does not break an old link?
A checker reads every built page and tests every internal link and anchor. If one points nowhere, the deploy stops until it is fixed.
Why does my address not end in .html?
Each page is a folder with an index file, so the address stays clean. It reads better and it still works if the site is rebuilt later.
We sell time
Get Years of Building Switched On in Hours
A thirty minute call. A written price. Nothing built until you say yes.