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 installStep 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-cloudflareThe 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 developmentfallback: 'plaintext' gives you a simple 404 page; use 'spa' if you need client-side routing for unmatched pathsroutes.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 dashboardmain: The entry point SvelteKit generates—don't change thiscompatibility_flags:nodejs_alsis required for SvelteKit's async context;nodejs_compathelps with NPM packages that use Node.js APIscompatibility_date: Cloudflare's runtime version—bump this periodically for new featuresassets: Tells Workers where your static files liveroutes: 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 buildThis creates a .svelte-kit/cloudflare/ directory containing your optimized app.
Now install Wrangler CLI and deploy:
pnpm add -g wrangler
wrangler login
wrangler deployAfter 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:
- Go to Workers & Pages in your Cloudflare dashboard
- Select your Worker
- Click "Triggers" → "Add Custom Domain"
- 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:
- Check for large dependencies bundled on the server side
- Move heavy libraries to client-only imports if possible
- 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.jsThis 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:
| Feature | Workers | Pages |
|---|---|---|
| Static assets | Via assets binding | Native |
| Serverless functions | Single _worker.js | /functions directory |
| Request limits | 100k/day free | 100k/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.
