1---
2title: SolidStart
3description: Deploy a SolidStart app to Fly with Fly.Website.SolidStart — nitro's node preset on a Machine, prerendered pages baked into the image, and SolidStart's own Vite dev server under alchemy dev.
4sidebar:
5 order: 10.5
6---
7
8import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
9
10export const devDeps = "@alchemy.run/frontend-frameworks";
11
12`Fly.Website.SolidStart` deploys a
13[SolidStart](https://start.solidjs.com) app to Fly. It runs your
14project's own `vite build` and appends nitro's `node` preset: the SSR
15server runs as a Node [`Fly.Service`](/fly/compute/services) on a
16Machine, and client assets plus prerendered pages are baked into the
17image and served first. Omit `app` and Alchemy creates a
18[`Fly.App`](/fly/compute/apps) plus a shared IPv4 so
19`https://{app}.fly.dev` answers. No adapter, no `fly.toml`.
20
21:::note[Verified version line]
22This page tracks `@solidjs/start` **2.x** with
23`@solidjs/vite-plugin-nitro-2` — the Vite-native line where
24`solidStart()` is a plain Vite plugin and nitro is a second plugin.
25:::
26
27## Install
28
29The build integration is not bundled with alchemy. Install
30`@alchemy.run/frontend-frameworks`; the resource loads its
31`/solidstart` and `/solidstart/node` exports from your project at
32deploy time. It is only used at build time, so a dev dependency is
33enough:
34
35<Tabs syncKey="pkgManager">
36 <TabItem label="bun" icon="bun">
37 <Code code={`bun add -d ${devDeps}`} lang="sh" />
38 </TabItem>
39 <TabItem label="npm" icon="npm">
40 <Code code={`npm install -D ${devDeps}`} lang="sh" />
41 </TabItem>
42 <TabItem label="pnpm" icon="pnpm">
43 <Code code={`pnpm add -D ${devDeps}`} lang="sh" />
44 </TabItem>
45 <TabItem label="yarn" icon="seti:yarn">
46 <Code code={`yarn add -D ${devDeps}`} lang="sh" />
47 </TabItem>
48</Tabs>
49
50`@solidjs/vite-plugin-nitro-2` is the server half of a SolidStart
51build and must be a project dependency too:
52
53<Tabs syncKey="pkgManager">
54 <TabItem label="bun" icon="bun">
55 <Code code="bun add @solidjs/vite-plugin-nitro-2" lang="sh" />
56 </TabItem>
57 <TabItem label="npm" icon="npm">
58 <Code code="npm install @solidjs/vite-plugin-nitro-2" lang="sh" />
59 </TabItem>
60 <TabItem label="pnpm" icon="pnpm">
61 <Code code="pnpm add @solidjs/vite-plugin-nitro-2" lang="sh" />
62 </TabItem>
63 <TabItem label="yarn" icon="seti:yarn">
64 <Code code="yarn add @solidjs/vite-plugin-nitro-2" lang="sh" />
65 </TabItem>
66</Tabs>
67
68## Configure Vite
69
70Your `vite.config.ts` is the SolidStart plugin — no adapter, no
71nitro wiring. Vite plugins like Tailwind go in `plugins` as usual:
72
73```typescript
74// vite.config.ts
75import { solidStart } from "@solidjs/start/config";
76import tailwindcss from "@tailwindcss/vite";
77import { defineConfig } from "vite";
78
79export default defineConfig({
80 plugins: [solidStart(), tailwindcss()],
81});
82```
83
84Alchemy appends its own `nitroV2Plugin()` instance at build time,
85carrying nitro's `node` preset. Don't register `nitroV2Plugin()`
86yourself — the appended instance would discard its options, so the
87build fails with an actionable error instead. Pass nitro options
88through the resource's `nitro` prop; see [Prerendering](#prerendering)
89below.
90
91## Declare the Website
92
93Declare the site as a module-level const (rather than inline in the
94Stack):
95
96```typescript
97// alchemy.run.ts
98import * as Fly from "alchemy/Fly";
99
100export const Website = Fly.Website.SolidStart("Website");
101```
102
103Pass `app` to put the site on an existing `Fly.App`. `rootDir` is the
104project root — the directory holding `package.json` and `vite.config.*`.
105It defaults to `"."`, so set it only when the app does not sit next to
106`alchemy.run.ts`:
107
108```typescript
109export const Website = Fly.Website.SolidStart("Website", {
110 rootDir: "./app",
111});
112```
113
114## Add it to the Stack
115
116Yield the site from your Stack and return its URL:
117
118```typescript
119// alchemy.run.ts
120import * as Alchemy from "alchemy";
121import * as Effect from "effect/Effect";
122
123export default Alchemy.Stack(
124 "MySolidStartSite",
125 {
126 providers: Fly.providers(),
127 state: Alchemy.localState(),
128 },
129 Effect.gen(function* () {
130 const site = yield* Website;
131 return { url: site.url };
132 }),
133);
134```
135
136The live `url` is `https://{app}.fly.dev`. `site.app`,
137`site.service`, `site.ip`, and `site.certificate` are the Fly
138resources underneath — `undefined` during `alchemy dev`.
139
140See the [SolidStart API reference](/providers/fly/website/solidstart)
141for every prop and attribute.
142
143## Add environment variables
144
145Process environment is a top-level `env` map — plain strings, or
146`Redacted` values:
147
148```diff lang="typescript"
149// alchemy.run.ts
150export const Website = Fly.Website.SolidStart("Website", {
151+ env: {
152+ API_BASE: "https://api.example.com",
153+ },
154});
155```
156
157The values are copied onto `process.env` before the build and the
158dev server, and onto the Machine at deploy time, so server code
159reads the same keys in both modes. Client inlining is whatever Vite
160does (`VITE_` prefixes).
161
162An output from another resource only exists once that resource is
163yielded, so declare the site inside the Stack generator instead:
164
165```typescript
166// alchemy.run.ts
167Effect.gen(function* () {
168 const api = yield* Api;
169
170 const site = yield* Fly.Website.SolidStart("Website", {
171 env: { API_BASE: api.url },
172 });
173
174 return { url: site.url };
175});
176```
177
178## Read the environment in server code
179
180The SolidStart server is a plain Node process, so API routes,
181server functions, and SSR read the environment from `process.env`:
182
183```typescript
184// src/routes/api/hello.ts
185export function GET() {
186 return new Response(process.env.API_BASE ?? "unset");
187}
188```
189
190Values prefixed `VITE_` are inlined into the client bundle at build
191time instead, as `import.meta.env.VITE_*` — set those in the shell
192that runs `alchemy deploy`, not in `env`.
193
194## Prerendering
195
196Nitro renders the routes you list at build time into `.output/public`.
197They are baked into the image and served as static files with no
198handler invocation:
199
200```typescript
201export const Website = Fly.Website.SolidStart("Website", {
202 nitro: { prerender: { routes: ["/", "/about"] } },
203});
204```
205
206`prerender` takes the routes to render (`routes`) and can crawl links
207from prerendered pages to prerender them too (`crawlLinks`). The
208`nitro` prop is the home for all nitro options (route rules, storage,
209...) — the integration owns the plugin instance, so they cannot live
210in `vite.config.ts`. Values must be JSON-serializable; `preset` is
211owned by the Node deploy target, and a foreign preset is a hard error.
212
213## Local dev
214
215```sh
216bun alchemy dev
217```
218
219`alchemy dev` runs SolidStart's own Vite dev server (native HMR)
220instead of deploying; `site.url` is the local address and no Fly
221resources are created. Nitro plays no part in dev. Wrap the site in
222`Alchemy.remote()` to deploy the live App and Service even during
223dev:
224
225```typescript
226export const Website = Fly.Website.SolidStart("Website").pipe(
227 Alchemy.remote(),
228);
229```
230
231SolidStart's plugin resolves its app root and route directory from
232the process working directory, so one `alchemy dev` process serves
233one SolidStart app at a time.
234
235:::caution[Run `alchemy dev` under Node]
236SolidStart 2's dev server answers requests through `srvx/node`, which
237returns the string `[object Object]` instead of the rendered document
238when it runs under Bun. Production builds are unaffected — only the
239dev server is. Until that upstream gap closes, run `alchemy dev` with
240Node (`npx alchemy dev`) for SolidStart projects.
241:::
242
243## Custom domain
244
245```typescript
246const site = yield* Fly.Website.SolidStart("Web", {
247 domain: "app.example.com",
248});
249```
250
251`domain` requests ACME (`Fly.Certificate`) on the App. `url`
252becomes `https://app.example.com`. Point DNS at the App first — v1
253does not create DNS records.
254
255## Where next
256
257- [SolidStart API reference](/providers/fly/website/solidstart)
258- [Apps](/fly/compute/apps) and [Services](/fly/compute/services)
259- [IPs & certificates](/fly/networking)