4 min read
On this page

Adapters & Deployment

SvelteKit builds your application into an intermediate format, then an adapter transforms that output for a specific deployment target. Without an adapter, SvelteKit does not know whether you are deploying to a Node.js server, a serverless platform, or a CDN for static files. The adapter is the bridge between SvelteKit's universal output and your hosting platform's requirements.

How Adapters Work

During vite build, SvelteKit compiles your routes, load functions, actions, and server hooks into a platform-neutral format. The adapter then takes this output and restructures it for the target platform:

// svelte.config.js
import adapter from '@sveltejs/adapter-node';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter({
      out: 'build'
    })
  }
};

export default config;

The adapter runs after the main build. It reads the generated server code, client assets, and prerendered pages, then produces whatever the target platform expects: a Node.js server entry point, serverless function bundles, or static files.

adapter-node

The generic adapter for any Node.js environment. It produces a standalone server that you can run anywhere: a VPS, a Docker container, or any platform that runs Node.js.

// svelte.config.js
import adapter from '@sveltejs/adapter-node';

const config = {
  kit: {
    adapter: adapter({
      out: 'build',
      precompress: true, // Generate .gz and .br files for static assets
      envPrefix: 'APP_'  // Only expose env vars starting with APP_
    })
  }
};

export default config;

After building, run the server:

node build/index.js

The server listens on HOST and PORT environment variables (defaults to 0.0.0.0:3000). For Docker deployments:

FROM node:20-slim
WORKDIR /app
COPY build build
COPY package.json .
RUN npm ci --omit=dev
EXPOSE 3000
CMD ["node", "build/index.js"]

adapter-node is the right choice when you need full control over your server, when you are deploying to Kubernetes or Docker, or when your hosting provider runs generic Node.js apps.

adapter-cloudflare

Transforms your app into Cloudflare Workers and Pages functions. Code runs on Cloudflare's edge network, close to users worldwide.

// svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';

const config = {
  kit: {
    adapter: adapter({
      routes: {
        include: ['/*'],
        exclude: ['<all>']
      }
    })
  }
};

export default config;

Cloudflare Workers use the Web Standards API, not Node.js APIs. This means fetch, Request, Response, URL, and crypto work natively, but Node.js built-ins like fs, path, and child_process do not.

// src/routes/api/data/+server.ts
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ platform }) => {
  // Access Cloudflare-specific bindings through platform.env
  const value = await platform.env.MY_KV.get('key');
  const result = await platform.env.DB.prepare(
    'SELECT * FROM users WHERE id = ?'
  ).bind(1).first();

  return new Response(JSON.stringify({ value, result }), {
    headers: { 'Content-Type': 'application/json' }
  });
};

The platform object gives you access to Cloudflare bindings: KV, D1, R2, Durable Objects, and more.

adapter-vercel

Deploys to Vercel's serverless and edge infrastructure:

// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';

const config = {
  kit: {
    adapter: adapter({
      runtime: 'nodejs20.x', // or 'edge'
      regions: ['iad1'],
      split: false // Bundle all routes into one function
    })
  }
};

export default config;

You can configure per-route runtime targets:

// src/routes/api/fast/+server.ts
export const config = {
  runtime: 'edge'
};

// src/routes/api/heavy/+server.ts
export const config = {
  runtime: 'nodejs20.x',
  maxDuration: 30
};

adapter-static

Produces a fully static site with no server component. Every page must be prerenderable:

// svelte.config.js
import adapter from '@sveltejs/adapter-static';

const config = {
  kit: {
    adapter: adapter({
      pages: 'build',
      assets: 'build',
      fallback: '404.html',
      precompress: true
    })
  }
};

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

The output is a directory of HTML, CSS, and JS files. No server needed. Deploy to GitHub Pages, Cloudflare Pages (static mode), S3, or any static file host.

Choosing the Right Adapter

The decision tree is straightforward:

// All pages static, no server logic?
// → adapter-static (cheapest, fastest, simplest)

// Deploying to Cloudflare?
// → adapter-cloudflare (edge, fast globally, Cloudflare bindings)

// Deploying to Vercel?
// → adapter-vercel (serverless, edge options, Vercel integrations)

// Need full server control or deploying to Docker/VPS/Kubernetes?
// → adapter-node (most flexible, any Node.js host)

You can also use community adapters for platforms like AWS Lambda, Deno, Bun, and Netlify.

Environment Variables

Environment variables work differently at build time versus runtime, and the distinction matters:

// Build-time variables — baked into the output, cannot change after deploy
// Available in any file via $env/static/private and $env/static/public
import { API_KEY } from '$env/static/private';
import { PUBLIC_APP_URL } from '$env/static/public';

