{"id":31154747,"url":"https://github.com/robinbraemer/sveltekit-static-to-remote","last_synced_at":"2026-05-03T22:32:00.625Z","repository":{"id":313907073,"uuid":"1053376264","full_name":"robinbraemer/sveltekit-static-to-remote","owner":"robinbraemer","description":"📱🌐 Demo: Call SvelteKit remote functions from a static frontend to a separate backend deployment. Perfect for CDNs, mobile apps (Tauri/Capacitor), and serverless architectures.","archived":false,"fork":false,"pushed_at":"2025-09-11T10:12:19.000Z","size":132,"stargazers_count":44,"open_issues_count":2,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-18T07:57:51.191Z","etag":null,"topics":["capacitor","cdn","cross-deployment","mobile-apps","monorepo","pnpm","remote-functions","serverless","service-worker","static-site","sveltekit","tauri","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/robinbraemer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-09-09T11:12:35.000Z","updated_at":"2025-10-15T07:43:24.000Z","dependencies_parsed_at":"2025-09-09T14:22:38.618Z","dependency_job_id":"b491692b-fa9f-427e-8555-d173bfb54125","html_url":"https://github.com/robinbraemer/sveltekit-static-to-remote","commit_stats":null,"previous_names":["robinbraemer/sveltekit-static-to-remote"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/robinbraemer/sveltekit-static-to-remote","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robinbraemer%2Fsveltekit-static-to-remote","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robinbraemer%2Fsveltekit-static-to-remote/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robinbraemer%2Fsveltekit-static-to-remote/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robinbraemer%2Fsveltekit-static-to-remote/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/robinbraemer","download_url":"https://codeload.github.com/robinbraemer/sveltekit-static-to-remote/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robinbraemer%2Fsveltekit-static-to-remote/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32587816,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T22:12:39.696Z","status":"ssl_error","status_checked_at":"2026-05-03T22:09:10.534Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["capacitor","cdn","cross-deployment","mobile-apps","monorepo","pnpm","remote-functions","serverless","service-worker","static-site","sveltekit","tauri","typescript"],"created_at":"2025-09-18T19:35:50.192Z","updated_at":"2026-05-03T22:32:00.609Z","avatar_url":"https://github.com/robinbraemer.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🚀 SvelteKit Static-to-Remote\n\n\u003cdiv align=\"center\"\u003e\n\n**Call SvelteKit remote functions from a static frontend to a separate backend deployment**\n\n[![Demo](https://img.shields.io/badge/🎯-Live%20Demo-blue)](https://github.com/robinbraemer/sveltekit-static-to-remote)\n[![SvelteKit](https://img.shields.io/badge/SvelteKit-FF3E00?logo=svelte\u0026logoColor=white)](https://svelte.dev/docs/kit/remote-functions)\n[![Mobile](https://img.shields.io/badge/📱-Mobile%20Ready-green)](#mobile-apps)\n[![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?logo=typescript\u0026logoColor=white)](#tech-stack)\n\n\u003c/div\u003e\n\n\nhttps://github.com/user-attachments/assets/dae1146a-9391-4ab9-911e-b951b2fe7942\n\nIn 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.\n\n---\n\n## ⚡ TLDR\n\n**🎯 Problem**: SvelteKit remote functions only work within the same deployment, but you want static frontend + separate backend.\n\n**💡 Solution**: Service worker magic!\n\n1. **Same file paths** in both apps → **identical endpoint hashes**\n2. **Service worker** intercepts `/_app/remote/[HASH]/call` → redirects to backend\n3. **Backend CORS** handles cross-origin requests securely\n4. **Result**: Seamless remote function calls across deployments! ✨\n\n**Perfect for**: Static sites • Mobile apps (Tauri/Capacitor) • CDN deployments • Serverless architectures\n\n\u003cdiv align=\"center\"\u003e\n\u003cimg width=\"602\" height=\"431\" alt=\"image\" src=\"https://github.com/user-attachments/assets/5f1e545f-453f-4c5b-a2e8-8f54dd4a49cb\" /\u003e\n\u003c/div\u003e\n\n---\n\n## 🚀 Quick Start\n\n```bash\n# Clone and install\ngit clone https://github.com/robinbraemer/sveltekit-static-to-remote.git\ncd sveltekit-static-to-remote \u0026\u0026 pnpm install\n\n# Start backend (terminal 1)\ncd apps/backend \u0026\u0026 pnpm dev  # http://localhost:5174\n\n# Build and serve frontend (terminal 2)\ncd apps/frontend \u0026\u0026 pnpm build \u0026\u0026 pnpx serve build  # http://localhost:3000\n```\n\n**🧪 Test**: Open \u003chttp://localhost:3000\u003e → try the form, buttons, text converter → functions execute on backend!\n\n---\n\n## 🔧 How It Works\n\n**The Secret**: SvelteKit generates remote function hashes based on **file paths**, not code.\n\n1. **Same file paths** (`src/lib/all.remote.ts`) in both apps → **identical hashes**\n2. **Service worker** ([`service-worker.ts`](apps/frontend/src/service-worker.ts)) intercepts `/_app/remote/[HASH]/call`\n3. **Redirects** to backend server with custom `X-SvelteKit-Remote` header\n4. **Backend CORS** ([`hooks.server.ts`](apps/backend/src/hooks.server.ts)) allows cross-origin calls\n5. **Backend** recognizes hash → executes function → returns result\n\n**This means**: Static app calls non-existent endpoints → service worker intercepts → backend executes → seamless API!\n\n\u003e **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.\n\n---\n\n## 📂 Implementation\n\n**Core Files:**\n\n- 🔀 **Service Worker**: [`service-worker.ts`](apps/frontend/src/service-worker.ts) - Intercepts and forwards remote calls\n- 🌐 **CORS Handler**: [`hooks.server.ts`](apps/backend/src/hooks.server.ts) - Handles cross-origin requests securely\n- ⚡ **Remote Functions**: [`api.ts`](apps/backend/src/lib/server/api.ts) - Query, form, command, prerender implementations\n\n**Key Pattern**: Use **identical file paths** (`src/lib/all.remote.ts`) in both apps to ensure matching hashes.\n\n\u003e **📝 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.\n\n---\n\n## 🎯 Demo: All 4 Remote Function Types\n\n### 🔍 **Query** - Dynamic Data\n\n- **Purpose**: Real-time backend data fetching\n- **Example**: Text converter with instant transformation\n- **Features**: Type-safe responses, reactive loading states\n\n### 📝 **Form** - Progressive Enhancement\n\n- **Purpose**: Type-safe form submissions\n- **Example**: Contact form with validation\n- **Features**: Works without JS, built-in reactive states (`.pending`, `.result`)\n\n### ⚡ **Command** - Fire \u0026 Forget\n\n- **Purpose**: Server actions without return data\n- **Example**: Activity logging, analytics tracking\n- **Features**: Instant feedback, no response data\n\n### 📊 **Prerender** - Build-time Static\n\n- **Purpose**: Static data generated at build time\n- **Example**: App info, stats, configuration\n- **Status**: ❌ **Currently unsupported cross-origin** (service worker cannot reach backend during static serving)\n\n---\n\n## ⚠️ Known Limitation: Prerender Cross-Origin\n\n**What Works**: ✅ Prerender functions ARE called during backend build time  \n**What Fails**: ❌ Static app attempts runtime calls to backend but service worker cannot establish connection\n\n**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.\n\n**🚀 PRs Welcome!** Help implement proper prerender cache serving or fix cross-origin prerender calls!\n\n---\n\n## 🌐 Production Testing (No Signup Required)\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🚀 Test cross-origin functionality without signup using Surge.sh + Cloudflare Tunnel\u003c/strong\u003e\u003c/summary\u003e\n\n### **📦 Prerequisites**\n\n```bash\n# Install tools (no accounts needed)\npnpm install -g surge\n# Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/\n```\n\n### **🎯 Quick Deployment Test**\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eStep 1: Configure Backend for Production\u003c/strong\u003e\u003c/summary\u003e\n\n```bash\n# 1. Add your test domain to CORS\n# Edit: apps/backend/src/hooks.server.ts\nconst ALLOWED_ORIGINS = [\n  // ... existing origins\n  'https://your-test-domain.surge.sh', // Add your chosen domain\n];\n\n# 2. Allow Cloudflare tunnel domains\n# Edit: apps/backend/vite.config.js\nexport default {\n  server: {\n    allowedHosts: [\n      '.trycloudflare.com', // Allow any trycloudflare subdomain\n    ]\n  }\n};\n```\n\n**💡 Enables curl testing**: `curl -i https://xxx.trycloudflare.com/_app/version.json`\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eStep 2: Start Backend + Tunnel\u003c/strong\u003e\u003c/summary\u003e\n\n```bash\n# Terminal 1: Backend with public tunnel\ncd apps/backend\npnpm build \u0026\u0026 node build/index.js \u0026\ncloudflared tunnel --url http://localhost:5174\n\n# 📋 Copy the https://xxx.trycloudflare.com URL (appears in terminal)\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eStep 3: Deploy Frontend\u003c/strong\u003e\u003c/summary\u003e\n\n```bash\n# Terminal 2: Configure \u0026 deploy frontend\ncd apps/frontend\necho 'PUBLIC_BACKEND_HOST=\"xxx.trycloudflare.com\"' \u003e .env\necho 'PUBLIC_BACKEND_INSECURE=\"false\"' \u003e\u003e .env\n\npnpm build\nsurge ./build\n# 🌐 Choose domain: your-test-domain.surge.sh\n```\n\n\u003c/details\u003e\n\n### **🧪 Expected Results**\n\n- **✅ Query/Form/Command**: Cross-origin requests working\n- **❌ Prerender**: CORS failures (documented limitation)\n- **✅ Service Worker**: Console logs show interception\n\n### **🔧 Quick Validation with Curl**\n\n```bash\n# Test OPTIONS preflight (should return 204 with CORS headers)\ncurl -i -H \"Origin: https://your-test.surge.sh\" \\\n  -X OPTIONS \\\n  https://xxx.trycloudflare.com/_app/remote/13eoo5e/toUpper\n\n# Test backend health (should return 200 OK)\ncurl -i https://xxx.trycloudflare.com/_app/version.json\n```\n\n**🎯 This validates real production cross-origin scenarios without browser testing!**\n\n\u003c/details\u003e\n\n---\n\n## 🧪 Testing\n\n**Comprehensive test suite with automated validation:**\n\n```bash\n# Run all tests\npnpm test:all  # Builds + API tests + E2E tests\n\n# Individual test suites\npnpm test     # API tests (Vitest)\npnpm e2e      # Browser tests (Playwright)\n```\n\n### 📊 **Test Coverage:**\n\n- **✅ API Tests** (`apps/tests/src/api/`):\n\n  - CORS validation for all remote function types\n  - Cross-origin request/response verification\n  - Backend server integration testing\n\n- **✅ E2E Tests** (`apps/tests/src/e2e/`):\n\n  - **3 browsers tested**: Chrome, Firefox, Safari\n  - Service worker functionality validation\n  - Complete user interaction flows\n  - Network request tracking and analysis\n\n- **✅ Infrastructure Tests**:\n  - Automated build and serve setup\n  - Robust timeout and cleanup handling\n  - Process management (no hanging tests)\n\n### 🎯 **Test Results:**\n\n- **API Tests**: 3/4 pass (prerender limitation documented)\n- **E2E Tests**: 3/3 browsers pass\n- **Service Worker**: ✅ Intercepts all remote calls correctly\n- **Cross-Origin**: ✅ Query/Form/Command work perfectly\n\n**🔬 Scientific Validation**: E2E tests discovered and corrected false assumptions about prerender caching behavior.\n\n---\n\n## 🔧 Configuration\n\n### Frontend Service Worker\n\n**File**: [`apps/frontend/src/service-worker.ts`](apps/frontend/src/service-worker.ts)\n\n```typescript\nconst productionHost = 'api.yourdomain.com'; // Your backend domain\nconst productionSecure = true; // true for HTTPS\n```\n\n### Backend CORS Setup\n\n**File**: [`apps/backend/src/hooks.server.ts`](apps/backend/src/hooks.server.ts)\n\n```typescript\nconst ALLOWED_ORIGINS = [\n  'http://localhost:5173', // frontend dev\n  'http://localhost:3000', // frontend serve\n  'https://yourdomain.com', // production frontend\n  'capacitor://localhost', // Capacitor iOS\n  'http://localhost', // Capacitor Android\n  'tauri://localhost', // Tauri desktop\n];\n```\n\n### Mobile Apps (Tauri/Capacitor)\n\n**Additional setup for mobile apps:**\n\n- Add mobile origins to `ALLOWED_ORIGINS` (see above)\n- Set `productionHost` to your API server domain\n- Use HTTPS in production (`productionSecure: true`)\n- Mobile apps cache static build but call live backend functions\n\n---\n\n## 🚀 Deployment\n\n**📦 Frontend (Static)**\n\n- Vercel, Netlify, GitHub Pages → Deploy `build/` folder\n- Any CDN or static hosting service\n- Mobile frameworks: Tauri (desktop), Capacitor (iOS/Android), Electron\n\n**🖥️ Backend (Server)**\n\n- Railway, Fly.io, VPS → Deploy with Node.js\n- Vercel Functions, Netlify Functions → Deploy as serverless\n- Any container or traditional server\n\n**📱 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!\n\n---\n\n## 🛠️ Troubleshooting\n\n- **Service worker not working?** DevTools → Application → Service Workers → Update + reload\n- **CORS errors?** Add your frontend origin to `ALLOWED_ORIGINS` in backend hook\n- **JSON parsing errors?** Fixed with improved service worker error handling\n- **Prerender fails to load?** Expected - currently unsupported cross-origin (use query functions instead)\n\n---\n\n## 🎉 Benefits\n\n- ✅ **Elegant API**: Use SvelteKit's remote functions instead of manual fetch\n- ✅ **Type Safety**: Full TypeScript support across frontend/backend\n- ✅ **Separate Deployments**: Frontend and backend deploy independently\n- ✅ **Static Hosting**: CDN/GitHub Pages compatible\n- ✅ **Mobile Ready**: Perfect for Tauri/Capacitor apps\n- ❌ **Prerender Functions**: Currently unsupported cross-origin (3 out of 4 function types work)\n\n---\n\n## 📚 Technical Deep Dive\n\n**The Hash Secret**: SvelteKit generates endpoint hashes based on **file paths**, not code content.\n\n```javascript\n// SvelteKit source (simplified):\nremotes.push({\n  hash: hash(filePath), // Hash of file path\n  file: filePath, // e.g., \"src/lib/all.remote.ts\"\n});\n```\n\n**Why This Works**:\n\n- Both apps use `src/lib/all.remote.ts` → same hash → same endpoint\n- Frontend calls `/_app/remote/13eoo5e/call` (doesn't exist locally)\n- Service worker intercepts → forwards to `backend.com/_app/remote/13eoo5e/call`\n- Backend recognizes hash `13eoo5e` → executes function → returns result\n\n**Service Worker Flow**:\n\n1. Clone original request to preserve body streams\n2. Add `X-SvelteKit-Remote` header for backend detection\n3. Forward with preserved cookies, referrer, and metadata\n4. Handle POST body buffering to avoid stream consumption issues\n\n---\n\n## 🛠️ Tech Stack\n\n- **SvelteKit** - Remote functions framework\n- **TypeScript** - Type safety across deployments\n- **Service Workers** - Request interception and forwarding\n- **Zod** - Request/response validation\n- **PNPM** - Efficient monorepo management\n\n---\n\n## 📖 References\n\n- [SvelteKit Remote Functions](https://svelte.dev/docs/kit/remote-functions)\n- [Service Workers Documentation](https://svelte.dev/docs/kit/service-workers)\n- [Remote Functions Hashing Source](https://github.com/sveltejs/kit/blob/main/packages/kit/src/core/sync/create_manifest_data/index.js)\n- [Hash Function Implementation](https://github.com/sveltejs/kit/blob/main/packages/kit/src/utils/hash.js)\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**Made with ❤️ by [Robin Braemer](https://github.com/robinbraemer)**\n\n_Building bridges between static frontends and dynamic backends_\n\n[⭐ Star](https://github.com/robinbraemer/sveltekit-static-to-remote) • [🍴 Fork](https://github.com/robinbraemer/sveltekit-static-to-remote/fork) • [💬 Discuss](https://github.com/robinbraemer/sveltekit-static-to-remote/discussions)\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobinbraemer%2Fsveltekit-static-to-remote","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobinbraemer%2Fsveltekit-static-to-remote","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobinbraemer%2Fsveltekit-static-to-remote/lists"}