1---
2title: Vite
3description: Deploy a Vite SPA to Fly with Fly.Website.Vite — static assets on a Node Machine, fly.dev (or your domain) over HTTPS, and Vite's own dev server under alchemy dev.
4sidebar:
5 order: 2
6---
7
8import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
9
10export const devDeps = "@alchemy.run/frontend-frameworks";
11
12`Fly.Website.Vite` deploys a [Vite](https://vite.dev) project to Fly.
13It runs `vite build` and serves the output from a tiny Node
14static-file server on a Machine: one [`Fly.App`](/fly/compute/apps)
15(created if `app` is omitted), one
16[`Fly.Service`](/fly/compute/services) on port 3000, and a shared
17IPv4 so `https://{app}.fly.dev` answers. Your `vite.config.*` loads
18natively — plugins included. There is no framework server module.
19
20React, Vue, and Solid client SPAs belong here, as do multi-page apps
21that emit one HTML file per route. SSR frameworks that wrap Vite
22deploy through their own composites —
23[`Astro`](/fly/frontend/astro), [`Nextjs`](/fly/frontend/nextjs),
24[`Nuxt`](/fly/frontend/nuxt),
25[`ReactRouter`](/fly/frontend/react-router),
26[`SolidStart`](/fly/frontend/solidstart),
27[`SvelteKit`](/fly/frontend/sveltekit),
28[`TanStackStart`](/fly/frontend/tanstack-start),
29[`Waku`](/fly/frontend/waku), [`Octane`](/fly/frontend/octane).
30
31## Install
32
33The build integration is not bundled with alchemy. Install
34`@alchemy.run/frontend-frameworks`; the resource loads its `/vite`
35and `/vite/node` exports from your project at deploy time. It is
36only used at build time, so a dev dependency is enough:
37
38<Tabs syncKey="pkgManager">
39 <TabItem label="bun" icon="bun">
40 <Code code={`bun add -d ${devDeps}`} lang="sh" />
41 </TabItem>
42 <TabItem label="npm" icon="npm">
43 <Code code={`npm install -D ${devDeps}`} lang="sh" />
44 </TabItem>
45 <TabItem label="pnpm" icon="pnpm">
46 <Code code={`pnpm add -D ${devDeps}`} lang="sh" />
47 </TabItem>
48 <TabItem label="yarn" icon="seti:yarn">
49 <Code code={`yarn add -D ${devDeps}`} lang="sh" />
50 </TabItem>
51</Tabs>
52
53## Configure Vite
54
55Your `vite.config.*` file loads natively — React, Vue, or Solid
56plugins, Tailwind, and any other non-serializable options work
57exactly as they do outside Alchemy. Alchemy merges a programmatic
58config *over* it via the `vite` prop — see
59[Vite configuration](#vite-configuration) below.
60
61```typescript
62// vite.config.ts
63import { defineConfig } from "vite";
64import react from "@vitejs/plugin-react";
65import tailwindcss from "@tailwindcss/vite";
66
67export default defineConfig({
68 plugins: [react(), tailwindcss()],
69});
70```
71
72Do not set a Vite SSR adapter. This composite never creates a
73framework server module — it only ships the client build.
74
75## Declare the Website
76
77Declare the site as a module-level const (rather than inline in the
78Stack):
79
80```typescript
81// alchemy.run.ts
82import * as Fly from "alchemy/Fly";
83
84export const Website = Fly.Website.Vite("Website");
85```
86
87Pass `app` to put the site on an App you already have. Omit it and
88Alchemy creates `Fly.App("App")` under the site's namespace:
89
90```typescript
91export const Site = Fly.App("Site");
92
93export const Website = Fly.Website.Vite("Website", { app: Site });
94```
95
96## Add it to the Stack
97
98Yield the site from your Stack and return its URL:
99
100```typescript
101// alchemy.run.ts
102import * as Alchemy from "alchemy";
103import * as Effect from "effect/Effect";
104
105export default Alchemy.Stack(
106 "MyViteSite",
107 {
108 providers: Fly.providers(),
109 state: Alchemy.localState(),
110 },
111 Effect.gen(function* () {
112 const site = yield* Website;
113 return { url: site.url };
114 }),
115);
116```
117
118On deploy, `url` is `https://{app}.fly.dev`. `app`, `service`,
119`ip`, and `certificate` are the backing resources; they are
120`undefined` under `alchemy dev`.
121
122See
123[examples/fly-website-vite](https://github.com/alchemy-run/alchemy/tree/main/examples/fly-website-vite)
124for the checked-in example.
125
126## Add environment variables
127
128`env` is process environment — set on `process.env` before
129`vite build` / `alchemy dev`, and on the Machine at deploy. It is
130not Worker bindings:
131
132```diff lang="typescript"
133// alchemy.run.ts
134export const Website = Fly.Website.Vite("Website", {
135+ env: {
136+ VITE_GREETING: "Hello from Alchemy!",
137+ },
138});
139```
140
141Vite inlines `VITE_*` into the client bundle at build time. Other
142keys reach the hosted Node process but not the browser.
143
144## Read the environment
145
146Client code reads inlined values from `import.meta.env`:
147
148```typescript
149// src/main.ts
150const greeting = import.meta.env.VITE_GREETING ?? "hello";
151```
152
153## Single-page vs multi-page
154
155Plain Vite apps are typically SPAs, so `assets.notFoundHandling`
156defaults to `"single-page-application"`: unmatched paths get
157`index.html` with status 200 so client-side routes deep-link. Use
158`"404-page"` for multi-page sites that ship a `404.html`:
159
160```typescript
161export const Website = Fly.Website.Vite("Docs", {
162 assets: { notFoundHandling: "404-page" },
163});
164```
165
166## Vite configuration
167
168`rootDir` is the project root (the directory containing
169`package.json`). Default `"."`.
170
171`vite.outDir` and `vite.base` are serializable overrides merged *over* the
172config file:
173
174```typescript
175export const Website = Fly.Website.Vite("Website", {
176 rootDir: "./app",
177 vite: { outDir: "build", base: "/docs/" },
178});
179```
180
181`outDir` is relative to `rootDir` and defaults to the file's
182`build.outDir` (Vite's default: `"dist"`). `base` is Vite's public
183base path.
184
185## Local development
186
187`alchemy dev` runs Vite's own dev server (native HMR) instead of
188deploying; `site.url` is the local address and no Fly App, Service,
189IP, or certificate is created. Wrap the site in
190`Alchemy.remote()` to deploy the real infrastructure even during
191dev.
192
193## Custom domain
194
195`domain` is a hostname string. Alchemy requests ACME
196([`Fly.Certificate`](/fly/networking)) on the App and `url` becomes
197`https://{domain}`. v1 expects the hostname's DNS to already point
198at the App:
199
200```typescript
201export const Website = Fly.Website.Vite("Website", {
202 domain: "app.example.com",
203});
204```
205
206## Where next
207
208- [`Fly.Website.Vite` API](/providers/fly/website/vite)
209- [Astro](/fly/frontend/astro), [Next.js](/fly/frontend/nextjs),
210 [Nuxt](/fly/frontend/nuxt), [SvelteKit](/fly/frontend/sveltekit),
211 [Waku](/fly/frontend/waku), [Octane](/fly/frontend/octane) — SSR
212 frameworks
213- [Services](/fly/compute/services),
214 [IPs & certificates](/fly/networking)