1---
2title: SolidStart
3description: Deploy a SolidStart app to AWS with AWS.Website.SolidStart — nitro's aws-lambda preset on a streaming Lambda Function URL, S3 assets behind CloudFront, 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`AWS.Website.SolidStart` deploys a
13[SolidStart](https://start.solidjs.com) app to AWS. It runs your
14project's own `vite build` and appends nitro's `aws-lambda` preset:
15the SSR server runs on a Lambda Function URL with response streaming,
16and client assets plus prerendered pages are served from a private S3
17bucket through CloudFront. A CloudFront Function routes each request
18at the edge — uploaded files go to S3, everything else streams from
19the Lambda.
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/aws` 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 just the SolidStart plugin — no adapter, no
71nitro wiring:
72
73```typescript
74// vite.config.ts
75import { solidStart } from "@solidjs/start/config";
76import { defineConfig } from "vite";
77
78export default defineConfig({
79 plugins: [solidStart()],
80});
81```
82
83Alchemy appends its own `nitroV2Plugin()` instance at build time,
84carrying the `aws-lambda` preset with response streaming enabled.
85Don't register `nitroV2Plugin()` yourself — the appended instance
86would discard its options, so the build fails with an actionable
87error instead. Pass nitro options through the resource's `nitro`
88prop; see [Prerendering](#prerendering) below.
89
90## Declare the Website
91
92Declare the site as a module-level const (rather than inline in the
93Stack):
94
95```typescript
96// alchemy.run.ts
97import * as AWS from "alchemy/AWS";
98
99export const Website = AWS.Website.SolidStart("Website");
100```
101
102`rootDir` is the project root — the directory holding `package.json`
103and `vite.config.*`. It defaults to `"."`, so set it only when the app
104does not sit next to `alchemy.run.ts`:
105
106```typescript
107export const Website = AWS.Website.SolidStart("Website", {
108 rootDir: "./app",
109});
110```
111
112## Add it to the Stack
113
114Yield the site from your Stack and return its URL:
115
116```typescript
117// alchemy.run.ts
118import * as Alchemy from "alchemy";
119import * as Effect from "effect/Effect";
120
121export default Alchemy.Stack(
122 "MySolidStartSite",
123 {
124 providers: AWS.providers(),
125 state: AWS.state(),
126 },
127 Effect.gen(function* () {
128 const site = yield* Website;
129 return { url: site.url };
130 }),
131);
132```
133
134## Add environment variables
135
136The server function's environment is configured under `env`:
137
138```diff lang="typescript"
139// alchemy.run.ts
140export const Website = AWS.Website.SolidStart("Website", {
141+ memorySize: 2048,
142+ env: {
143+ API_BASE: "https://api.example.com",
144+ },
145});
146```
147
148`env` values are set on the Lambda on deploy and injected into
149the dev server's process environment under `alchemy dev`, so server
150code reads the same values in both modes. Sibling props tune the
151Lambda itself (`memorySize`, `timeout`, `architecture`, `runtime`).
152
153An output from another resource only exists once that resource is
154yielded, so declare the site inside the Stack generator instead:
155
156```typescript
157// alchemy.run.ts
158Effect.gen(function* () {
159 const api = yield* Api;
160
161 const site = yield* AWS.Website.SolidStart("Website", {
162 env: { API_BASE: api.url },
163 });
164
165 return { url: site.url };
166});
167```
168
169## Read the environment in server code
170
171The SolidStart server runs in a plain Node Lambda, so API routes,
172server functions, and SSR read the environment from `process.env`:
173
174```typescript
175// src/routes/api/hello.ts
176export function GET() {
177 return new Response(process.env.API_BASE ?? "unset");
178}
179```
180
181Values prefixed `VITE_` are inlined into the client bundle at build
182time instead, as `import.meta.env.VITE_*` — set those in the shell
183that runs `alchemy deploy`, not in `env`.
184
185## Prerendering
186
187Nitro renders the routes you list at build time into `.output/public`.
188They are uploaded to S3 and served from the edge with no Lambda
189invocation:
190
191```typescript
192export const Website = AWS.Website.SolidStart("Website", {
193 nitro: { prerender: { routes: ["/", "/about"] } },
194});
195```
196
197`prerender` takes the routes to render (`routes`) and can crawl links
198from prerendered pages to prerender them too (`crawlLinks`). The
199`nitro` prop is the home for all nitro options (route rules, storage,
200...) — the integration owns the plugin instance, so they cannot live
201in `vite.config.ts`. Values must be JSON-serializable; `preset` is
202owned by the AWS deploy target, and a foreign preset is a hard error.
203
204## Local dev
205
206```sh
207bun alchemy dev
208```
209
210`alchemy dev` runs SolidStart's own Vite dev server (native HMR)
211instead of deploying anything; `site.url` is the dev server's
212`http://localhost:<port>` address and no AWS resources are created.
213Nitro plays no part in dev. Wrap the site in `Alchemy.remote()` to
214deploy the real infrastructure even during dev.
215
216SolidStart's plugin resolves its app root and route directory from
217the process working directory, so one `alchemy dev` process serves
218one SolidStart app at a time.
219
220:::caution[Run `alchemy dev` under Node]
221SolidStart 2's dev server answers requests through `srvx/node`, which
222returns the string `[object Object]` instead of the rendered document
223when it runs under Bun. Production builds are unaffected — only the
224dev server is. Until that upstream gap closes, run `alchemy dev` with
225Node (`npx alchemy dev`) for SolidStart projects.
226:::
227
228## Custom domain
229
230```typescript
231// alchemy.run.ts
232export const Website = AWS.Website.SolidStart("Website", {
233 domain: { name: "app.example.com" },
234});
235```
236
237The certificate is created in `us-east-1` (required by CloudFront),
238DNS validation records and the alias record are managed through
239Route 53. The hosted zone is inferred from the hostname — pass
240`hostedZoneId` to pin it when several zones could match.
241
242## Share a distribution with Router
243
244CloudFront distributions take minutes to create. To serve several
245sites (or a site plus an API) from one distribution, attach the site
246to an [`AWS.Website.Router`](/aws/frontend/static-site#compose-sites-with-a-router):
247
248The site takes the Router's outputs, so both are yielded inside the
249Stack generator:
250
251```typescript
252// alchemy.run.ts
253Effect.gen(function* () {
254 const router = yield* AWS.Website.Router("FrontDoor", {
255 domain: { name: "example.com" },
256 });
257
258 const site = yield* AWS.Website.SolidStart("Website", {
259 domain: { router },
260 });
261
262 return { url: site.url };
263});
264```