React SPA with APIs on Cloudflare Worker

Sprited DevSprited Dev
4 min read

Cloudflare is amazing. Think of it as AWS’s thrifty cousin: it can do almost everything, but won’t drain your wallet. Then there’s Vercel, the flashy nephew in a sports car — a fun free ride, but only if you play by their rules.


Options

Vercel Functions

I tried a Vite React SPA with Vercel Functions for APIs, and it was almost too easy. Drop an endpoint .ts file in the api folder and it just works — no extra setup.
Still, Vercel feels like Heroku: trendy, convenient, but risky for a long-term relationship. I don’t want to be locked into their ecosystem.

AWS

I got burned by AWS in college — surprise bills and high usage costs. That was over a decade ago, but it left a scar.AWS has the “premium“ image, but for me it’s a no-go.

Google Cloud Run

Pixel suggested Google Cloud Run. But my history with Google App Engine wasn’t great — the app I built no longer runs because the frameworks were all decommissioned. Competitive pricing aside, I don’t want to step into Google’s ecosystem again.

Fly.io

Fly.io is neat: you provide a Docker image, and they’ll run it. But large images timed out for me, and the community feels smaller. I decided to skip it.

Cloudflare Workers

Now to my favorite: Cloudflare. It offers almost everything you need for SaaS infra at a fraction of AWS’s cost. Generous free tier, solid docs, and a company that’s been strong since 2004.
Their pricing is straightforward — more like Amazon retail than AWS. The Cloudflare brand stands for stability, consistency, and value.

The UI may look a little old-school, but that actually gives it a charming, no-frills vibe. No excessive border-radius here.

When I discovered Cloudflare Workers could handle both SPA hosting and API endpoints, I knew I had to try it.


Set Up Guide

The guide I followed:
https://developers.cloudflare.com/workers/vite-plugin/tutorial/

It’s exactly what I needed. Deploying an SPA with minimal APIs and high availability felt simple.

You install @cloudflare/vite-plugin and add it to vite.config.ts. After that, you can use the usual vite dev server. No ecosystem-specific commands like vercel dev or next dev.
I like that — it’s just a plugin, not a lock-in.

// vite.config.ts
export default defineConfig({
  plugins: […, cloudflare()]
})

Now, just run npm run dev. Your SPA, APIs, HMR, and React Refresh all work. The API even runs inside a proper V8 runtime like production.

You’ll need wrangler for deployments and a wrangler.jsonc config:

{
  "name": "sprite-dx",                               // Project name
  "compatibility_date": "2025-08-20",                // Just use today's date
  "assets": {
    "directory": "dist",                             // SPA to use when deployed.
    "not_found_handling": "single-page-application", // This routes all non-matching urls to the SPA
    "run_worker_first": ["/api", "/api/*"]           // Route /api/* to worker
  },
  "main": "./worker/index.ts"                        // API endpoint entrypoint.
}

Then add a worker-specific tsconfig.json:

// conf/tsconfig.worker.json 
{
  "extends": "./tsconfig.node.json",
  "compilerOptions": {
    "tsBuildInfoFile": "../node_modules/.tmp/tsconfig.worker.tsbuildinfo",
    "types": ["@cloudflare/workers-types/2023-07-01", "vite/client"],
  },
  "include": ["../worker"],
}

// tsconfig.json
{
  "files": [],
  "references": [
    { "path": "./conf/tsconfig.app.json" },
    { "path": "./conf/tsconfig.node.json" },
    { "path": "./conf/tsconfig.worker.json" }   // added
  ]
}

And your worker entrypoint:

// worker/index.ts
import type { ExportedHandler } from '@cloudflare/workers-types/2023-07-01';

export default {
  fetch(request) {
    const url = new URL(request.url);

    if (url.pathname.startsWith("/api/")) {
      return Response.json({
        name: "Cloudflare",
      });
    }

    return new Response(null, { status: 404 });
  },
} satisfies ExportedHandler;

At this point, npm run dev gives you a working SPA + API locally.


Deployment

In package.json, add:

   "scripts": {
     "dev": "vite",
     "build": "tsc -b && vite build",
     "lint": "eslint .",
     "preview": "vite preview",
+    "deploy": "npm run build && wrangler deploy"
   },

Deploy with:

npm run deploy

You don’t even need to create a project beforehand — the first deploy sets it up automatically. You’ll find it under Cloudflare > Compute (Workers) > Workers & Pages.


Yeah, the whole experience has been smooth. Next for me: adding environment variables, connecting it to RunPod Serverless with Comfy installed, and testing a full end-to-end image generation workflow at app.spritedx.com.

— Sprited Dev 🌱

0
Subscribe to my newsletter

Read articles from Sprited Dev directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sprited Dev
Sprited Dev