all writing

Why I Moved Everything Off Netlify

I migrated my personal site and all client projects from Netlify to Cloudflare Workers. Here's what the move actually looked like.

3 min read

AI-generated isometric diagram of connected cloud shapes and network paths, in cyan on a dark charcoal background
On this page

I moved my personal site and every client project off Netlify. They all run on Cloudflare Workers now. Netlify had been my default for years — I recommended it to clients and wrote about it on this blog. Two things changed that.

First, the billing model shifted to credits. Credits get consumed by builds, bandwidth, and function invocations, which means pushing a fix can cost you money. That’s not the experience that made Netlify great.

Second, the product direction pivoted hard toward AI features while the free tier kept shrinking. I’m not anti-AI — I use it daily. But when a hosting platform prioritizes AI tooling over build performance and function limits, it tells you who the audience is now, and it’s not me.

The Numbers

FeatureNetlify (Pro)Cloudflare (Workers Paid)
Monthly Cost$19/member$5 flat
RequestsCredit-based10 million included
Bandwidth1TB (credit-based)Unlimited (static assets)
Serverless FunctionsLimited invocations10M requests/month
DatabaseNone built-inD1 (SQLite) included
Key-Value StoreBlob storageWorkers KV included
Object StorageNoneR2 (zero egress)
Edge ComputeEdge FunctionsWorkers (every request)
Build MinutesCredit-based3,000/month
Pricing as of March 2026.

For $5/month I get Workers, Workers KV, D1, R2 (zero egress), Durable Objects, and Queues. I’m running form handling, database queries, rate limiting, email integrations, and static hosting on that same plan. On Netlify each of those is a separate concern with its own billing.

The Migration

Swap the Astro adapter. Replace @astrojs/netlify with @astrojs/cloudflare, add a wrangler.jsonc, update dependencies. For a static site the adapter generates a Worker that serves pre-rendered pages from Cloudflare’s asset storage.

The one gotcha was images. Netlify handles Sharp at runtime; Workers don’t support it. Set imageService: 'compile' to optimize at build time instead, and keep Sharp as a dev dependency.

// astro.config.mjs
import cloudflare from '@astrojs/cloudflare';

export default defineConfig({
  output: 'static',
  adapter: cloudflare({
    imageService: 'compile',
  }),
});

The bigger migration task was the contact form. I’d been using Netlify Forms — drop a data-netlify attribute, submissions show up in your dashboard. Convenient until you leave.

I replaced it with a Worker that accepts submissions, validates with Cloudflare Turnstile, stores in D1, and sends notification emails. Same $5 plan. Now I own the pipeline, the email templates, and the submissions data, and adding a new client site takes minutes.

DNS was the easiest part. Point nameservers to Cloudflare, add a proxied AAAA record to 100:: for the root and a CNAME for www. SSL and caching come with it.

Client Sites

Same process for every client site. By the second one I had it down to about 30 minutes: swap adapter, add Wrangler config, rewire the form, update DNS.

One client is a local HVAC company — a few hundred visitors a week. On Netlify the free tier was fine until it wasn’t, and any Google ad campaign made me nervous about overages. On Cloudflare the same site costs nothing. Static assets free, Worker handles the form, Turnstile handles spam.

Closing

Netlify isn’t broken. It still deploys sites. But the pricing model is opaque, the limits are tight, and the direction is AI features over developer experience.

Cloudflare’s pricing is flat, the breadth of services at $5 is hard to beat, and it all lives in one ecosystem. If you’re evaluating platforms, do the comparison honestly. For my use cases it wasn’t close.

written by

Cody Williamson

Senior Software Engineer