This tutorial takes you through the steps needed to adapt a Vite project to use the Cloudflare Vite plugin. Much of the content can also be applied to adapting existing Vite projects and to front-end frameworks other than React.
In this tutorial, you will create a React SPA that can be deployed as a Worker with static assets. You will then add an API Worker that can be accessed from the front-end code. You will develop, build, and preview the application using Vite before finally deploying to Cloudflare.
Start by creating a React TypeScript project with Vite.
npm create vite@latest -- cloudflare-vite-tutorial --template react-tsyarn create vite cloudflare-vite-tutorial --template react-tspnpm create vite@latest cloudflare-vite-tutorial --template react-tsNext, open the cloudflare-vite-tutorial directory in your editor of choice.
npm i -D @cloudflare/vite-plugin wrangleryarn add -D @cloudflare/vite-plugin wranglerpnpm add -D @cloudflare/vite-plugin wranglerbun add -d @cloudflare/vite-plugin wranglerIn your vite.config.ts, add the Cloudflare Vite plugin after your framework plugin:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { cloudflare } from "@cloudflare/vite-plugin";
export default defineConfig({
plugins: [react(), cloudflare()],
});The Cloudflare Vite plugin does not require any configuration by default and will look for a wrangler.jsonc, wrangler.json, or wrangler.toml in the root of your application.
Create a wrangler.jsonc file in the root of your project:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-app",
// Set this to today's date
"compatibility_date": "2026-09-07",
"assets": {
"not_found_handling": "single-page-application"
}
}name = "my-app"
# Set this to today's date
compatibility_date = "2026-09-07"
[assets]
not_found_handling = "single-page-application"The not_found_handling value has been set to single-page-application.
This means that all not-found requests will serve the index.html file, which is required for React Router and other client-side routing solutions.
With the Cloudflare plugin, the assets routing configuration is used in place of Vite's default behavior.
This ensures that your application's routing configuration works the same way while developing as it does when deployed to production.
The directory field is not used when configuring assets with Vite.
The directory in the output configuration will automatically point to the client build output.
Refer to Static Assets for more information.
When developing Workers, additional files are used and/or generated that should not be stored in Git.
Add the following lines to your .gitignore file:
.wrangler
.dev.vars*Run your framework's development command to start the Vite development server and verify that your application is working as expected.
npm run devyarn run devpnpm run devFor a purely front-end application, you could now build, preview, and deploy your application. The following sections will show you how to go further and add an API Worker.
npm i -D @cloudflare/workers-typesyarn add -D @cloudflare/workers-typespnpm add -D @cloudflare/workers-typesbun add -d @cloudflare/workers-typesCreate a tsconfig.worker.json that extends your Node TypeScript configuration and adds the Workers types:
{
"extends": "./tsconfig.node.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.worker.tsbuildinfo",
"types": ["@cloudflare/workers-types/2023-07-01", "vite/client"],
},
"include": ["worker"],
}Then add a reference to this new configuration in your root tsconfig.json:
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.worker.json" },
],
}Update your Wrangler configuration file to add a main field that points to your Worker entrypoint:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-app",
// Set this to today's date
"compatibility_date": "2026-09-07",
"main": "./worker/index.ts",
"assets": {
"not_found_handling": "single-page-application"
}
}name = "my-app"
# Set this to today's date
compatibility_date = "2026-09-07"
main = "./worker/index.ts"
[assets]
not_found_handling = "single-page-application"The main field specifies the entry file for your Worker code.
Create a worker/index.ts file with the following contents:
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;The Worker defined in the preceding code block will be invoked for any non-navigation request that does not match a static asset.
It returns a JSON response if the pathname starts with /api/ and otherwise returns a 404 response.
Edit src/App.tsx so that it includes an additional button that calls the API and sets some state:
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState("unknown");
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button
onClick={() => setCount((count) => count + 1)}
aria-label="increment"
>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<div className="card">
<button
onClick={() => {
fetch("/api/")
.then((res) => res.json() as Promise<{ name: string }>)
.then((data) => setName(data.name));
}}
aria-label="get name"
>
Name from API is: {name}
</button>
<p>
Edit <code>api/index.ts</code> to change the name
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
);
}
export default App;Now, if you click the button, it will display 'Name from API is: Cloudflare'.
Increment the counter to update the application state in the browser.
Next, edit api/index.ts by changing the name it returns to 'Cloudflare Workers'.
If you click the button again, it will display the new name while preserving the previously set counter value.
With Vite and the Cloudflare plugin, you can iterate on the client and server parts of your app together, without losing UI state between edits.
Run the build command to build your application.
npm run buildyarn run buildpnpm run buildThe dist directory will contain your client build output in the client subdirectory and your Worker code alongside the output wrangler.json configuration file.
Run the preview command to validate that your application runs as expected.
npm run previewyarn run previewpnpm run previewThis command will run your build output locally in the Workers runtime, closely matching its behavior in production.
Run the deploy command to deploy your application to Cloudflare.
npx wrangler deployyarn wrangler deploypnpm wrangler deployThis command will automatically use the output wrangler.json that was included in the build output.
In this tutorial, we created an SPA that could be deployed as a Worker with static assets. We then added an API Worker that could be accessed from the front-end code. Finally, we deployed both the client and server-side parts of the application to Cloudflare.
Possible next steps include:
- Adding a binding to another Cloudflare service such as a KV namespace or D1 database
- Expanding the API to include additional routes
- Using a library, such as Hono ↗ or tRPC ↗, in your API Worker