norns

Cloudflare Workers

adapter-cloudflare, the two compatibility flags the runtime needs, static assets, custom domains and a deploy workflow.

This is the setup the demo and the docs site use.

#svelte.config.js

import adapter from '@sveltejs/adapter-cloudflare';

export default nornsConfig({
  // ...preprocess
  kit: { adapter: adapter() }
});

#wrangler.toml

name = "my-app"
main = ".svelte-kit/cloudflare/_worker.js"
compatibility_date = "2026-04-01"
compatibility_flags = ["nodejs_als", "nodejs_compat"]

routes = [
  { pattern = "my-app.example.com", custom_domain = true }
]

[assets]
directory = ".svelte-kit/cloudflare"
binding = "ASSETS"

[observability]
enabled = true

The two compatibility flags are not optional:

  • nodejs_als provides AsyncLocalStorage, which the request scope is built on.
  • nodejs_compat is the general Node compatibility layer that Drizzle drivers and many libraries need.

custom_domain = true works when the zone is on Cloudflare; Workers issues the certificate. Otherwise point a CNAME at the workers.dev hostname.

Add a [[d1_databases]] block for a database; see Cloudflare D1.

#Local development

vite dev gets event.platform from the adapter's platform proxy, which reads wrangler.toml and keeps local state under .wrangler/state/. No account is needed to run locally. Add .wrangler/ to .gitignore.

#Deploying

bun run build && wrangler deploy

A GitHub Actions workflow that lints, checks, builds and deploys on every push to main:

name: Deploy
on:
  push:
    branches: [main]
  workflow_dispatch:
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install
      - run: bun run lint
      - run: bun run check
      - run: bun run build
      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          packageManager: bun
          command: deploy

The token needs Workers Scripts: Edit (and D1: Edit when the workflow applies migrations). Keep it in repository secrets, never in wrangler.toml.

#Prerendering

A fully prerendered app (like this documentation site) still produces a worker, but every page lands in the assets directory and the worker only runs for misses. Set export prerender := true in the root +layout.c.