sveltekit-static-to-remote Svelte Themes

Sveltekit Static To Remote

๐Ÿ“ฑ๐ŸŒ Demo: Call SvelteKit remote functions from a static frontend to a separate backend deployment. Perfect for CDNs, mobile apps (Tauri/Capacitor), and serverless architectures.

๐Ÿš€ SvelteKit Static-to-Remote

Call SvelteKit remote functions from a static frontend to a separate backend deployment

https://github.com/user-attachments/assets/dae1146a-9391-4ab9-911e-b951b2fe7942

In this demo, the static frontend example is deployed on Surge CDN, while the backend is running locally and exposed through a Cloudflare tunnel. You can see that the remote functions are calling the backend URL instead of the frontend URL.


โšก TLDR

๐ŸŽฏ Problem: SvelteKit remote functions only work within the same deployment, but you want static frontend + separate backend.

๐Ÿ’ก Solution: Service worker magic!

  1. Same file paths in both apps โ†’ identical endpoint hashes
  2. Service worker intercepts /_app/remote/[HASH]/call โ†’ redirects to backend
  3. Backend CORS handles cross-origin requests securely
  4. Result: Seamless remote function calls across deployments! โœจ

Perfect for: Static sites โ€ข Mobile apps (Tauri/Capacitor) โ€ข CDN deployments โ€ข Serverless architectures


๐Ÿš€ Quick Start

# Clone and install
git clone https://github.com/robinbraemer/sveltekit-static-to-remote.git
cd sveltekit-static-to-remote && pnpm install

# Start backend (terminal 1)
cd apps/backend && pnpm dev  # http://localhost:5174

# Build and serve frontend (terminal 2)
cd apps/frontend && pnpm build && pnpx serve build  # http://localhost:3000

๐Ÿงช Test: Open http://localhost:3000 โ†’ try the form, buttons, text converter โ†’ functions execute on backend!


๐Ÿ”ง How It Works

The Secret: SvelteKit generates remote function hashes based on file paths, not code.

  1. Same file paths (src/lib/all.remote.ts) in both apps โ†’ identical hashes
  2. Service worker (service-worker.ts) intercepts /_app/remote/[HASH]/call
  3. Redirects to backend server with custom X-SvelteKit-Remote header
  4. Backend CORS (hooks.server.ts) allows cross-origin calls
  5. Backend recognizes hash โ†’ executes function โ†’ returns result

This means: Static app calls non-existent endpoints โ†’ service worker intercepts โ†’ backend executes โ†’ seamless API!

Note: Query, form, and command functions work perfectly. Prerender functions currently fail cross-origin due to service worker unable to reach backend during static serving.


๐Ÿ“‚ Implementation

Core Files:

  • ๐Ÿ”€ Service Worker: service-worker.ts - Intercepts and forwards remote calls
  • ๐ŸŒ CORS Handler: hooks.server.ts - Handles cross-origin requests securely
  • โšก Remote Functions: api.ts - Query, form, command, prerender implementations

Key Pattern: Use identical file paths (src/lib/all.remote.ts) in both apps to ensure matching hashes.

๐Ÿ“ Note on Remote File Structure: We use a single all.remote.ts file to re-export all remote functions for simplicity. You could have multiple remote files like lib/users.remote.ts, lib/orders.remote.ts, etc., but you'd need to ensure each file exists at the exact same path in both apps (since the hash is based on file path). Using a single all.remote.ts file simplifies maintenance and reduces the chance of path mismatches between frontend and backend.


๐ŸŽฏ Demo: All 4 Remote Function Types

๐Ÿ” Query - Dynamic Data

  • Purpose: Real-time backend data fetching
  • Example: Text converter with instant transformation
  • Features: Type-safe responses, reactive loading states

๐Ÿ“ Form - Progressive Enhancement

  • Purpose: Type-safe form submissions
  • Example: Contact form with validation
  • Features: Works without JS, built-in reactive states (.pending, .result)

โšก Command - Fire & Forget

  • Purpose: Server actions without return data
  • Example: Activity logging, analytics tracking
  • Features: Instant feedback, no response data

