Skip to main content

Deployment

getbased is deployed on Vercel. The browser app is shipped as static files, and Vercel also runs small same-origin API routes from api/ 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):
{
  "buildCommand": "node scripts/fetch-catalog.mjs",
  "outputDirectory": ".",
  "routes": [
    {
      "src": "/(.*)",
      "headers": { "...CSP and security headers..." },
      "continue": true
    },
    { "src": "^/guide/(.*)", "status": 301, "headers": { "Location": "/docs/guide/$1" } },
    { "src": "^/docs/guide/getting-started(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/quickstart" } },
    { "src": "^/docs/guide/charts(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/guides/biomarker-charts" } },
    { "src": "^/docs/guide/folder-backup(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/guides/backup" } },
    { "src": "^/docs/guide/json-export-import(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/guides/export-import" } },
    { "src": "^/docs/guide/ai-providers(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/ai-providers" } },
    { "src": "^/docs/guide/([^.]+?)(?:\\.html)?/?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/guides/$1" } },
    { "src": "^/docs(?:/.*)?$", "status": 301, "headers": { "Location": "https://docs.getbased.health/" } },
    { "src": "^/app/?$", "dest": "/index.html" }
  ]
}
RouteDestination
/index.html — the application (served by Vercel filesystem default)
/api/shareapi/share.js — encrypted profile share envelope storage
/api/proxyapi/proxy.js — same-origin helper for wearable runtime config, CAMS/light data, and allowlisted external API calls
/api/commitapi/commit.js — deployed commit/version helper used by diagnostics and smoke checks
/guide/*301 redirect to the equivalent old /docs/guide/* compatibility path
/docs301 redirect to https://docs.getbased.health/
/docs/guide/getting-started301 redirect to https://docs.getbased.health/quickstart
/docs/guide/charts301 redirect to https://docs.getbased.health/guides/biomarker-charts
/docs/guide/folder-backup301 redirect to https://docs.getbased.health/guides/backup
/docs/guide/json-export-import301 redirect to https://docs.getbased.health/guides/export-import
/docs/guide/ai-providers301 redirect to https://docs.getbased.health/ai-providers
/docs/guide/*301 redirect to the equivalent Mintlify guide path
/appindex.html — app route used when the landing-page project owns /
Everything elseServed as-is from the filesystem (JS, CSS, images, manifest)
User documentation is not built from this repository anymore. It lives in the separate Mintlify docs project at docs.getbased.health; this app keeps only compatibility redirects for old app.getbased.health/docs/* links.

API routes

Vercel functions live in api/. 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.
MethodPurpose
POST /api/shareStore 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/shareCORS preflight for allowed same-origin/app origins
Production requires a Vercel Blob write token configured as a sensitive environment variable. The route uses a private Blob store namespace, caps payload size, rejects shares longer than 30 days, rejects weak PBKDF2 iteration counts, rate-limits anonymous share creation, and returns 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.
Do not add a new proxy mode without documenting the target host, the caller allowlist, what secrets it can see, and which tests cover the branch.

/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:
SubdomainVercel projectRepo
getbased.healthget-based-siteelkimek/get-based-site
app.getbased.healthget-basedelkimek/get-based
DNS points the root, app, and www hostnames at the Vercel projects. Keep provider-specific DNS account details out of public docs. The landing page is self-contained (all CSS/JS inline) and depends only on three icon files. CTA links point to 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:
PathDestination
/Landing page from ../get-based-site/index.html
/appApp from index.html
/api/shareIn-memory encrypted profile share endpoint
/api/proxylocal proxy/runtime-config mirror with LAN/same-origin safety gates
/api/commitlocal commit/version helper
/api/deploy-cataloglocal git/Vercel deploy-catalog helper for operator workflows
/docs/*301 redirect to docs.getbased.health
Without the sibling repo, / 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:
default-src 'self'
script-src  'self' 'unsafe-inline' 'wasm-unsafe-eval' blob:
            https://umami-iota-olive.vercel.app
            https://cdn.jsdelivr.net
style-src   'self' 'unsafe-inline'
font-src    'self'
connect-src 'self' https: wss:
            http://localhost:*
            http://127.0.0.1:*
            ws://localhost:*
            ws://*.onion
            http://*.onion
img-src     'self' data: blob:
worker-src  'self' blob:
manifest-src 'self'
frame-src   'none'
object-src  'none'
base-uri    'self'
'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:
const CACHE_NAME = 'labcharts-v55';
When to bump the version: Any time you change an app file — JS, CSS, HTML, manifest, images. Incrementing the version busts the cache for existing users, who will download fresh files on next visit. The service worker uses three caching strategies:
Resource typeStrategy
AI API calls (OpenRouter, Routstr, PPQ, Venice, Local AI)Bypassreturn 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
The API bypass is critical for streaming. If the service worker intercepts a streaming SSE response, the IPC pipe between the SW and the page buffers the chunks, breaking the streaming experience. The bypass (returning without calling 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:
{
  "name": "getbased",
  "short_name": "getbased",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#0f1117",
  "theme_color": "#0f1117",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/icon.svg",     "sizes": "any",     "type": "image/svg+xml" }
  ]
}

Vendor dependencies

Chart.js, pdf.js, and Google Fonts are bundled locally in vendor/. To update:
  1. Edit the version pins at the top of update-vendor.sh
  2. Run ./update-vendor.sh
  3. Bump version.js to bust the SW cache
  4. Commit the updated vendor/ directory