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