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 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, 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 with adapter-node instead.
What you'll build
- A SvelteKit project configured with
adapter-staticinstead ofadapter-auto - A DeployHQ 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 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-static3.x.adapter-static@3declares 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 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 account, start a free trial. 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, 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 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 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 = falseon 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 commonprerendering worked but my pages are empty
cause. +page.server.tsis 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, orurl.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 withThe following routes were marked as prerenderable, but were not prerendered.
Fix it by exporting anentriesfunction from the route, or by listing paths inkit.prerender.entries. - Standalone
+server.jsendpoints need their own opt-in. If an endpoint isn't fetched by a pageloadfunction during the crawl, addexport const prerender = trueto it directly.
Run npm run build locally and confirm build/ appears with an HTML file per route before you go near DeployHQ. 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 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 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) writesabout.htmltrailingSlash: 'always'writesabout/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 project, click New Server:
- Enter a name for the server (your reference only)
- Select Static Hosting from the protocol picker under the Hosting section
- 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 accounts - Set the subdirectory to deploy from to
build— SvelteKit's static output directory, notdist - Single Page Application (SPA) mode: on only if you chose the fallback path in Step 3. Off for a fully prerendered site
- Click Create Server
If your repository is already connected, DeployHQ 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 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 project's build pipeline 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 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:
- Clones the repo at HEAD
- Runs the build pipeline in an isolated container
- Runs
adapter-staticas part of the SvelteKit build, writing prerendered HTML tobuild/ - Uploads the configured subdirectory to object storage
- 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-autois 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. Addentries, or list it inkit.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 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 project.
One operational detail to file away, from the Static Hosting support library: 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, 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 to Cloudflare's edge
- HTTPS on the
deployhq-sites.comhostname with no certificate management - One-click rollback to any previous deployment
For background on where Static Hosting sits in the catalogue, the hosting hub covers the full set of options, and the Static Hosting pillar guide 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 is the direct comparison.
For the same walkthrough in other frameworks, see deploying a Next.js static site to DeployHQ Static Hosting and deploying an Astro site to DeployHQ Static Hosting. For a framework-agnostic static workflow, building and deploying your static site with DeployHQ 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 project:
- Switch to
adapter-nodeand 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. - 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 places DeployHQ alongside the rest of the landscape, and DeployHQ as a universal deployment and hosting platform covers all the hosting types in one place.
Check the current plans 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 or follow @deployhq on X for product updates.