1---
2title: SolidStart
3description: Deploy a SolidStart app to Railway with Railway.Website.SolidStart — nitro's node preset on a container Service, prerendered pages in 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`Railway.Website.SolidStart` deploys a
13[SolidStart](https://start.solidjs.com) app to Railway. It runs your
14project's own `vite build` and appends nitro's `node` preset: the SSR
15server plus client assets (prerendered pages included) run on one
16[`Railway.Service`](/railway/compute/services). Alchemy creates a
17[`Railway.Project`](/railway/compute/projects) if you omit
18`project`. The URL is the generated `*.up.railway.app` hostname. No
19adapter.
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 Railway from "alchemy/Railway";
99
100export const Website = Railway.Website.SolidStart("Website");
101```
102
103Pass `project` to put the Service in an existing `Railway.Project`.
104`rootDir` is the project root — the directory holding `package.json`
105and `vite.config.*`. It defaults to `"."`, so set it only when the app
106does not sit next to `alchemy.run.ts`:
107
108```typescript
109export const Website = Railway.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: Railway.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://{name}.up.railway.app`. `site.service`
137and `site.project` are the Railway resources underneath —
138`undefined` during `alchemy dev`.
139
140See the
141[SolidStart API reference](/providers/railway/website/solidstart)
142for every prop and attribute.
143
144## Add environment variables
145
146Process environment is a top-level `env` map — plain strings, or
147`Redacted` values:
148
149```diff lang="typescript"
150// alchemy.run.ts
151export const Website = Railway.Website.SolidStart("Website", {
152+ env: {
153+ API_BASE: "https://api.example.com",
154+ },
155});
156```
157
158The values are copied onto `process.env` before the build and the
159dev server, and onto the Service at deploy time, so server code
160reads the same keys in both modes. Client inlining is whatever Vite
161does (`VITE_` prefixes).
162
163An output from another resource only exists once that resource is
164yielded, so declare the site inside the Stack generator instead:
165
166```typescript
167// alchemy.run.ts
168Effect.gen(function* () {
169 const api = yield* Api;
170
171 const site = yield* Railway.Website.SolidStart("Website", {
172 env: { API_BASE: api.url },
173 });
174
175 return { url: site.url };
176});
177```
178
179## Read the environment in server code
180
181The SolidStart server is a plain Node process, so API routes,
182server functions, and SSR read the environment from `process.env`:
183
184```typescript
185// src/routes/api/hello.ts
186export function GET() {
187 return new Response(process.env.API_BASE ?? "unset");
188}
189```
190
191Values prefixed `VITE_` are inlined into the client bundle at build
192time instead, as `import.meta.env.VITE_*` — set those in the shell
193that runs `alchemy deploy`, not in `env`.
194
195## Prerendering
196
197Nitro renders the routes you list at build time into `.output/public`.
198They are baked into the Service image and served as static files with
199no handler invocation:
200
201```typescript
202export const Website = Railway.Website.SolidStart("Website", {
203 nitro: { prerender: { routes: ["/", "/about"] } },
204});
205```
206
207`prerender` takes the routes to render (`routes`) and can crawl links
208from prerendered pages to prerender them too (`crawlLinks`). The
209`nitro` prop is the home for all nitro options (route rules, storage,
210...) — the integration owns the plugin instance, so they cannot live
211in `vite.config.ts`. Values must be JSON-serializable; `preset` is
212owned by the Node deploy target, and a foreign preset is a hard error.
213
214## Local dev
215
216```sh
217bun alchemy dev
218```
219
220`alchemy dev` runs SolidStart's own Vite dev server (native HMR)
221instead of deploying; `site.url` is the local address and no Railway
222resources are created. Nitro plays no part in dev. Wrap the site in
223`Alchemy.remote()` to deploy the live Service even during dev:
224
225```typescript
226export const Website = Railway.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* Railway.Website.SolidStart("Web", {
247 domain: "app.example.com",
248});
249```
250
251`domain` attaches a [`Railway.CustomDomain`](/railway/networking)
252(`targetPort` 3000). `url` becomes `https://app.example.com`
253instead of the generated `*.up.railway.app`.
254
255## Where next
256
257- [SolidStart API reference](/providers/railway/website/solidstart)
258- [Projects](/railway/compute/projects) and
259 [Services](/railway/compute/services)
260- [Custom domains](/railway/networking)