Render your blog into the site at build time

A marketing site and a blog usually live in different systems, and the join between them is where things quietly rot. The common answer is to fetch posts in the browser. We think that is the wrong…

Share
Render your blog into the site at build time

A marketing site and a blog usually live in different systems, and the join between them is where things quietly rot. The common answer is to fetch posts in the browser. We think that is the wrong default.

Two reasons not to fetch in the browser

Most hosted CMS content APIs send no CORS headers, so the page cannot read them directly without a proxy you now have to run and monitor.

More importantly, a static marketing page should not depend on a third party being reachable when someone loads it. If the CMS is down, a client-side fetch turns a working page into an empty section — or a spinner that never resolves.

Rendering at build time

Fetch the posts during the build, write the cards into the HTML, and ship a static file:

posts = fetch_published(limit=3)
before, rest = source.split(START, 1)
_, after = rest.split(END, 1)
result = before + START + render(posts) + END + after

The markers make the operation idempotent and the diff readable. The cost is staleness — a post published today only appears on the next deploy — which a scheduled rebuild and a publish webhook between them reduce to about a minute.

The check that matters

Generating into an existing file means you can delete parts of it. Ours did, on the first run: a regex ran past the end of the section and the render removed the contact form below it. The page looked perfect, because the part we were testing was correct.

So the generator now refuses to write when anything outside its own region changes:

for landmark in ('id="contact"', 'class="footer-bottom"', "</html>"):
    if source.count(landmark) != result.count(landmark):
        raise SystemExit(f"refusing to write: {landmark!r} count changed")

It is five lines, and it is the difference between a build step you trust and one that can quietly remove your only conversion path.