๐Ÿ“Š Prerender - Build-time Static

  • Purpose: Static data generated at build time
  • Example: App info, stats, configuration
  • Status: โŒ Currently unsupported cross-origin (service worker cannot reach backend during static serving)

โš ๏ธ Known Limitation: Prerender Cross-Origin

What Works: โœ… Prerender functions ARE called during backend build time
What Fails: โŒ Static app attempts runtime calls to backend but service worker cannot establish connection

Root Cause: Prerender functions bypass static cache and attempt cross-origin calls at runtime. Service worker intercepts but cannot reach backend server during static serving.

๐Ÿš€ PRs Welcome! Help implement proper prerender cache serving or fix cross-origin prerender calls!


๐ŸŒ Production Testing (No Signup Required)

๐Ÿš€ Test cross-origin functionality without signup using Surge.sh + Cloudflare Tunnel

๐Ÿ“ฆ Prerequisites

# Install tools (no accounts needed)
pnpm install -g surge
# Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/

๐ŸŽฏ Quick Deployment Test

Step 1: Configure Backend for Production
# 1. Add your test domain to CORS
# Edit: apps/backend/src/hooks.server.ts
const ALLOWED_ORIGINS = [
  // ... existing origins
  'https://your-test-domain.surge.sh', // Add your chosen domain
];

# 2. Allow Cloudflare tunnel domains
# Edit: apps/backend/vite.config.js
export default {
  server: {
    allowedHosts: [
      '.trycloudflare.com', // Allow any trycloudflare subdomain
    ]
  }
};

๐Ÿ’ก Enables curl testing: curl -i https://xxx.trycloudflare.com/_app/version.json

Step 2: Start Backend + Tunnel
# Terminal 1: Backend with public tunnel
cd apps/backend
pnpm build && node build/index.js &
cloudflared tunnel --url http://localhost:5174

# ๐Ÿ“‹ Copy the https://xxx.trycloudflare.com URL (appears in terminal)
Step 3: Deploy Frontend
# Terminal 2: Configure & deploy frontend
cd apps/frontend
echo 'PUBLIC_BACKEND_HOST="xxx.trycloudflare.com"' > .env
echo 'PUBLIC_BACKEND_INSECURE="false"' >> .env

pnpm build
surge ./build
# ๐ŸŒ Choose domain: your-test-domain.surge.sh

๐Ÿงช Expected Results

  • โœ… Query/Form/Command: Cross-origin requests working
  • โŒ Prerender: CORS failures (documented limitation)
  • โœ… Service Worker: Console logs show interception

๐Ÿ”ง Quick Validation with Curl

# Test OPTIONS preflight (should return 204 with CORS headers)
curl -i -H "Origin: https://your-test.surge.sh" \
  -X OPTIONS \
  https://xxx.trycloudflare.com/_app/remote/13eoo5e/toUpper

# Test backend health (should return 200 OK)
curl -i https://xxx.trycloudflare.com/_app/version.json

๐ŸŽฏ This validates real production cross-origin scenarios without browser testing!


๐Ÿงช Testing

Comprehensive test suite with automated validation:

# Run all tests
pnpm test:all  # Builds + API tests + E2E tests

# Individual test suites
pnpm test     # API tests (Vitest)
pnpm e2e      # Browser tests (Playwright)

๐Ÿ“Š Test Coverage:

  • โœ… API Tests (apps/tests/src/api/):

    • CORS validation for all remote function types
    • Cross-origin request/response verification
    • Backend server integration testing
  • โœ… E2E Tests (apps/tests/src/e2e/):

    • 3 browsers tested: Chrome, Firefox, Safari
    • Service worker functionality validation
    • Complete user interaction flows
    • Network request tracking and analysis
  • โœ… Infrastructure Tests:

    • Automated build and serve setup
    • Robust timeout and cleanup handling
    • Process management (no hanging tests)

๐ŸŽฏ Test Results:

  • API Tests: 3/4 pass (prerender limitation documented)
  • E2E Tests: 3/3 browsers pass
  • Service Worker: โœ… Intercepts all remote calls correctly
  • Cross-Origin: โœ… Query/Form/Command work perfectly

