Deployment
getbased is deployed on Vercel. The browser app is shipped as static files, and Vercel also runs small same-origin API routes fromapi/ for features that need hosted infrastructure. Vercel serves the app files directly, executes those API routes, and injects security headers.
Vercel configuration
vercel.json uses a deploy-time catalog fetch plus the legacy routes array (not rewrites or headers):
| Route | Destination |
|---|---|
/ | index.html — the application (served by Vercel filesystem default) |
/api/share | api/share.js — encrypted profile share envelope storage |
/api/proxy | api/proxy.js — same-origin helper for wearable runtime config, CAMS/light data, and allowlisted external API calls |
/api/commit | api/commit.js — deployed commit/version helper used by diagnostics and smoke checks |
/guide/* | 301 redirect to the equivalent old /docs/guide/* compatibility path |
/docs | 301 redirect to https://docs.getbased.health/ |
/docs/guide/getting-started | 301 redirect to https://docs.getbased.health/quickstart |
/docs/guide/charts | 301 redirect to https://docs.getbased.health/guides/biomarker-charts |
/docs/guide/folder-backup | 301 redirect to https://docs.getbased.health/guides/backup |
/docs/guide/json-export-import | 301 redirect to https://docs.getbased.health/guides/export-import |
/docs/guide/ai-providers | 301 redirect to https://docs.getbased.health/ai-providers |
/docs/guide/* | 301 redirect to the equivalent Mintlify guide path |
/app | index.html — app route used when the landing-page project owns / |
| Everything else | Served as-is from the filesystem (JS, CSS, images, manifest) |
docs.getbased.health; this app keeps only compatibility redirects for old app.getbased.health/docs/* links.
API routes
Vercel functions live inapi/. They must stay same-origin, minimal, and avoid handling plaintext health data unless a feature explicitly requires it.
/api/share
api/share.js stores password-protected profile share links. The browser builds a single-profile export, encrypts it locally with AES-GCM using a key derived from the user-provided password, and uploads only the encrypted envelope. The password is never sent to the route and is not embedded in the link.
| Method | Purpose |
|---|---|
POST /api/share | Store a new encrypted envelope and management-token hash |
GET /api/share?id=... | Return the encrypted envelope for a recipient to decrypt locally |
DELETE /api/share?id=... | Delete the hosted envelope when the creating browser supplies the management token |
OPTIONS /api/share | CORS preflight for allowed same-origin/app origins |
no-store JSON responses. The route never has the password or plaintext profile JSON.
Local development mirrors the endpoint in dev-server.js with an in-memory store so the modal and deep-link flow can be smoke-tested without Blob credentials. That local store is process-local and disappears when the dev server restarts.
/api/proxy
api/proxy.js is the controlled same-origin proxy boundary. It exists for browser flows that cannot safely call a third-party endpoint directly from app.getbased.health.
Current responsibilities:
- wearable runtime configuration and OAuth helper calls where client IDs/secrets must not be hardcoded into docs;
- CAMS/light-data relay paths used by Sun & Light calculations;
- Custom API proxying with public-host and allowlist checks so it cannot become a general SSRF tunnel;
- CORS and method handling for the app’s own allowed origins.
/api/commit
api/commit.js returns the deployed app identity used by smoke checks, diagnostics, and “which build am I looking at?” debugging. Keep it cheap, no-store, and free of private deployment-provider details.
Domain layout
The app and landing page are deployed as two separate Vercel projects on the same domain:| Subdomain | Vercel project | Repo |
|---|---|---|
getbased.health | get-based-site | elkimek/get-based-site |
app.getbased.health | get-based | elkimek/get-based |
https://app.getbased.health. A small inline script rewrites these to /app on localhost for local development.
Local dev server
node dev-server.js mirrors the production layout. If the site repo is cloned as a sibling (../get-based-site), the server routes:
| Path | Destination |
|---|---|
/ | Landing page from ../get-based-site/index.html |
/app | App from index.html |
/api/share | In-memory encrypted profile share endpoint |
/api/proxy | local proxy/runtime-config mirror with LAN/same-origin safety gates |
/api/commit | local commit/version helper |
/api/deploy-catalog | local git/Vercel deploy-catalog helper for operator workflows |
/docs/* | 301 redirect to docs.getbased.health |
/ serves the app directly. Override the site path with SITE_DIR=/path/to/site node dev-server.js.
CSP headers
The Content-Security-Policy allows what the current app needs:'unsafe-inline' is required for scripts because index.html has inline bootstrapping and legacy window-exported action hooks. 'wasm-unsafe-eval' and blob: are needed for in-browser model/vector/worker paths such as local Knowledge Base and private transports.
Most JS libraries are bundled locally in vendor/, but the CSP currently permits https://cdn.jsdelivr.net for explicitly allowed browser-runtime dependencies. Run ./update-vendor.sh when bumping vendored assets, and update vercel.json whenever a provider or runtime dependency needs a new host.
localhost:* and 127.0.0.1:* in connect-src allow local AI servers (Ollama, LM Studio, Jan, etc.) running on the same machine. LAN IPs (e.g. 192.168.x.x) are not supported from the hosted HTTPS app due to browser mixed-content blocking — this is a browser security fundamental, not a CSP limitation.
If a new AI provider, wearable provider, private transport, worker runtime, or relay host is added, review connect-src, script-src, service-worker bypass rules, and api/proxy.js together.
vercel.json also sets Onion-Location for Tor discovery and Link service descriptors for /.well-known/mcp.json and /.well-known/agent-skills/index.json.
Service worker
The service worker (service-worker.js) manages PWA caching. The cache name includes a version number:
| Resource type | Strategy |
|---|---|
| AI API calls (OpenRouter, Routstr, PPQ, Venice, Local AI) | Bypass — return without event.respondWith. Streaming ReadableStreams must go directly to the page without SW IPC buffering |
| App shell (HTML, CSS, JS, vendor libs, fonts, images) | Stale-while-revalidate — serve cached, update in background |
event.respondWith) routes requests directly to the network. Local/private hosts are bypassed only when they are cross-origin, so same-origin app-shell requests can still be cached. Normal localhost development unregisters the SW to avoid stale module caches; use /app?dev-sw=1 for an explicit local offline smoke test.
PWA manifest
manifest.json makes the app installable as a native app on desktop and mobile:
Vendor dependencies
Chart.js, pdf.js, and Google Fonts are bundled locally invendor/. To update:
- Edit the version pins at the top of
update-vendor.sh - Run
./update-vendor.sh - Bump
version.jsto bust the SW cache - Commit the updated
vendor/directory