The best SvelteKit hosting platforms in 2026
SvelteKit made the hosting decision explicit in a way most frameworks don't: you choose an adapter, and the adapter decides what your app can do. Pick adapter-static and server-side rendering, form actions, and API routes stop existing. Pick an edge adapter and Node APIs stop existing. Pick adapter-node and everything works but the CDN and scaling are now yours.
So the honest version of a SvelteKit hosting comparison is an adapter comparison with platform names attached. I build PandaStack, which sits in the adapter-node camp — noted where it matters.
The adapters, and what each costs you
adapter-static — a folder of files
Prerenders every route at build time and emits HTML, CSS, and JS. Deploy it to any static host or object store, and it's essentially free and impossible to break. What you give up is everything that runs per-request: form actions, +page.server.ts load functions that read cookies or a database, API routes, and hooks.server.ts.
Right for docs, marketing sites, blogs, and any app whose data comes from a separate API and is fetched in the browser. Wrong the moment you want a session.
Edge adapters — Cloudflare, Vercel edge, Netlify edge
Your server code runs in a V8 isolate close to the user. Latency is excellent and the pricing is often the cheapest of the three. The constraint is the runtime: it isn't Node. No filesystem, no native modules, no Node-only libraries, and database drivers need to be the HTTP or WebSocket-based variety rather than a classic TCP client.
This is a great fit for an app that talks to an HTTP API or a serverless database and does modest work per request. It's a rewrite for anything with a conventional Postgres driver, an image pipeline, or a native dependency.
adapter-node — a real server
Builds a plain Node server you start with `node build`. Every SvelteKit feature works because nothing is being translated: hooks, form actions, streaming, server-only modules, long-lived connections, in-memory caches. You can add a WebSocket server in the same process.
What you take on: a CDN in front for static assets (usually Cloudflare, ten minutes of work), your own scaling decisions, and a process to keep alive. For an app with real server-side logic, this is the option with no asterisks, and it runs on any container PaaS, VPS, or microVM platform.
// svelte.config.js — the single most consequential file for hosting
import adapter from "@sveltejs/adapter-node";
export default {
kit: {
adapter: adapter({ out: "build" }),
// Trust the platform's proxy headers, or every request looks like
// it came from 127.0.0.1 and Origin checks on form actions fail.
csrf: { checkOrigin: true },
},
};Which platform, given an adapter
- adapter-static: any static host — Cloudflare Pages, Netlify, GitHub Pages, an object store behind a CDN. Cost is approximately zero. Nothing to operate.
- Edge adapters: Cloudflare Workers, Vercel, Netlify. Fast and cheap when your dependencies fit the isolate runtime. Verify your database driver first — that's the usual blocker.
- adapter-node on a container PaaS: Render, Railway, Fly.io, Koyeb, Northflank. The default for an app with server-side logic. Put a CDN in front for assets.
- adapter-node on a microVM platform: Fly Machines, PandaStack (mine). Same deploy shape with a hardware-isolated VM and its own kernel. Relevant when the app runs code it didn't write, when per-tenant isolation must survive a security review, or when the app provisions environments at runtime — a fresh microVM via snapshot-restore is about 179ms at p50.
- adapter-node on a VPS: still fine. systemd, a reverse proxy, and a deploy script.
Deploying adapter-node without a Dockerfile
# Pin Node in the repo
echo "22.11.0" > .nvmrc
# Build produces ./build; start it with node
pandastack apps create --name dashboard \
--git-url https://github.com/acme/dashboard \
--build-cmd 'npm ci && npm run build' \
--start-cmd 'node build'
# adapter-node reads PORT, HOST and ORIGIN from the environment
# ORIGIN must be the public URL or form actions 403Four checks before you commit
- Submit a form action from the deployed URL. A 403 here is proxy header configuration, and it's better to find it now.
- If you chose an edge adapter, deploy with your real database driver early. Driver incompatibility is the usual dealbreaker and it surfaces at request time, not build time.
- Check that static assets are served with sensible cache headers. adapter-node serves them from the Node process by default, which works and wastes your CPU at any real traffic level.
- Confirm which env namespace each secret comes from before wondering why it's undefined.
The short version
Content site with no sessions: adapter-static on any static host, for free. Server logic with dependencies that fit an isolate: an edge adapter, and check your database driver first. Real server-side app: adapter-node on a container PaaS or microVM platform with a CDN in front — no feature asterisks, and portable between platforms because it's just a Node server. The adapter choice is the hosting choice; make it deliberately rather than by copying a template.
Frequently asked questions
Which SvelteKit adapter should I use?
Work backwards from what your app does per request. If every route can be prerendered and all data is fetched from the browser, adapter-static gives you a folder of files that any static host serves for nearly nothing. If you have server-side logic but your dependencies work in a V8 isolate — meaning no native modules, no filesystem access, and an HTTP-based database driver — an edge adapter is fast and cheap. If you have conventional server-side code, a normal Postgres driver, or anything long-lived, adapter-node builds a plain Node server where every SvelteKit feature works and which runs on any platform that runs Node.
Why do my SvelteKit form actions return 403 in production?
SvelteKit checks that the Origin header of a POST matches the request's own host, as CSRF protection. Behind a reverse proxy that doesn't forward the original host and protocol, the app computes its own origin as something like http://127.0.0.1:3000 while the browser sends your real domain, the two don't match, and every form action is rejected. With adapter-node the fix is to set the ORIGIN environment variable to your public URL, or to configure the protocol and host headers the adapter reads from the proxy. It reads as an authentication bug and is entirely a header-forwarding bug.
Can I self-host SvelteKit?
Yes, and adapter-node makes it straightforward. The build output is an ordinary Node server started with `node build`, so a VPS with systemd and a reverse proxy, a container on any PaaS, or a microVM all work identically. You take on three things the managed platforms were doing: serving static assets efficiently, which means putting a CDN in front rather than letting Node serve them; deciding how many instances to run; and keeping the process alive across restarts. None is difficult, and in exchange you get a deployment with no adapter translation layer and no feature caveats.
Why is my environment variable undefined in SvelteKit?
You probably imported it from $env/static/private or $env/static/public, which are inlined at build time, while your platform supplies the variable only when the process starts. The build saw nothing and inlined undefined. Use $env/dynamic/private or $env/dynamic/public for values that are only known at runtime, and reserve the static namespaces for values your build genuinely has. The public and private split is a separate axis: anything from a public namespace ships to the browser, so a secret must never come from one regardless of which timing you choose.
Does adapter-node need a CDN?
It works without one and you'll want one at any real traffic level. adapter-node serves your built static assets straight from the Node process, which means every hashed JavaScript chunk and image consumes event-loop time that should be handling requests. Putting a CDN in front — Cloudflare in front of your origin is ten minutes of work — moves that traffic off the app entirely, adds edge caching for prerendered pages, and gives you somewhere sensible to terminate TLS. It also insulates you from a traffic spike that would otherwise be absorbed by your single Node process.
Keep reading
- App hosting on PandaStack — runs the adapter-node build straight from your repo
- The best Next.js hosting platforms in 2026
- The best Express and Node.js hosting platforms
- Build-time vs runtime environment variables
- Why your JavaScript build runs out of memory
49ms p50 cold start. Fork, snapshot, and scale to zero.