// Runtime variables — read at request time, can change without rebuilding
// Available in server-side code via $env/dynamic/private and $env/dynamic/public
import { DATABASE_URL } from '$env/dynamic/private';
import { PUBLIC_FEATURE_FLAG } from '$env/dynamic/public';
// src/routes/api/data/+server.ts
import { DATABASE_URL } from '$env/dynamic/private';
import { API_KEY } from '$env/static/private';

export async function GET() {
  // DATABASE_URL is read at request time — can change via env vars
  // API_KEY was baked in at build time — changing it requires a rebuild
  const db = connect(DATABASE_URL);
  const response = await fetch('https://api.example.com', {
    headers: { Authorization: `Bearer ${API_KEY}` }
  });

  return new Response('ok');
}

Public variables (prefixed with PUBLIC_) are safe to expose to the browser. Private variables are server-only and never sent to the client.

Edge vs Serverless vs Traditional Server

The three deployment models offer different trade-offs:

Edge (Cloudflare Workers, Vercel Edge): Code runs in data centers worldwide, close to users. Cold starts are near-instant. Limited to Web Standards APIs. Best for latency-sensitive routes with simple logic.

Serverless (Vercel Functions, AWS Lambda): Code runs in a single region. Cold starts can add latency. Full Node.js API available. Best for compute-heavy operations or when you need Node.js dependencies.

Traditional Server (adapter-node, Docker): A long-running process handles all requests. No cold starts. Full control over the runtime. Best for WebSocket connections, background jobs, or when you need persistent in-memory state.

// Edge route — fast globally, limited APIs
// src/routes/api/geo/+server.ts
export const config = { runtime: 'edge' };

export async function GET({ request }) {
  const country = request.headers.get('cf-ipcountry');
  return new Response(`You are in ${country}`);
}
// Serverless route — full Node.js, single region
// src/routes/api/report/+server.ts
export async function GET() {
  // Can use any Node.js library
  const pdf = await generatePDFReport();
  return new Response(pdf, {
    headers: { 'Content-Type': 'application/pdf' }
  });
}

Deploying to Cloudflare Pages

A complete workflow for deploying a SvelteKit app to Cloudflare Pages:

npm install -D @sveltejs/adapter-cloudflare
// svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';

const config = {
  kit: {
    adapter: adapter()
  }
};

export default config;
# Local development with Cloudflare bindings
npx wrangler pages dev -- npx vite dev
# Build and deploy
npx vite build
npx wrangler pages deploy .svelte-kit/cloudflare

For CI/CD, connect your GitHub repository to Cloudflare Pages. Set the build command to npx vite build and the output directory to .svelte-kit/cloudflare. Cloudflare rebuilds and deploys on every push.

To use Cloudflare bindings locally during development, create a wrangler.toml:

name = "my-app"
compatibility_date = "2024-01-01"

[[kv_namespaces]]
binding = "MY_KV"
id = "abc123"

[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "def456"

Common Pitfalls

  • Using Node.js APIs with edge adapters. Cloudflare Workers and Vercel Edge do not support fs, path, crypto.createHash, or other Node.js built-ins. Stick to Web Standards APIs or use polyfills where available.
  • Confusing build-time and runtime environment variables. $env/static/* is inlined at build time. Changing the value after deployment has no effect. Use $env/dynamic/* for values that should be configurable without rebuilding.
  • Not setting PUBLIC_ prefix for client-side variables. Only variables prefixed with PUBLIC_ are available in browser code. Accessing a private variable on the client throws an error at build time.
  • Forgetting adapter-specific limitations. Serverless functions have execution time limits. Edge functions have memory limits. Static adapters cannot handle form actions. Know your platform's constraints.
  • Deploying without testing the production build. vite dev behaves differently from the production build. Always run vite build && vite preview locally before deploying. Some issues only surface in the built output.

Key Takeaways

  • Adapters transform SvelteKit's build output for specific platforms. Choose based on your hosting target.
  • adapter-node for Docker, VPS, or any Node.js host. adapter-cloudflare for Cloudflare Workers/Pages. adapter-vercel for Vercel. adapter-static for fully static sites.
  • Environment variables split into four modules: $env/static/private, $env/static/public, $env/dynamic/private, $env/dynamic/public. Static is build-time, dynamic is runtime.
  • Edge deployment runs code close to users with near-zero cold starts but limits you to Web Standards APIs.
  • Serverless gives you full Node.js but adds cold start latency and runs in fewer regions.
  • Traditional servers offer the most flexibility: persistent connections, background work, and full runtime control.
  • Always test with vite build && vite preview before deploying. Development mode hides platform-specific issues.