Skip to content
Starterdough
Menu

Blog Starterdough maintainers

Serve anywhere: web, PWA, desktop, mobile, your own box

One SvelteKit codebase powers the web app and PWA support; a Tauri desktop shell and mobile setup scripts extend it. How the targets fit together and what still needs work.

  • deployment
  • tauri
  • pwa
  • self-hosting

“Serve anywhere” is a promise about the same code. Starterdough’s application is one SvelteKit codebase; what changes per target is how it is built and where it is hosted. This post walks through each target and the decisions behind it (D3, D9, D10, D11, D12 and D13 in the repository’s decision log).

The website

apps/web builds for Node with adapter-node, for Cloudflare Workers with adapter-cloudflare, or as a static SPA with adapter-static. One environment variable, ADAPTER, selects the target. The app is a thin client of the API, so every route works both server-rendered and as a single-page app. There is no server-only code to strip.

The public surfaces (this site and the documentation) are Astro and fully static. They deploy to Cloudflare Workers static assets or are served by Caddy when self-hosting. Shared design tokens from the UI package keep them visually consistent with the app. The tokens are light-dark() pairs, so they follow the operating system’s color scheme with no JavaScript.

The mobile site and the PWA

Not every user wants an installer, and phones are the most common “mobile site” client. The app ships a web manifest, a theme color and installable icons, shows an “Install app” button when the browser offers it, and registers a service worker in production browsers. The service worker precaches the build and serves a prerendered offline page when a navigation has no network.

The kit includes PWA support alongside the website. A Tauri shell can use the same frontend in remote-URL mode. Native packaging is a separate setup step, and installed journeys have not been verified for the public examples.

The desktop app

apps/native is a Tauri 2 project. The shell is a small Rust program that opens a system webview on the static build of the app. There are no Tauri commands and no IPC data layer: the frontend talks to the API over HTTPS exactly as the browser does. Tauri provides a small binary on the system webview, installers, and mobile targets from the same project.

The intended shell transport uses bearer tokens instead of browser cookies. The frontend can be bundled with an installer or point at the hosted app URL. Installed authentication journeys are not yet verified: native OAuth and payment returns and secure native token storage still need work.

With no IPC there is no hot path in the shell, so Rust versus Bun is decided by packaging and the mobile story, and Tauri wins. Bun runs everything else: the dev server, the builds, the API and the tooling.

The mobile app

The kit includes Android and iOS initialization scripts for the Tauri project, wrapping the same Svelte UI. Producing installed mobile apps takes platform setup, signing and testing; no app-store release is included or demonstrated here.

Mobile UX is therefore web UX with responsive design, so the app invests in it: a responsive sidebar that becomes a sheet on small screens, and the same accessibility checks in both color modes.

Your own box

Cloudflare hosts the static surfaces and can host the SvelteKit SSR build. The API, the services behind it and Postgres need a long-lived host. That can be a managed container platform or your own VPS or home server with infra/compose.yml: Postgres, the API, the web app, Caddy and everything else in one compose file.

Caddy runs in one of two modes. Subdomains gives you app., api. and docs. under your domain with automatic HTTPS; the session cookie spans them through a cookie domain setting. Single-origin routes by path on one host with TLS terminated elsewhere. No CORS, plain same-site cookies, which is what tailnet and LAN deployments want.

Reaching it from anywhere, including your phone

Tailscale on the host closes the loop. tailscale serve exposes Caddy to the devices on your tailnet under a MagicDNS HTTPS name; tailscale funnel makes the same URL public. No open ports, no DNS to manage, certificates handled. Install the Tailscale app on the phone once to reach the web app on your own server. Mobile packaging needs the separate setup and verification described above.

Why it holds together

Each target is cheap because of one decision made earlier: the application has no backend of its own. It renders, and it calls the API. Once that is true, “where does it run” becomes a build flag and a hosting choice rather than a rewrite.