1---
2title: Vue
3description: Deploy a Vue single-page app to Cloudflare with the Vite resource — one declaration, no Wrangler config.
4sidebar:
5 order: 7
6---
7
8[Vue](https://vuejs.org) is pure Vite — the entire app builds from
9`@vitejs/plugin-vue` in your `vite.config.ts`, so
10[`Cloudflare.Website.Vite`](/cloudflare/frontend/vite)
11deploys it with a single declaration: no `main` entrypoint, no
12build command, no output directory, no Wrangler configuration.
13
14## Configure Vite
15
16Your Vite config stays exactly what Vue's scaffold gives you:
17
18```typescript
19// vite.config.ts
20import { defineConfig } from "vite";
21import vue from "@vitejs/plugin-vue";
22
23export default defineConfig({
24 plugins: [vue()],
25});
26```
27
28Alchemy runs Vite programmatically on the project root and layers
29its Cloudflare integration on top of this config — your plugins,
30aliases, and the rest of your setup are preserved as-is.
31
32## Declare the Website
33
34Declare the site as a module-level const (rather than inline in the
35Stack) — every prop is optional, so the minimal declaration is just
36a name:
37
38```typescript
39// alchemy.run.ts
40import * as Cloudflare from "alchemy/Cloudflare";
41
42export const Website = Cloudflare.Website.Vite("Vue");
43```
44
45## Add it to the Stack
46
47Yield the class from your Stack and return its URL — see
48[examples/cloudflare-vue](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-vue)
49for the checked-in example:
50
51```typescript
52// alchemy.run.ts
53import * as Alchemy from "alchemy";
54import * as Effect from "effect/Effect";
55
56export default Alchemy.Stack(
57 "CloudflareVueExample",
58 {
59 providers: Cloudflare.providers(),
60 state: Cloudflare.state(),
61 },
62 Effect.gen(function* () {
63 const worker = yield* Website;
64
65 return {
66 url: worker.url,
67 };
68 }),
69);
70```
71
72Alchemy builds the client assets and serves them from a Worker at
73the returned `url`.
74
75## Deep links with vue-router
76
77If your app uses `vue-router` in history mode
78(`createWebHistory`), a deep link like `/about` arrives at the
79server as a request for a file that doesn't exist. Configure the
80asset layer to fall back to `index.html`:
81
82```diff lang="typescript"
83// alchemy.run.ts
84export const Website = Cloudflare.Website.Vite("Vue", {
85+ assets: {
86+ notFoundHandling: "single-page-application",
87+ },
88});
89```
90
91With `single-page-application` not-found handling, unmatched paths
92return `index.html` instead of a 404, and vue-router resolves the
93route on the client.
94
95## Add environment variables
96
97A pure SPA has no server, so anything it needs from the rest of
98your Stack is baked into the bundle at build time — pass a
99`VITE_`-prefixed key in `env`:
100
101```diff lang="typescript"
102// alchemy.run.ts
103export const Website = Cloudflare.Website.Vite("Vue", {
104+ env: {
105+ VITE_API_URL: backend.url,
106+ },
107});
108```
109
110Type it for your Vue code with Vite's standard `ImportMetaEnv`
111augmentation:
112
113```typescript
114// src/vite-env.d.ts
115/// <reference types="vite/client" />
116
117interface ImportMetaEnv {
118 readonly VITE_API_URL: string;
119}
120```
121
122Client code reads it as `import.meta.env.VITE_API_URL`. See
123[Environment](/cloudflare/frontend/vite#environment) for the full
124inlining semantics.
125
126Because a SPA ships no server code, there are no runtime bindings to
127type with `Cloudflare.InferEnv` — when you add server routes (via an
128SSR framework like [TanStack Start](/cloudflare/frontend/tanstack-start)
129or a separate [Worker](/cloudflare/compute/workers)), that's where
130`InferEnv` comes in.