2025-12-14
Shipping My First Cloudflare Worker
The contact form on this site used to hit a tiny Express server on a $5 VPS I was paying for and not really using for anything else. I replaced it with a Cloudflare Pages Function this week, mostly out of curiosity about how much of “a backend” you actually need for something this small. Answer: not much.
What got simpler
- No server to patch. The VPS needed occasional
apt upgradebabysitting for a single endpoint. The Worker just runs. - Deploys are part of the same pipeline. The function lives at
src/pages/api/contact.tsin the same Astro repo as the frontend — onegit push, one deploy, no separate service to keep in sync. - Cold starts basically aren’t a thing. V8 isolates instead of containers — I never noticed a cold-start delay in testing, unlike the Express server after it had been idle a while.
What didn’t get simpler
- No filesystem, no long-running process. Fine for a contact form; would matter a lot for anything doing real background work.
- Environment variables work differently. Cloudflare Pages Functions expose bindings through the request context rather than
process.env, which took a minute to get used to coming from Node. - Local dev needs Wrangler.
astro devalone doesn’t fully emulate the Workers runtime — worth runningwrangler pages devagainst the built output before trusting a deploy.
The actual code
Stripped down, the handler is small enough to paste in full:
export const prerender = false;
export const POST: APIRoute = async ({ request }) => {
const { name, email, message } = await request.json();
if (!name || !email || !message) {
return new Response(JSON.stringify({ error: 'Missing fields' }), { status: 400 });
}
// ...validate, then send via an email API...
return new Response(JSON.stringify({ ok: true }), { status: 200 });
};
For a form that gets a few submissions a week, this is now costing me nothing instead of $5/month, and I have one less server to think about. I wouldn’t reach for it yet for something stateful, but for “small function, triggered occasionally, no persistent state” it’s hard to find a reason not to.