1---
2title: SvelteKit
3description: Deploy a SvelteKit app to Fly with Fly.Website.SvelteKit — kit SSR on a Machine, prerendered assets baked into the image, and Kit's own dev server under alchemy dev.
4sidebar:
5 order: 12
6---
7
8import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
9
10export const devDeps = "@alchemy.run/frontend-frameworks";
11
12`Fly.Website.SvelteKit` deploys a
13[SvelteKit](https://svelte.dev/docs/kit) app to Fly. It builds the
14app with SvelteKit's own Vite pipeline and an in-memory Node adapter.
15Client assets and prerendered pages bake into the Machine image and
16are served first; dynamic routes run in the kit handler on port 3000.
17Alchemy creates a `Fly.App` if you omit `app`, a `Fly.Service` on
18that App, and a shared IPv4 so `https://{app}.fly.dev` answers. Your
19`vite.config.ts` loads natively; there is no `svelte.config.js` to
20write (kit v3 dropped it) and no adapter to install.
21
22## Install
23
24The build integration is not bundled with alchemy. Install
25`@alchemy.run/frontend-frameworks`; the resource loads its
26`/sveltekit` and `/sveltekit/node` exports from your project at
27deploy time. It is only used at build time, so a dev dependency is
28enough:
29
30<Tabs syncKey="pkgManager">
31 <TabItem label="bun" icon="bun">
32 <Code code={`bun add -d ${devDeps}`} lang="sh" />
33 </TabItem>
34 <TabItem label="npm" icon="npm">
35 <Code code={`npm install -D ${devDeps}`} lang="sh" />
36 </TabItem>
37 <TabItem label="pnpm" icon="pnpm">
38 <Code code={`pnpm add -D ${devDeps}`} lang="sh" />
39 </TabItem>
40 <TabItem label="yarn" icon="seti:yarn">
41 <Code code={`yarn add -D ${devDeps}`} lang="sh" />
42 </TabItem>
43</Tabs>
44
45## Configure SvelteKit
46
47Your project's `vite.config.ts` loads natively — your Vite plugins
48and the kit options in your `sveltekit(...)` call all apply as usual.
49Alchemy injects its Node adapter (replacing any adapter you declare,
50with a warning), so a fresh SvelteKit project deploys as-is.
51Deploy-specific kit overrides can also be passed via the `kit` prop,
52which merges over your `sveltekit(...)` options — see
53[Kit options](#kit-options) below. A project without a
54`vite.config.*` works too: the resource falls back to a fully
55programmatic build.
56
57## Declare the Website
58
59Declare the site as a module-level const (rather than inline in the
60Stack):
61
62```typescript
63// alchemy.run.ts
64import * as Fly from "alchemy/Fly";
65
66export const Website = Fly.Website.SvelteKit("Website");
67```
68
69Omit `app` to create a `Fly.App` under the site namespace. Pass an
70existing App to share it with other resources.
71
72## Add it to the Stack
73
74Yield the site from your Stack and return its URL:
75
76```typescript
77// alchemy.run.ts
78import * as Alchemy from "alchemy";
79import * as Effect from "effect/Effect";
80
81export default Alchemy.Stack(
82 "MySvelteKitSite",
83 {
84 providers: Fly.providers(),
85 state: Alchemy.localState(),
86 },
87 Effect.gen(function* () {
88 const site = yield* Website;
89 return { url: site.url };
90 }),
91);
92```
93
94Routes with `export const prerender = true` are baked into the image
95and served as static files first; server routes (`+server.ts`) and
96server load functions run in the kit handler on the Machine.
97
98## Add environment variables
99
100Process environment is a top-level `env` map:
101
102```diff lang="typescript"
103// alchemy.run.ts
104export const Website = Fly.Website.SvelteKit("Website", {
105+ env: {
106+ GREETING: "Hello from alchemy",
107+ API_BASE: "https://api.example.com",
108+ },
109});
110```
111
112The values are copied onto `process.env` before the build (and the
113dev server) and onto the Machine at deploy time, so server code
114reads the same keys in both modes.
115
116## Read the environment in server code
117
118The Kit server runs in Node on the Machine, so server routes read
119the environment from `process.env` (or kit's `$env/dynamic/private`,
120which is initialized from it):
121
122```typescript
123// src/routes/+page.server.ts
124export const load = () => {
125 return { greeting: process.env.GREETING ?? "hello" };
126};
127```
128
129## Kit options
130
131Since kit v3 there is no `svelte.config.js` — kit options live in the
132`sveltekit(...)` call in your `vite.config.ts`, which loads natively:
133
134```typescript
135// vite.config.ts
136import { sveltekit } from "@sveltejs/kit/vite";
137import tailwindcss from "@tailwindcss/vite";
138import { defineConfig } from "vite";
139
140export default defineConfig({
141 plugins: [
142 tailwindcss(),
143 sveltekit({
144 alias: { $lib: "src/lib" },
145 }),
146 ],
147});
148```
149
150The `kit` prop on the resource is a deploy-time override layer merged
151over those options (the override wins). It must be JSON-serializable:
152
153```typescript
154// alchemy.run.ts
155export const Website = Fly.Website.SvelteKit("Website", {
156 kit: {
157 paths: { base: "/docs" },
158 },
159});
160```
161
162Don't set `kit.adapter` — Alchemy injects its Node adapter (an
163adapter declared in your `sveltekit(...)` call is replaced with a
164warning).
165
166## Local dev
167
168```sh
169bun alchemy dev
170```
171
172`alchemy dev` runs SvelteKit's own Vite dev server — Node SSR with
173full HMR — instead of deploying; `site.url` is the local address and
174no Fly resources are created (`app`, `service`, `ip`, and
175`certificate` are `undefined`). Wrap the site in `Alchemy.remote()`
176to deploy the live App even during dev.
177
178## Custom domain
179
180```typescript
181const site = yield* Fly.Website.SvelteKit("Web", {
182 domain: "app.example.com",
183});
184```
185
186Alchemy requests an ACME [`Fly.Certificate`](/fly/networking) on the
187App. Point DNS at the App first — v1 does not create records.
188`url` becomes `https://app.example.com`.
189
190## Where next
191
192- [`SvelteKit` API](/providers/fly/website/sveltekit)
193- [Apps](/fly/compute/apps) and [Services](/fly/compute/services)
194- [IPs & certificates](/fly/networking)