Migrating from WordPress to Cloudflare Workers in 2026
Migrating from WordPress to Cloudflare Workers in 2026
After years on a WordPress site that I rarely touched, I finally pulled the trigger on a full rebuild. The new yigittanriverdi.com runs as a single Cloudflare Worker, with Astro 6 for SSR, D1 for content, and an admin panel I can edit from anywhere. Here is the why and the how.
Why now
WordPress was fine. It rendered. It was slow on mobile, the admin felt heavy, and every time I opened it I lost ten minutes deciding whether to publish anything. The friction was real.
Three things changed my mind:
- The Cloudflare stack genuinely is a fullstack platform now. Workers, D1, R2, KV, Cloudflare Access, plus the Vite-based developer tooling. It is not just a CDN anymore.
- I wanted to author posts from my terminal. My day job is in TypeScript. Logging into a CMS feels like pulling on a costume.
- I want to be able to ship the same kind of stack I would build for a client. There is no honest way to recommend a stack you do not use yourself.
The stack
- Single Cloudflare Worker, deployed with Workers Static Assets (the new unified pattern, not Pages).
- Astro 6 in SSR mode for the public site.
- D1 (SQLite at the edge) as the source of truth for posts, projects, and site settings.
- R2 for binary assets (images, the CV PDF).
- Cloudflare Access for the admin route in production.
- Tailwind CSS v4 for styling.
- TypeScript end to end.
The data model
Three tables do the real work:
postsfor the blog (title, slug, body, status, timestamps).projectsfor the home-page cards (title, tagline, description, status).settingsis a single key/value row holding the JSON blob the admin edits.
Drizzle ORM gives me typed queries over D1. The D1 migrations live in src/db/migrations as plain SQL files and are applied with wrangler d1 migrations apply.
Two write paths into the same source of truth
This was the load-bearing decision. I wanted to author from two places: an admin UI and the terminal.
- The admin UI is a small Astro page behind a session cookie. Forms post to
/api/admin/*. - The CLI is a tiny script that POSTs to
/api/postswith a bearer token. From anywhere I have a shell,pnpm new-post --title "..." --body-file draft.mdships a post.
Both paths funnel through the same service layer in src/lib/posts.ts. One source of truth, two ergonomic surfaces.
What surprised me
A few things were less polished than I expected:
- The Astro Cloudflare adapter (v13) emits
dist/server/entry.mjsand tries to validate thewrangler.tomlmainpath at config-load time, before the build has produced it. I had to leavemainout ofwrangler.tomland pass it on the deploy CLI. - The
@cloudflare/vitest-pool-workersv0.14 rewrite dropped the./configexport. I inlinedreadD1Migrationsto apply migrations to the test runtime. - Astro 6 auto-provisions a
SESSIONKV binding for its session feature. If you do not use sessions, set the session driver explicitly tolruCacheso the adapter stops asking for KV.
These are the papercuts you only learn about by shipping.
SEO without ceremony
A server-rendered site lets you cheat at SEO, but I wanted the real signals:
- Server-rendered HTML on every page (no JS required to read the content).
- A
<SEO>component that emits canonical, OpenGraph, Twitter, and JSON-LDBlogPosting,Person, andBreadcrumbList. - Sitemap and RSS generated on request from D1.
- A
robots.txtthat points at the sitemap and disallows/adminand/api. - Human-readable URL slugs validated against
^[a-z0-9]+(?:-[a-z0-9]+)*$server-side.
That is the entire SEO surface. Google does the rest.
What is next
I am writing this on the same site. The blog is live, the admin panel works, the CV is one click away. Next up is a nightly cron that exports D1 to git as markdown for free version history.
The RSS feed is the lowest-friction way to catch the next post.
Built on Cloudflare, in Berlin, in 2026.