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.
๐ฏ Problem: SvelteKit remote functions only work within the same deployment, but you want static frontend + separate backend.
๐ก Solution: Service worker magic!
/_app/remote/[HASH]/call
โ redirects to backendPerfect for: Static sites โข Mobile apps (Tauri/Capacitor) โข CDN deployments โข Serverless architectures
# 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!
The Secret: SvelteKit generates remote function hashes based on file paths, not code.
src/lib/all.remote.ts
) in both apps โ identical hashesservice-worker.ts
) intercepts /_app/remote/[HASH]/call
X-SvelteKit-Remote
headerhooks.server.ts
) allows cross-origin callsThis 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.
Core Files:
service-worker.ts
- Intercepts and forwards remote callshooks.server.ts
- Handles cross-origin requests securelyapi.ts
- Query, form, command, prerender implementationsKey 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 likelib/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 singleall.remote.ts
file simplifies maintenance and reduces the chance of path mismatches between frontend and backend.
.pending
, .result
)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!
# Install tools (no accounts needed)
pnpm install -g surge
# Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
# 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
# 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)
# 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
# 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!
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)
โ
API Tests (apps/tests/src/api/
):
โ
E2E Tests (apps/tests/src/e2e/
):
โ Infrastructure Tests:
๐ฌ Scientific Validation: E2E tests discovered and corrected false assumptions about prerender caching behavior.
File: apps/frontend/src/service-worker.ts
const productionHost = 'api.yourdomain.com'; // Your backend domain
const productionSecure = true; // true for HTTPS
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
];
Additional setup for mobile apps:
ALLOWED_ORIGINS
(see above)productionHost
to your API server domainproductionSecure: true
)๐ฆ Frontend (Static)
build/
folder๐ฅ๏ธ Backend (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!
ALLOWED_ORIGINS
in backend hookThe 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:
src/lib/all.remote.ts
โ same hash โ same endpoint/_app/remote/13eoo5e/call
(doesn't exist locally)backend.com/_app/remote/13eoo5e/call
13eoo5e
โ executes function โ returns resultService Worker Flow:
X-SvelteKit-Remote
header for backend detectionMade with โค๏ธ by Robin Braemer
Building bridges between static frontends and dynamic backends
โญ Star โข ๐ด Fork โข ๐ฌ Discuss