๐Ÿ”ฌ Scientific Validation: E2E tests discovered and corrected false assumptions about prerender caching behavior.


๐Ÿ”ง Configuration

Frontend Service Worker

File: apps/frontend/src/service-worker.ts

const productionHost = 'api.yourdomain.com'; // Your backend domain
const productionSecure = true; // true for HTTPS

Backend CORS Setup

File: apps/backend/src/hooks.server.ts

const ALLOWED_ORIGINS = [
  'http://localhost:5173', // frontend dev
  'http://localhost:3000', // frontend serve
  'https://yourdomain.com', // production frontend
  'capacitor://localhost', // Capacitor iOS
  'http://localhost', // Capacitor Android
  'tauri://localhost', // Tauri desktop
];

Mobile Apps (Tauri/Capacitor)

Additional setup for mobile apps:

  • Add mobile origins to ALLOWED_ORIGINS (see above)
  • Set productionHost to your API server domain
  • Use HTTPS in production (productionSecure: true)
  • Mobile apps cache static build but call live backend functions

๐Ÿš€ Deployment

๐Ÿ“ฆ Frontend (Static)

  • Vercel, Netlify, GitHub Pages โ†’ Deploy build/ folder
  • Any CDN or static hosting service
  • Mobile frameworks: Tauri (desktop), Capacitor (iOS/Android), Electron

๐Ÿ–ฅ๏ธ Backend (Server)

  • Railway, Fly.io, VPS โ†’ Deploy with Node.js
  • Vercel Functions, Netlify Functions โ†’ Deploy as serverless
  • Any container or traditional server

๐Ÿ“ฑ Mobile Advantage: Tauri and Capacitor require static builds since they bundle your web app into native containers. This technique lets you keep heavy backend logic on servers while maintaining elegant remote function APIs!


๐Ÿ› ๏ธ Troubleshooting

  • Service worker not working? DevTools โ†’ Application โ†’ Service Workers โ†’ Update + reload
  • CORS errors? Add your frontend origin to ALLOWED_ORIGINS in backend hook
  • JSON parsing errors? Fixed with improved service worker error handling
  • Prerender fails to load? Expected - currently unsupported cross-origin (use query functions instead)

๐ŸŽ‰ Benefits

  • โœ… Elegant API: Use SvelteKit's remote functions instead of manual fetch
  • โœ… Type Safety: Full TypeScript support across frontend/backend
  • โœ… Separate Deployments: Frontend and backend deploy independently
  • โœ… Static Hosting: CDN/GitHub Pages compatible
  • โœ… Mobile Ready: Perfect for Tauri/Capacitor apps
  • โŒ Prerender Functions: Currently unsupported cross-origin (3 out of 4 function types work)

๐Ÿ“š Technical Deep Dive

The Hash Secret: SvelteKit generates endpoint hashes based on file paths, not code content.

// SvelteKit source (simplified):
remotes.push({
  hash: hash(filePath), // Hash of file path
  file: filePath, // e.g., "src/lib/all.remote.ts"
});

Why This Works:

  • Both apps use src/lib/all.remote.ts โ†’ same hash โ†’ same endpoint
  • Frontend calls /_app/remote/13eoo5e/call (doesn't exist locally)
  • Service worker intercepts โ†’ forwards to backend.com/_app/remote/13eoo5e/call
  • Backend recognizes hash 13eoo5e โ†’ executes function โ†’ returns result

Service Worker Flow:

  1. Clone original request to preserve body streams
  2. Add X-SvelteKit-Remote header for backend detection
  3. Forward with preserved cookies, referrer, and metadata
  4. Handle POST body buffering to avoid stream consumption issues

๐Ÿ› ๏ธ Tech Stack

  • SvelteKit - Remote functions framework
  • TypeScript - Type safety across deployments
  • Service Workers - Request interception and forwarding
  • Zod - Request/response validation
  • PNPM - Efficient monorepo management

๐Ÿ“– References


Made with โค๏ธ by Robin Braemer

Building bridges between static frontends and dynamic backends

โญ Star โ€ข ๐Ÿด Fork โ€ข ๐Ÿ’ฌ Discuss

Top categories

Loading Svelte Themes