Skip to content
devzakaria — home

How this site works: Astro on Cloudflare Workers, edited from D1

A portfolio that is fast, cheap to run and still has a real CMS. The stack behind devzakaria.com — Astro, Cloudflare Workers, D1, KV, R2 — plus the caching bugs I found along the way.

Zakaria Binsaifullah · · 4 min read

When I rebuilt this site I wanted three things that usually pull against each other: pages that load instantly anywhere in the world, a proper admin where I can write posts and manage leads without redeploying, and running costs close to zero. It runs entirely on Cloudflare. Here is how it fits together, and a few bugs worth knowing about if you build something similar.

The stack

  • Astro renders every page, with a handful of small interactive islands (Preact) for things like the command palette, the editor and the forms.

  • Cloudflare Workers run the site — one Worker serves pages, the API and the admin.

  • D1 (SQLite at the edge) stores posts, case studies, products, testimonials, leads, settings and redirects. Queries go through Drizzle.

  • KV caches query results; the Cache API caches rendered pages.

  • R2 stores images and video, served through Cloudflare Image Transformations as AVIF/WebP.

  • Cloudflare Access protects /admin, and the Worker verifies the Access token itself as well.

  • Turnstile, a honeypot and per-IP rate limits protect the forms; Resend sends the emails; Cal.com handles bookings and calls a signed webhook when someone books.

There is no separate CMS service and no database server to babysit.

Pages from D1, cached at the edge

Public pages are rendered on request from D1, then cached. Rendering on request means a post I publish in the admin is live immediately — no build, no deploy. Caching means visitors almost never wait for that render.

The trick that makes this simple is a content version. Every cache key includes a version number stored in KV. Publishing anything bumps the version, which makes every older cache entry unreachable at once. No purging individual URLs, no stale index pages listing a post that was unpublished.

Bugs worth sharing

Three caching problems showed up after launch, and all three are easy to hit on Workers:

1. Cached HTML pointing at old assets. The page cache was keyed by content version only. After a deploy, cached HTML could still reference the previous build's JavaScript files. The fix: add the build ID to the page cache key, so a deploy is a clean slate.

2. Cached settings surviving a deploy. The same mistake one layer down: query results in KV didn't include the build ID, so a deploy that changed a default value kept serving the old merged settings for up to five minutes. Same fix.

3. Browsers holding pages for four hours. The response sent s-maxage for the edge but no browser max-age. Cloudflare's zone-level Browser Cache TTL filled the gap with four hours, so returning visitors could see pages that were hours old. The fix was to send max-age=0 for HTML — browsers revalidate, the edge absorbs the traffic — and set the zone to respect origin headers. Hashed assets keep a one-year immutable cache.

The editor

Posts are written in a Tiptap editor in the admin: slash commands, code blocks, images from the media library, embeds, autosave every ten seconds and a preview. The document is stored as JSON and rendered to HTML with Tiptap's static renderer when saved.

One Workers-specific lesson: libraries that need a DOM don't run in the Worker runtime. Tiptap's usual server-side HTML helper depends on one, so the Worker uses the static renderer instead, and the Markdown import converts files in the browser. Code highlighting uses Shiki with its JavaScript regex engine for the same reason — the WebAssembly engine wasn't allowed during prerendering.

Built for search engines and AI assistants

Every page has a canonical URL, Open Graph tags, generated social images and a JSON-LD graph. There are sitemaps, an RSS feed with full content, and IndexNow pings when something is published. Posts and case studies are also available as plain Markdown, and an llms.txt file summarises the site for AI assistants — the robots file says what crawlers may do with the content.

Security and quality checks

A Content Security Policy with script hashes, strict security headers, a same-origin check on every admin write, and Access in front of the admin. On every push, GitHub Actions runs type checks, linting, unit tests and end-to-end tests in Chrome, Firefox, Safari and mobile browsers, including accessibility checks in light and dark mode and a run with JavaScript turned off.

Was it worth it?

For a site like this — mostly content, a little interactivity, a private admin — yes. Pages are fast from Dhaka to San Francisco, publishing is instant, and the monthly bill is a rounding error. The trade-off is that you own the plumbing: caching, auth and email are yours to get right. That's the kind of work I enjoy, and it's what I do for clients too.

If you want a site or SaaS built on this kind of stack, book a 20-minute call or send a short brief.

Available now — can start right awayDhakaGMT+6I overlap with US mornings and EU afternoons

Let's build it.

Tell me what you're building. You'll talk to me, not an account manager.

  • Reply within 12 hours
  • You own 100% of the code
  • Every agent-written line is reviewed and tested by me
esc