Deploying SvelteKit to Cloudflare Workers for Free

For years, deploying a full-stack web app meant either paying for a VPS or wrestling with complex container orchestration. Cloudflare Workers changes that. You can run SvelteKit with server-side rendering, API routes, and edge caching—all without spending a dime on the generous free tier.

This guide walks through the exact setup I use for this site. No theoretical fluff, just working configuration.

What You Get for Free

Cloudflare's free tier is surprisingly capable:

  • 100,000 requests/day — enough for most personal projects and small sites
  • 10ms CPU time per request — SvelteKit runs comfortably within this
  • 1 GB of KV storage — for simple config or session data
  • Custom domains — connect your own domain at no extra cost

Compare this to Vercel's hobby tier (limited to 10s serverless function duration) or Netlify's build minute limits. Workers' V8 isolate model means faster cold starts and more predictable pricing if you ever need to scale.

Prerequisites

  • Node.js 18+ installed
  • A Cloudflare account (free tier works fine)
  • A SvelteKit project ready to deploy

If you don't have a SvelteKit project yet:

pnpx sv create my-app
cd my-app
pnpm install

Step 1: Install the Adapter

Cloudflare Workers runs JavaScript in V8 isolates, not Node.js. SvelteKit needs an adapter to bridge this gap:

pnpm add -D @sveltejs/adapter-cloudflare

The adapter-cloudflare package handles both Workers and Pages deployment. For new Workers Static Assets (the modern approach), this is the adapter you want—not the older adapter-cloudflare-workers.

Step 2: Configure svelte.config.js

Replace the default adapter in your svelte.config.js:

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

/** @type {import('@sveltejs/kit').Config} */
const config = {
	preprocess: vitePreprocess(),
	kit: {
		adapter: adapter({
			// See below for options
			config: undefined,
			platformProxy: {
				configPath: undefined,
				environment: undefined,
				persist: undefined
			},
			fallback: 'plaintext',
			routes: {
				include: ['/*'],
				exclude: ['<files>', '<build>', '<redirects>']
			}
		})
	}
};

export default config;

Key options explained:

  • config: Path to your Wrangler config file (we'll create this next)
  • platformProxy: Controls how local bindings are emulated during development
  • fallback: 'plaintext' gives you a simple 404 page; use 'spa' if you need client-side routing for unmatched paths
  • routes.exclude: Tells Cloudflare which requests can bypass the Worker and serve static assets directly—this saves you invocation costs

Step 3: Create wrangler.jsonc

Create a wrangler.jsonc file in your project root:

{
	"name": "my-sveltekit-app",
	"main": ".svelte-kit/cloudflare/_worker.js",
	"compatibility_flags": ["nodejs_als", "nodejs_compat"],
	"compatibility_date": "2024-09-23",
	"assets": {
		"binding": "ASSETS",
		"directory": ".svelte-kit/cloudflare"
	},
	"routes": [
		{
			"pattern": "yourdomain.com",
			"custom_domain": true
		}
	]
}

What each field does:

  • name: Your Worker's identifier in the Cloudflare dashboard
  • main: The entry point SvelteKit generates—don't change this
  • compatibility_flags: nodejs_als is required for SvelteKit's async context; nodejs_compat helps with NPM packages that use Node.js APIs
  • compatibility_date: Cloudflare's runtime version—bump this periodically for new features
  • assets: Tells Workers where your static files live
  • routes: Connects custom domains (you can add this later if you don't have a domain yet)

Step 4: Build and Deploy

Build your app locally first to verify everything works:

pnpm build

This creates a .svelte-kit/cloudflare/ directory containing your optimized app.

Now install Wrangler CLI and deploy:

pnpm add -g wrangler
wrangler login
wrangler deploy

After the first deploy, Cloudflare gives you a *.workers.dev subdomain—free hosting, no domain required.

Step 5: Connect a Custom Domain (Optional)

If you have a domain managed by Cloudflare:

  1. Go to Workers & Pages in your Cloudflare dashboard
  2. Select your Worker
  3. Click "Triggers" → "Add Custom Domain"
  4. Enter your domain

If your DNS is elsewhere, add a CNAME record pointing to your *.workers.dev subdomain.

Common Gotchas

Node.js Compatibility

Not all pnpm packages work in Workers. Anything relying on fs, native bindings, or certain Node.js internals will fail. Check the Cloudflare Workers runtime API docs before adding heavy dependencies.

If you hit issues, try adding the nodejs_compat flag (shown in the config above). This enables polyfills for common Node.js modules.

Environment Variables

Don't use process.env in Workers. Instead, use SvelteKit's built-in $env modules:

// +page.server.js
import { SECRET_API_KEY } from '$env/static/private';

export async function load() {
	// Use SECRET_API_KEY here
}

For Cloudflare-specific bindings (KV, Durable Objects), access them via the platform object:

// hooks.js or +server.js
export async function handle({ event, resolve }) {
	const { env } = event.platform;
	// env.MY_KV_NAMESPACE, env.MY_DURABLE_OBJECT, etc.
	return resolve(event);
}

Worker Size Limits

The final Worker bundle must stay under Cloudflare's size limits (currently around 1MB gzipped). If your build fails with a size error:

  1. Check for large dependencies bundled on the server side
  2. Move heavy libraries to client-only imports if possible
  3. Use dynamic imports for code splitting

Testing Locally

You can test the production build locally with Wrangler:

pnpm build
wrangler dev .svelte-kit/cloudflare/_worker.js

This runs the exact same code that deploys to production, including platform bindings if you've configured them in wrangler.jsonc.

GitHub Actions for CI/CD

For automatic deployments on push:

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: pnpm install
      - run: pnpm build
      - run: pnpx wrangler deploy
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Create a Cloudflare API token with "Cloudflare Workers" edit permissions and add it as CLOUDFLARE_API_TOKEN in your repository secrets.

Why Not Cloudflare Pages?

Both Workers and Pages can host SvelteKit, but there are differences:

FeatureWorkersPages
Static assetsVia assets bindingNative
Serverless functionsSingle _worker.js/functions directory
Request limits100k/day free100k/day free
Build output.svelte-kit/cloudflare.svelte-kit/cloudflare

I prefer Workers because the adapter-cloudflare generates a single _worker.js file that handles routing, SSR, and static assets. It's simpler mentally—one entry point, one mental model.

Monitoring Your Usage

The Cloudflare dashboard shows your request volume and CPU time. Keep an eye on:

  • Requests: Free tier = 100k/day. At ~10k requests/day, you've got a 10-day buffer.
  • CPU time: SvelteKit usually runs under 5ms per request unless you're doing heavy computation.

If you're approaching limits, consider:

  • Prerendering more pages at build time
  • Caching API responses at the edge
  • Using KV for frequently-read, rarely-written data

Conclusion

Deploying SvelteKit to Cloudflare Workers gives you a production-grade hosting stack at zero cost. The edge runtime means faster response times for global visitors, and the free tier is genuinely usable—not just a teaser to get you hooked.

The setup is minimal: install an adapter, add a config file, run two commands. The rest is just SvelteKit doing what it does best.

Asaduzzaman Pavel

About the Author

Asaduzzaman Pavel is a Software Engineer who actually enjoys the friction of a well-architected system. He has over 15 years of experience building high-performance backends and infrastructure that can actually handle the real-world chaos of scale.

Currently looking for new opportunities to build something amazing.