1---
2title: Vite
3description: Deploy any pure-Vite app to Cloudflare Workers with a single resource.
4sidebar:
5 order: 2
6---
7
8`Cloudflare.Website.Vite` deploys any app that is pure Vite — a
9project whose entire build is `vite build` with plugins from your
10`vite.config.ts`. That covers a plain `index.html`, a React or Vue
11SPA, and full-stack SSR frameworks like TanStack Start or React
12Router. This page explains the resource itself; each supported
13framework has [its own landing page](#frameworks) that flows from
14here.
15
16## One resource, one build
17
18The minimal form takes no props at all:
19
20```typescript
21const site = yield* Cloudflare.Website.Vite("Website");
22```
23
24Alchemy loads **your** project's Vite install (resolved from
25`rootDir`'s `package.json`), appends Alchemy's own Cloudflare Vite
26plugin to the plugins your
27`vite.config.ts` already declares, and runs `builder.buildApp()`.
28Client assets are uploaded as Worker static assets; if the build
29emits a server bundle (SSR), that bundle becomes the Worker entry —
30otherwise the Worker is assets-only. There is no `main` entrypoint,
31no build command, no output directory, and no `wrangler.jsonc`: an
32`index.html` next to your `alchemy.run.ts` (plus your
33`vite.config.ts`, if you have one) is enough.
34
35## What pure Vite means
36
37Condensed from the provider, a deploy runs exactly this:
38
39```typescript
40const vite = await loadVite(rootDir); // your project's own vite install
41const builder = await vite.createBuilder({
42 root: rootDir,
43 define: getDefine(env), // VITE_-prefixed env, see Environment below
44 plugins: [cloudflare(pluginOptions), outputPlugin.plugin],
45}, null);
46await builder.buildApp();
47```
48
49A framework is supported by this resource if — and only if — a
50`vite build` of your `vite.config.ts` builds the whole app.
51Frameworks that drive their own build never enter this path; they
52have dedicated resources instead — see
53[Astro](/cloudflare/frontend/astro),
54[Nuxt](/cloudflare/frontend/nuxt),
55[SvelteKit](/cloudflare/frontend/sveltekit), and
56[Waku](/cloudflare/frontend/waku). For any other build command that
57produces a directory of files, use
58[StaticSite](/cloudflare/frontend/static-site).
59
60## Remove `@cloudflare/vite-plugin`
61
62:::caution[Remove @cloudflare/vite-plugin if present]
63Alchemy appends its own Cloudflare Vite plugin
64(`@alchemy.run/cloudflare-runtime/vite`) and is **not
65compatible** with `@cloudflare/vite-plugin` in your
66`vite.config.ts`. Remove it — this applies to every framework
67deployed with this resource:
68
69```diff lang="typescript"
70// vite.config.ts
71import { defineConfig } from "vite";
72import react from "@vitejs/plugin-react";
73-import cloudflare from "@cloudflare/vite-plugin";
74
75export default defineConfig({
76- plugins: [react(), cloudflare()],
77+ plugins: [react()],
78});
79```
80:::
81
82## Props
83
84`ViteProps` is `WorkerProps` minus the things the build now owns
85(`main`, the directory-shaped `assets`), plus Vite-specific options:
86
87```typescript
88// an SPA whose Worker serves the /api/* routes
89const site = yield* Cloudflare.Website.Vite("Website", {
90 rootDir: "./web",
91 assets: {
92 notFoundHandling: "single-page-application",
93 runWorkerFirst: ["/api/*"],
94 },
95});
96```
97
98- **`rootDir`** — Vite's project root, default `process.cwd()`.
99- **`memo`** — narrows which files are hashed to decide whether a
100 rebuild is needed (see [Rebuilds and memo](#rebuilds-and-memo)).
101- **`viteEnvironments`** — for frameworks that build more than one
102 server environment (see
103 [Multiple server environments](#multiple-server-environments-rsc)).
104- **`assets`** — a flat `AssetsConfig` for routing behavior:
105 `runWorkerFirst`, `htmlHandling`, `notFoundHandling`, etc. SSR
106 apps need none of it: server routes and API paths match no built
107 asset, so they already fall through to the Worker. The glob array
108 matters for SPAs — with `notFoundHandling:
109 "single-page-application"`, whether an unmatched path serves the
110 app shell or reaches the Worker depends on the request's
111 `Sec-Fetch-Mode`, so `runWorkerFirst: ["/api/*"]` pins your API
112 namespace to the Worker deterministically. `true` routes every
113 request through the Worker, for handlers that intercept asset
114 requests. The built asset directory is supplied by the build, so
115 unlike a plain Worker there is no `directory` to configure.
116- Everything else is inherited from the Worker — `domain`, `env`,
117 `compatibility`, `crons`, and so on. See
118 [Workers](/cloudflare/compute/workers).
119
120SSR server bundles routinely use Node APIs at runtime — the
121`nodejs_compat` compatibility flag covers that, and it is enabled by
122default for every Worker, so there is nothing to pass.
123
124:::caution[index.html must be at rootDir]
125Vite resolves `index.html` relative to its project root. `rootDir`
126defaults to `process.cwd()` — so place `index.html` next to your
127`alchemy.run.ts`, or set `rootDir` to the folder that contains it.
128See Vite's
129[project structure docs](https://vite.dev/guide/#index-html-and-project-root).
130:::
131
132Like `Worker`, the resource also has a class form — only needed
133when *other* resources bind to the site (the class gives it a named
134type they can reference):
135
136```typescript
137export class Website extends Cloudflare.Website.Vite<Website>()("Website", {}) {}
138```
139
140For everything else — including deriving `env` types — a plain
141`const` is enough; see [Runtime bindings](#runtime-bindings) below.
142
143## Environment
144
145`env` feeds your app through two distinct channels: build-time
146inlining into the client bundle, and runtime Worker bindings for
147server code.
148
149### Build-time inlining
150
151Only `VITE_`-prefixed keys are inlined into the client bundle as
152`import.meta.env.<KEY>`, matching `vite build`'s default
153`envPrefix` semantics:
154
155```typescript
156const web = yield* Cloudflare.Website.Vite("Website", {
157 env: {
158 VITE_API_URL: backend.url.as<string>(),
159 },
160});
161```
162
163`Output` values like `backend.url` resolve at deploy time before
164the build runs, so the client reads a concrete
165`import.meta.env.VITE_API_URL`. `Redacted` values are unwrapped
166when `VITE_`-prefixed — the prefix means you are opting the value
167into the public bundle, so don't prefix secrets with `VITE_`.
168
169### The site's own URL
170
171`backend.url` works for *another* Worker's URL, but a site can't
172reference its own `url` Output. `Worker.URL` closes the loop:
173Alchemy resolves the URL the Worker will be served at — its first
174custom domain, otherwise its `workers.dev` URL — before the build
175and injects it:
176
177```typescript
178const web = yield* Cloudflare.Website.Vite("Website", {
179 env: {
180 VITE_PUBLIC_URL: Cloudflare.Worker.URL,
181 },
182});
183```
184
185The client bundle reads a concrete
186`import.meta.env.VITE_PUBLIC_URL`, and server code sees the same
187value on `env.VITE_PUBLIC_URL` (typed `string` by `InferEnv`).
188Under `alchemy dev` it resolves to the local dev server's URL.
189
190### Runtime bindings
191
192Non-`VITE_` values become native Worker bindings, available to SSR
193code and typed via `Cloudflare.InferEnv`:
194
195```typescript
196export const Website = Cloudflare.Website.Vite("Website", {
197 env: {
198 BUCKET: Bucket,
199 BACKEND: Backend,
200 },
201});
202
203export type WebsiteEnv = Cloudflare.InferEnv<typeof Website>;
204```
205
206See [TanStack Start](/cloudflare/frontend/tanstack-start) for the
207full pattern of consuming these bindings from server routes.
208
209## Multiple server environments (RSC)
210
211`viteEnvironments` defaults to `{ entry: "ssr", children: [] }`.
212Frameworks that emit several server environments — React Server
213Components split into `rsc` and `ssr` — declare which environment
214produces the Worker entry chunk and which are bundled alongside it:
215
216```typescript
217const app = yield* Cloudflare.Website.Vite("ReactRouterRSC", {
218 viteEnvironments: { entry: "rsc", children: ["ssr"] },
219});
220```
221
222The `entry` environment becomes the deployed Worker entry,
223`children` chunks are bundled alongside it, and the `client`
224environment is always deployed as static assets. See
225[React Router](/cloudflare/frontend/react-router) for the worked
226example.
227
228## Rebuilds and memo
229
230By default, every non-gitignored file under `rootDir` plus the
231nearest lockfile is content-hashed; if nothing changed, both the
232build and the deploy are skipped entirely. The hash is
233path-insensitive — relocating `rootDir` with identical sources is a
234no-op deploy. Narrow the scope with `memo` when the project
235contains large directories that don't affect the build output:
236
237```typescript
238const site = yield* Cloudflare.Website.Vite("Docs", {
239 memo: {
240 include: ["src/**", "index.html", "package.json"],
241 },
242});
243```
244
245## Local dev
246
247```sh
248bun alchemy dev
249```
250
251`alchemy dev` boots Vite's own dev server programmatically with the
252Cloudflare plugin attached — you get HMR on your application code
253while bindings point at the **real** cloud resources, so there is
254no emulation fidelity gap. By default the dev server picks an
255available port; set `dev.port` to keep the local URL stable across
256runs:
257
258```typescript
259const web = yield* Cloudflare.Website.Vite("Website", {
260 dev: { port: 3000 },
261});
262```
263
264## Frameworks
265
266Each supported framework has its own landing page building on this
267resource:
268
269- [React SPA](/cloudflare/frontend/vite-spa) — single-page apps,
270 from a bare `index.html` to the `create-vite` template.
271- [TanStack Start](/cloudflare/frontend/tanstack-start) —
272 full-stack React and Solid with server routes and typed bindings.
273- [React Router](/cloudflare/frontend/react-router) — including
274 React Server Components via `viteEnvironments`.
275- [Vue](/cloudflare/frontend/vue) — Vue 3 SPAs.
276- [SolidStart](/cloudflare/frontend/solidstart) — SolidStart and
277 hand-rolled SolidJS SSR.
278- [Octane](/cloudflare/frontend/octane#octane-spas-use-vite-instead) —
279 client-only OctaneJS SPAs (the `octane()` compiler plugin composes
280 with the injected Cloudflare plugin).
281
282[Astro](/cloudflare/frontend/astro),
283[Nuxt](/cloudflare/frontend/nuxt),
284[SvelteKit](/cloudflare/frontend/sveltekit),
285[Waku](/cloudflare/frontend/waku), and fullstack
286[Octane](/cloudflare/frontend/octane) drive their own builds, so they
287have dedicated resources (`Website.Astro`, `Website.Nuxt`,
288`Website.SvelteKit`, `Website.Waku`, `Website.Octane`) instead of
289this one.