SvelteKit's adapter ecosystem gives you a clean path to static output via `@sveltejs/adapter-static`. Once the adapter is configured and your routes are prerenderable, [DeployHQ Static Hosting](https://www.deployhq.com/hosting/static) is a natural target — serve the build from Cloudflare's edge with atomic deploys and rollback.

This guide walks through deploying a SvelteKit site end to end: swap `adapter-auto` for `adapter-static`, decide between full prerendering and an SPA fallback, set `trailingSlash` before you build, provision the site in [DeployHQ](https://www.deployhq.com), configure the build pipeline, and ship the first deploy. If your SvelteKit project has routes that genuinely need to render at request time — form actions, session-aware pages, anything reading cookies or headers per request — Static Hosting isn't the right fit. Use [DeployHQ Managed VPS Hosting](https://www.deployhq.com/hosting/managed-vps) with `adapter-node` instead.

## What you'll build

- A SvelteKit project configured with `adapter-static` instead of `adapter-auto`
- A [DeployHQ](https://www.deployhq.com) project with Static Hosting connected to your repository
- A build pipeline that runs the SvelteKit build and uploads the output to Cloudflare's edge
- The site live on `<subdomain>.deployhq-sites.com`
- Atomic deploys on every push, with rollback from the [DeployHQ](https://www.deployhq.com) dashboard

Expected time: under 15 minutes for a fresh project. The adapter swap adds a step compared with Next.js and Astro, both of which produce static output without changing a dependency.

## Prerequisites

- **SvelteKit 2.x with `@sveltejs/adapter-static` 3.x.** `adapter-static@3` declares a peer dependency on `@sveltejs/kit@^2.0.0`, so the v3 adapter and SvelteKit 2 go together. Svelte 5 is the current major; SvelteKit 2 supports both Svelte 4 and Svelte 5.
- A Git repository the project lives in
- **A [DeployHQ](https://www.deployhq.com) account with beta features enabled.** Static Hosting is a beta feature — turn it on under **Settings \> Beta Features** before you start, or the protocol won't appear in the picker
- Node.js locally, so you can run the build once before pushing

If you don't have a [DeployHQ](https://www.deployhq.com) account, [start a free trial](https://www.deployhq.com/signup). Static Hosting is free while it's in beta, and a trial account can provision one site — enough to follow this guide end to end.

## Step 1: Swap `adapter-auto` for `adapter-static`

SvelteKit scaffolds new projects with `adapter-auto`, which inspects the build environment and picks a platform adapter at build time. It knows about Vercel, Netlify, Cloudflare Pages and a handful of others. It does not know about [DeployHQ](https://www.deployhq.com), and it does not fall back to static output.

That's the source of an error a lot of people hit the first time they build SvelteKit anywhere outside the big three:

```
Could not detect a supported production environment.
See https://svelte.dev/docs/kit/adapters to learn how to configure your app
to run on the platform of your choice
```

This is not a [DeployHQ](https://www.deployhq.com) problem and it isn't a bug. It's `adapter-auto` telling you it has nothing to auto-detect. The fix is to stop auto-detecting and name the adapter you actually want:

```
npm install -D @sveltejs/adapter-static
npm uninstall @sveltejs/adapter-auto
```

Removing `adapter-auto` is optional — installing `adapter-static` and referencing it in the config is what matters — but taking it out means nobody on the team accidentally reintroduces the ambiguity later.

Now update `svelte.config.js`:

```
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

export default {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter({
      pages: 'build',
      precompress: false,
      strict: true,
    }),
  },
};
```

Three things worth knowing about that config, because they trip people up:

**`vitePreprocess` moved.** In SvelteKit 1 you imported it from `@sveltejs/kit/vite`. SvelteKit 2 made `@sveltejs/vite-plugin-svelte` a peer dependency and stopped re-exporting `vitePreprocess`, so that import path no longer resolves. A lot of tutorials and LLM-generated configs still use the old one. If your build dies on an unresolved import before it gets anywhere near the adapter, this is why.

**`assets` defaults to `pages`.** You'll see `assets: 'build'` in plenty of examples alongside `pages: 'build'`. It's redundant — the adapter defaults `assets` to whatever `pages` is. Only set it if you're genuinely splitting prerendered HTML and static assets into different directories.

**`pages: 'build'` is not `dist`.** SvelteKit's static output directory is `build`. Vite's convention — and Astro's — is `dist`. That matters in Step 5, when [DeployHQ](https://www.deployhq.com) asks which subdirectory to deploy from.

There's no `fallback` key in that config, and that's deliberate. The next two steps explain why.

## Step 2: Mark routes as prerenderable

Add `export const prerender = true;` to the root layout to opt the whole site in:

```
// src/routes/+layout.ts
export const prerender = true;
```

`prerender` can be exported from `+page.js`, `+page.server.js`, `+layout.js`, `+layout.server.js`, or `+server.js`, and it accepts `true`, `false`, or `'auto'`. Setting it once on the root layout and overriding per-route is the pattern that scales.

A few things the SvelteKit docs are explicit about that are easy to miss:

- **Don't set `ssr = false` on a page you want prerendered.** If SSR is off, prerendering writes an empty shell instead of rendered content. You get HTML files, the build passes, and the pages are blank to anyone without JavaScript — including crawlers that don't execute it. This is the single most common prerendering worked but my pages are empty cause.
- **`+page.server.ts` is fine to prerender.** Server load functions run at build time and their data is baked into the HTML. You do _not_ need to move the fetch to `+page.ts`. What can't be prerendered is a page that has form actions, or one that reads request-specific state like cookies, headers, or `url.searchParams`.
- **Parameterised routes aren't crawled by default.** A route like `/blog/[slug]` only gets prerendered if the crawler finds a link to each instance. If nothing links to them, the build fails with The following routes were marked as prerenderable, but were not prerendered. Fix it by exporting an `entries` function from the route, or by listing paths in `kit.prerender.entries`.
- **Standalone `+server.js` endpoints need their own opt-in.** If an endpoint isn't fetched by a page `load` function during the crawl, add `export const prerender = true` to it directly.

Run `npm run build` locally and confirm `build/` appears with an HTML file per route before you go near [DeployHQ](https://www.deployhq.com). If it fails here, it will fail in the pipeline, and the local error message is easier to read.

If `adapter-static` finds routes it can't prerender, it prints the offending route IDs and lists your options — adjust `prerender.entries`, add `prerender = true` to the root layout, opt in your `+server.js` files, set a `fallback`, or pass `strict: false`. Read the route list before reaching for `strict: false`; it's almost always telling you something true about the app.

For the wider SvelteKit picture — `adapter-node`, PM2, reverse proxies, branch-based environments — the [SvelteKit deployment guide](https://www.deployhq.com/guides/sveltekit) covers the server-rendered path this article deliberately skips.

## Step 3: Decide between full prerendering and an SPA fallback

This is the fork in the road, and getting it wrong is the difference between a site search engines index and a site they see as one page.

**Full prerendering (recommended).** Every route becomes its own HTML file. Crawlers get distinct, fully-rendered pages. No `fallback` key in the adapter config. This is what Step 1's config does.

**SPA fallback.** The adapter writes one shell page that the client router takes over from. SvelteKit's own documentation is blunt about the cost: a fallback has large negative performance and SEO impacts and is only recommended in certain circumstances. Unknown paths return the shell with a 200 status, so search engines see every dynamic URL as the same page.

If you genuinely need the SPA path, three DeployHQ-specific details matter:

**The fallback filename must be `index.html`, not `200.html`.** SvelteKit's docs suggest `200.html` and note it may differ from host to host. [DeployHQ](https://www.deployhq.com) Static Hosting's SPA mode rewrites unknown paths to `/index.html`. So on DeployHQ:

```
adapter: adapter({
  pages: 'build',
  fallback: 'index.html',
})
```

**Setting `fallback` silently disables `strict`.** The adapter's dynamic-route validation only runs when no fallback is configured. Set `fallback` and the check is skipped entirely, regardless of what `strict` says. You lose the build-time warning that a route isn't prerenderable — it just quietly becomes a client-rendered path. Keep that in mind when a route mysteriously stops appearing in your sitemap.

**`index.html` collides with a prerendered homepage.** The adapter writes prerendered pages into the output directory first, then writes the fallback into that same directory. If `/` is prerendered _and_ your fallback is named `index.html`, both target `build/index.html`. For a true SPA that's fine, because you shouldn't be prerendering `/` anyway — SvelteKit's SPA guidance is to set `export const ssr = false;` in the root layout, then opt individual routes back in with `prerender = true` and `ssr = true` where you can. For a mostly-prerendered site, don't set a fallback at all.

In practice: if this is a marketing site, docs site, or blog, prerender everything and leave `fallback` unset. Reach for the fallback only when the app is genuinely client-rendered behind a login.

## Step 4: Set `trailingSlash` before your first build

`trailingSlash` looks like a canonical-URL preference. On a static host it's closer to a functional setting, because it changes the filenames on disk.

- `trailingSlash: 'never'` (the default) writes `about.html`
- `trailingSlash: 'always'` writes `about/index.html`

SvelteKit's adapter docs state the constraint directly: if your host doesn't serve `/a.html` in response to a request for `/a`, you need `trailingSlash: 'always'`. Extensionless URL resolution is a per-host behaviour and DeployHQ's Static Hosting documentation doesn't currently specify it either way.

The deterministic option is `'always'`. Directory-style output means every route resolves through an `index.html`, which every static host serves. If you deploy with the default and find your homepage works while `/about` returns 404, this is the setting to change.

```
// svelte.config.js
kit: {
  trailingSlash: 'always',
  // ...
}
```

Decide this before launch. Changing it later rewrites every URL on the site and needs redirects to avoid breaking inbound links.

## Step 5: Provision a Static Hosting site in DeployHQ

In your [DeployHQ](https://www.deployhq.com) project, click **New Server** :

1. Enter a name for the server (your reference only)
2. Select **Static Hosting** from the protocol picker under the **Hosting** section
3. Choose a **subdomain** — the site serves at `<subdomain>.deployhq-sites.com`. Lowercase letters, numbers and hyphens only, and it has to be unique across all [DeployHQ](https://www.deployhq.com) accounts
4. Set the **subdirectory to deploy from** to `build` — SvelteKit's static output directory, not `dist`
5. **Single Page Application (SPA) mode**: on only if you chose the fallback path in Step 3. Off for a fully prerendered site
6. Click **Create Server**

If your repository is already connected, [DeployHQ](https://www.deployhq.com) pre-fills the subdirectory and SPA toggle from framework detection and shows a callout naming the framework it found. Detection runs as a rule-based pass over public manifest files (`package.json`, `vite.config.ts` and similar), with an AI-assisted fallback for projects the rules can't classify confidently — and only those manifest files are ever sent, never application source or environment files. Detection is advisory; override anything before clicking **Create Server**.

Worth knowing: [DeployHQ](https://www.deployhq.com) runs a preflight check before each deploy that compares your configured subdirectory against what the detected framework actually builds to. If they disagree — the classic case being a SvelteKit project pointed at `dist` out of Vite muscle memory — the deploy still runs, but the build log carries a warning. Read that warning rather than wondering why the edge is serving nothing.

Provisioning takes under a minute. The site is live on the edge but empty until the first deploy.

## Step 6: Configure the build pipeline

In the [DeployHQ](https://www.deployhq.com) project's [build pipeline](https://www.deployhq.com/features/build-pipelines) settings, add:

```
npm ci
npm run build
```

Substitute `pnpm install --frozen-lockfile && pnpm build` or `yarn install --frozen-lockfile && yarn build` for other package managers.

For environment variables, SvelteKit splits them by prefix:

- **`PUBLIC_*`** — available through `$env/static/public`, importable from client code, inlined into the client bundle
- **Everything else** — available through `$env/static/private`, which SvelteKit refuses to let you import into client-side code

One nuance that matters more on a static build than a server-rendered one: `$env/static/private` protects you from _importing_ a secret into the browser bundle. It does not protect you from _rendering_ one. Static env values are statically replaced at build time, and on a prerendered site the build output is the shipped artifact. If a private value ends up in the HTML a page renders, it ships to every visitor in plain text. Use private vars for build-time work — fetching from an API, reading a CMS — and check the generated HTML in `build/` before your first production deploy.

The corollary: because values are baked in at build time, changing an environment variable in [DeployHQ](https://www.deployhq.com) has no effect until you redeploy. There is no runtime to read the new value.

## Step 7: First deploy

Push to the configured branch or trigger a manual deploy. DeployHQ:

1. Clones the repo at HEAD
2. Runs the build pipeline in an isolated container
3. Runs `adapter-static` as part of the SvelteKit build, writing prerendered HTML to `build/`
4. Uploads the configured subdirectory to object storage
5. Flips edge routing to the new version once the upload is complete

The upload is atomic — routing only switches after the full build is in place, so visitors never see a half-deployed site. DeployHQ's atomic-transfer and accelerated-transfer options don't apply here and are hidden from Deployment Options for Static Hosting servers; there's no SSH transfer to tune.

Watch the deployment log. When it completes, visit `https://<subdomain>.deployhq-sites.com`.

If the build fails, the log names the cause. The three failures worth recognising on sight:

- **`Could not detect a supported production environment`** — `adapter-auto` is still wired up. Go back to Step 1.
- **`all routes must be fully prerenderable, but found the following routes that are dynamic`** — read the route list the adapter prints. Usually a form action, or a parameterised route the crawler never reached.
- **`The following routes were marked as prerenderable, but were not prerendered`** — the opposite problem. The route opted in but nothing linked to it. Add `entries`, or list it in `kit.prerender.entries`.

## Step 8: Your site's hostname

Static Hosting sites serve from `<subdomain>.deployhq-sites.com`. You can rename the subdomain at any time from the site's settings, and the change takes effect immediately — but so does the removal: the old hostname starts returning 404 right away, with no redirect. If the old name is in circulation anywhere, rename before you publicise it, not after. Renames are limited to one per 24 hours.

Custom domains aren't available on Static Hosting yet — sites serve from their `deployhq-sites.com` hostname. If serving from your own domain matters for your project, email [support@deployhq.com](mailto:support@deployhq.com) and say so; customer demand is what drives what gets built next here. If you need your own hostname today, the same build can deploy to an S3-compatible bucket (S3, R2, Spaces) behind your own CDN from the same [DeployHQ](https://www.deployhq.com) project.

One operational detail to file away, from the [Static Hosting support library](https://www.deployhq.com/support/servers/static-hosting): sites are suspended along with the account — a lapsed trial, a failed renewal — and show a suspension page while the files and configuration are preserved. The grace period is 7 days for trial accounts and 30 days for paid, with a warning email 5 days before removal.

## Common gotchas

**Form actions block the build, not the request.** It's tempting to assume form actions simply fail at runtime on a static host. They don't get that far: pages with actions cannot be prerendered at all, because a server has to handle the POST. The build fails first. Move the form to an external endpoint — a backend on [DeployHQ Managed VPS](https://www.deployhq.com/blog/managed-vps-hosting-on-deployhq), a Cloudflare Worker, or a form service — and point a plain HTML `action` attribute at its absolute URL.

**`hooks.server.ts` runs at build time only.** Server hooks execute during prerendering and then cease to exist. Auth checks, rewrites, and header manipulation written as hooks will appear to work locally in `dev` and silently do nothing in production. Move that logic client-side, or put a Cloudflare Worker in front of the site.

**SPA fallback makes every URL look like the homepage.** With a fallback configured, unknown paths return the shell with a 200. Users navigate fine; crawlers index duplicates. If a route has SEO value, prerender it explicitly.

**Changing the deploy subdirectory doesn't retroactively fix a deploy.** SPA mode and the subdirectory can both be edited after provisioning, but changes apply from the next deployment. Trigger a redeploy after editing them.

**Static Hosting isn't available in project templates.** A Static Hosting site is a real resource tied to one account — a hostname, a storage prefix, a routing record — not a copyable configuration blueprint. Provision it normally and reuse the project's config files when scaffolding the next project.

## What you've shipped

- A SvelteKit site rebuilding on every push with `adapter-static`
- [Zero-downtime atomic transfers](https://www.deployhq.com/features/zero-downtime-deployments) to Cloudflare's edge
- HTTPS on the `deployhq-sites.com` hostname with no certificate management
- [One-click rollback](https://www.deployhq.com/features/one-click-rollback) to any previous deployment

For background on where Static Hosting sits in the catalogue, the [hosting hub](https://www.deployhq.com/hosting) covers the full set of options, and the [Static Hosting pillar guide](https://www.deployhq.com/blog/static-hosting-on-deployhq) goes deeper on framework auto-detection and SPA mode. If you're weighing the managed edge against the obvious alternative, [Static Hosting against Cloudflare Pages](https://www.deployhq.com/blog/deployhq-static-hosting-vs-cloudflare-pages) is the direct comparison.

For the same walkthrough in other frameworks, see [deploying a Next.js static site to](https://www.deployhq.com/blog/deploy-nextjs-static-site-to-deployhq-static-hosting)[DeployHQ](https://www.deployhq.com) Static Hosting and [deploying an Astro site to](https://www.deployhq.com/blog/deploy-astro-site-to-deployhq-static-hosting)[DeployHQ](https://www.deployhq.com) Static Hosting. For a framework-agnostic static workflow, [building and deploying your static site with DeployHQ](https://www.deployhq.com/blog/using-deployhq-to-build-your-static-site) covers the general shape.

## What's next

If your SvelteKit project picks up server-rendered requirements — form actions, authenticated routes, anything that has to render per request — there are two upgrade paths inside the same [DeployHQ](https://www.deployhq.com) project:

1. **Switch to `adapter-node` and deploy to Managed VPS.** SvelteKit's Node adapter produces a Node server; Managed VPS hosts it. The build pipeline you configured here doesn't change — only the deployment target does.
2. **Split the static and dynamic surfaces.** Marketing pages, docs and blog stay prerendered on Static Hosting. Dynamic functionality runs on Managed VPS or a BYO server. Both ship from the same project, same push.

The second option is usually the better trade. Prerendered pages are faster and cheaper to serve than server-rendered ones, and moving an entire site to `adapter-node` because three routes need a server gives up that advantage everywhere.

For a category-wide view, [best software deployment tools in 2026](https://www.deployhq.com/blog/best-software-deployment-tools) places [DeployHQ](https://www.deployhq.com) alongside the rest of the landscape, and [DeployHQ as a universal deployment and hosting platform](https://www.deployhq.com/blog/deployhq-your-universal-deployment-platform-for-all-hosting-types) covers all the hosting types in one place.

[Check the current plans](https://www.deployhq.com/pricing) if you don't have an account yet — the trial's included Static Hosting site is enough to ship this guide end to end.

* * *

Questions or feedback on SvelteKit + Static Hosting? Email [support@deployhq.com](mailto:support@deployhq.com) or follow [@deployhq](https://x.com/deployhq) on X for product updates.

