Part 0 was the manifesto: I build things constantly and share almost none of them because the publishing step is a context-switch too far. Part 1 was the planning: technology selection, cost analysis, two pre-built themes that looked perfect on paper and failed on a fundamental layout incompatibility, and the decision to build custom.
This is Part 2: the build. Another six hours.
Clean Slate
The first thing we did was rip out every trace of the Congo theme we’d rejected in Part 1. Go module files, split config directories, Tailwind color schemes, Congo-specific partials. Fourteen files deleted, one new hugo.toml created. We committed that cleanup as its own rollback point before writing a single line of new code.
This is a habit I picked up from years of production incident response: before you start fixing, create a known-good state you can revert to. It costs thirty seconds and buys you unlimited confidence to move fast. Claude didn’t suggest this; I did. The AI is great at writing code but it doesn’t have scar tissue from that one time a “quick fix” cascaded into a four-hour rollback.
Extracting the System (Not Just the Styles)
Part 1 established the principle: extract from the source, don’t guess from screenshots. Part 2 is where that principle really paid off.
I had Claude explore the Squarespace site mirror HTML and CSS while I pulled up the live site side-by-side. We were looking at section background colors, and my first instinct was wrong. I described the pattern as “cream, puce, cream, navy, puce.” Close, but not quite. What the source actually had was a systematic alternation: two light colors trading off, with a dark accent that could appear between any two light sections but never back-to-back.
We codified it into three CSS classes:
| Class | Color | Layout Direction |
|---|---|---|
section-cream | Warm cream | Text left, image right |
section-lavender | Muted lavender | Image left, text right |
section-navy | Dark navy | Centered, light text |
The rules: alternate cream and lavender. Navy can appear between any two, but never two of any section in a row. Footer and header are always lavender. Footer is the only place where the “never two of any section in a row” constraint is allowed to be violated if necessary.
This sounds obvious in hindsight. It was not obvious while I was staring at side-by-side browser windows trying to figure out why the homepage didn’t “feel” right. We went through salmon backgrounds (too saturated), the wrong section order (I had the navy pillars in the middle instead of at the bottom), and multiple rounds of “close but something’s off” before the pattern clicked. The insight: the source design wasn’t a list of individual color choices. It was a system. Once we extracted the system instead of copying individual values, every page fell into place. The homepage, the services page, the blog listing, the about page: they all just followed the rules.
This is the same lesson from Part 1, refined: don’t extract values. Extract patterns. A color is a data point. An alternation rule is a design system. The system is what makes new pages effortless.
The Six-Line Deploy Script (That Took Two Hours)
#!/usr/bin/env bash
set -euo pipefail
docker run --rm -v "$PWD:/src" hugomods/hugo:latest hugo --gc --minify
CLOUDFLARE_API_TOKEN=$(grep CLOUDFLARE_API_TOKEN .env | cut -d= -f2) \
CLOUDFLARE_ACCOUNT_ID=$(grep CLOUDFLARE_ACCOUNT_ID .env | cut -d= -f2) \
./node_modules/.bin/wrangler pages deploy public/ --project-name=theclouditect --branch=main
Six lines. Two hours to get there. Here’s why.
First, npx wrangler downloads the package on every invocation. That’s the same anti-pattern as pip install into system space: it works until it doesn’t, and you don’t control the version. I insisted on a project-scoped npm dependency, same philosophy as using uv for Python. Claude pushed back mildly (“npx is the standard approach”), but I’ve been burned by transient dependencies before and I wasn’t interested in being burned again.
Second, npm run deploy doesn’t reliably pass environment variables to subprocesses. We discovered this empirically: the token worked with wrangler whoami but silently failed with pages deploy. This took longer to debug than it should have because the failure was silent (exit code 1, no output). The fix was calling ./node_modules/.bin/wrangler directly instead of going through npm’s script runner.
Third, source .env had variable passing issues that remain unresolved. Figuring it out and fixing it was a “don’t let the perfect be the enemy of the good” situation. Maybe i’ll resolve it at some point when I have nothing better to do (“nothing better to do!” HAHAHAHAHA - BAZINGA!). The grep-based inline extraction is ugly but reliable.
Fourth (and this was the real head-scratcher): Wrangler’s whoami and pages deploy hit different API endpoints. whoami uses a token verification endpoint. pages deploy calls /memberships to auto-discover your account. That endpoint is user-scoped. Our token was account-scoped (which is what Cloudflare recommends for CI/CD). Account-scoped tokens cannot access user-scoped endpoints. The fix: provide CLOUDFLARE_ACCOUNT_ID explicitly so Wrangler never needs to call /memberships at all.
I see this pattern constantly in DevOps work: the finished automation looks trivial, but the knowledge baked into it is not. Every line in that script exists because something else didn’t work. The six lines are the distilled residue of four distinct failures, each with its own debugging cycle, documentation rabbit hole, and “oh, that’s why” moment.
An AI assistant can write you a deploy script in thirty seconds. It will probably use npx. It will probably use source .env. It will probably not know about the /memberships endpoint issue. You need the domain knowledge to recognize which “it works on my machine” patterns will break in production and which shortcuts will cost you time later. The AI accelerates the iteration; the human provides the judgment about which iteration to keep.
The Details That Add Up
A few smaller things that mattered:
Contact form. Web3Forms, free tier, 250 submissions/month. Three layers of spam protection (hCaptcha, honeypot field, server-side filter). The non-obvious problem: after submission, Web3Forms redirects the user to a generic success page in the same tab, stranding them with a back button that reloads stale form data. We added a custom thank-you page and set the redirect URL using Hugo’s baseURL template variable, so it resolves to localhost:1313 in dev and the production domain automatically.
Pillar icons. The three icons on the homepage (DevOps, FinOps, Software Engineering) were Squarespace’s generic stock icons: a cursor, a heart, and a people silhouette. I replaced them with inline SVGs: an infinity loop, a bar chart with trend line, and binary digits. Zero external dependencies, instant rendering on the navy background.
About page. Headshot, two-paragraph bio, resume PDF download, and a credits section. The bio was generated by Claude after reading my existing blog posts to match my voice. It’s the “good enough for v1” approach: ship it, refine later when I have a specific reason to.
Footer email. A mailto: link. Obvious, but I almost missed it because I was focused on visual fidelity with the source. Small things like this are exactly the kind of detail that AI-assisted development handles well when you’re reviewing the output with your “would a customer actually click this?” hat on.
Documentation as Pipeline Infrastructure
This is the part that connects back to the thesis of this series. In Part 0, I said the bottleneck was never the content; it was the pipeline. Documentation is part of that pipeline.
Every significant decision in this build has an Architecture Decision Record (ADR) paired with a Decision Log (DL). The ADR is lean: context, decision, rationale, consequences. The DL holds the messy stuff: alternatives ruled out, implementation details, things that surprised us. They share a numbering scheme (ADR-004 pairs with DL-004) so you can always find the companion.
We ended this session with six ADRs: custom theme approach, design extraction methodology, no dark mode, three-color section system, deploy pipeline, and secrets management. Each one answers a question someone (including future me) will ask: “why did you do it this way?”
The marginal cost of writing these was close to zero. They were generated from the same working context that produced the code. This is the AI-native workflow advantage: if you structure your working sessions around skills that know how to produce documentation artifacts, the docs materialize alongside the code. You don’t write them after the fact as a separate chore. They’re a byproduct of the process, not an addition to it.
A site manual (docs/site-manual.html) ties it all together: how to run the dev server, deploy, add content, and understand the design system. One HTML file, anchor-based navigation, styled in the brand palette. It’s the document I wish existed for every project I’ve ever inherited.
Is this overkill for a five-page static site? I keep asking myself that. The answer I keep arriving at: the documentation isn’t for the site. It’s for the pipeline. The site will grow. The pipeline will carry it.
The Series
- Part 0: The What: Why I built a publishing pipeline instead of just writing more posts on the platform I already had.
- Part 1: The Why: Technology selection, the napkin math, standing up the project with Claude Code, and the theme trap that reminded me to plan before I build, even when I’m excited about building.
- Part 2: The How (you’re here): Custom theme implementation, the three-color section system, contact form integration, and deploying to Cloudflare Pages.
- Part 3: Baseline SEO: A 30-year technologist who’s never done SEO uses Claude to learn the basics and optimize a Hugo static site.
- Part 4: Content SEO: Making every page worth indexing. Tag page enrichment, internal linking, and the SEO that actually matters for a small site.
- Companion: DNS Cutover: The full DNS migration to Cloudflare, and why AI-assisted planning is no substitute for infrastructure review.
- Companion: DNS Cutover: The full DNS migration to Cloudflare, and why AI-assisted planning is no substitute for infrastructure review.
The Clouditect helps businesses simplify their technology. If you’re overpaying for platforms you’ve outgrown, or your tech stack is working against you instead of for you, let’s talk.