Vite + React SPA on Cloudflare Workers
The simplest deployment: a client-side React app built with Vite, deployed as static assets on Workers. No SSR, no server code, zero cold starts. Uses the CF Vite Plugin for static asset serving configuration.
Create a New Project
npm create cloudflare@latest my-spa -- --framework=react
Or add Cloudflare to an existing Vite + React project:
npm install -D @cloudflare/vite-plugin
Project Structure
my-spa/
├── src/
│ ├── App.tsx # Root component
│ ├── main.tsx # Entry point
│ └── index.css
├── public/ # Static files
├── index.html # HTML template
├── vite.config.ts # Vite + CF plugin
└── wrangler.jsonc # Cloudflare config
Vite Config
// vite.config.ts
import { cloudflare } from "@cloudflare/vite-plugin";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
cloudflare(),
react(),
],
});
App Component
// src/App.tsx
import { useState, useEffect } from "react";
function App() {
const [data, setData] = useState<string | null>(null);
useEffect(() => {
// Fetch from your API (could be a separate Hono Worker)
fetch("/api/hello")
.then((r) => r.json())
.then((d) => setData(d.message));
}, []);
return (
<div>
<h1>React SPA on Cloudflare</h1>
<p>{data || "Loading..."}</p>
</div>
);
}
export default App;
Wrangler Config
// wrangler.jsonc
{
"name": "my-spa",
"assets": {
"directory": "./dist"
}
}
That’s the entire config. No main field needed since there’s no server code.
Adding an API Alongside the SPA
If you need a backend, you can add a Worker entry point:
// worker.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname.startsWith("/api/")) {
// Handle API routes
if (url.pathname === "/api/hello") {
return Response.json({ message: "Hello from Worker" });
}
return new Response("Not found", { status: 404 });
}
// Static assets are served automatically by Workers Static Assets
return new Response("Not found", { status: 404 });
},
};
// wrangler.jsonc (with API)
{
"name": "my-spa",
"main": "./worker.ts",
"assets": {
"directory": "./dist",
"binding": "ASSETS"
}
}
Dev and Deploy
# Development
npm run dev
# Build
npm run build
# Deploy
npx wrangler deploy
Key Points
- Zero server code by default, all static assets
- No cold starts, globally cached via Cloudflare CDN
- CF Vite Plugin handles asset configuration
- Can add a Worker entry point for API routes if needed
- Best for: dashboards, admin panels, tools, any app where SEO doesn’t matter
- Not for: content sites needing SEO (use Astro or React Router with SSR)
Tip: For SPAs that need an API, consider deploying a separate Hono Worker for the API and using this SPA for the frontend. Service bindings let them communicate efficiently.
Gotcha: SPA routing (React Router, etc.) requires a catch-all that serves
index.htmlfor all paths. Workers Static Assets handles this automatically when there’s no Worker entry point. If you add a Worker, make sure unmatched routes fall through to static assets.