Skip to content

Deploying

LookAlike’s interactive scoring runs in the browser, but the production deployment uses a TypeScript Cloudflare Worker. The Worker serves the application shell through Workers Static Assets, serves the national data package from a private R2 bucket, and runs Better Auth and the protected product API against D1.

First, build the application to static HTML, JavaScript, and assets:

Terminal window
cd web
pnpm install
pnpm build:cloudflare

This runs the web and Worker TypeScript checks, builds the Vite application into dist/, and creates a separate dist-cloudflare/ bundle with the data directory excluded. Ordinary Vite previews retain local data, while Wrangler can only see the small application bundle.

After building, web/dist/ contains:

  • index.html — the entry point
  • assets/ — minified JavaScript and CSS
  • data/ — the committed compatibility data files (PMTiles, binary feature packs, JSON metadata, metro definitions, store locations). Pre-computed candidate rankings were removed when scoring moved into the browser; the app ranks tracts live. See web/public/data/RELEASE.md for what this copy is and is not.

The application bundles and small assets in dist-cloudflare/ deploy through Workers Static Assets. The data/ directory deploys separately to R2.

Critical: The host must support HTTP range requests (Accept-Ranges: bytes headers). This is required to serve the PMTiles vector tile file efficiently.

PMTiles (tracts.pmtiles, about 29 MiB) is a single binary file containing all census tract boundaries as vector tiles. It is larger than Cloudflare Workers Static Assets’ 25 MiB per-file limit. The LookAlike Worker therefore reads it from R2 and returns correct 206 Partial Content responses for MapLibre’s byte-range requests.

After deployment, verify the route with:

Terminal window
curl -I -H "Range: bytes=0-99" https://your-deployed-site.com/data/tracts.pmtiles

You should see a 206 Partial Content response, not 200 OK.

Seed Wrangler’s local R2 store, then run the Worker:

Terminal window
cd web
pnpm data:seed:local
cp .dev.vars.example .dev.vars
pnpm db:migrate:local
pnpm auth:invite:local you@example.com --bootstrap
pnpm dev:cloudflare

The local Worker exposes /api/health, serves /data/* from local R2, and serves the application from dist/. It also runs the invite-only authentication flow. Local email mode keeps the magic link in Worker memory and shows a localhost-only continuation button after the sign-in request; it does not send email or print the token.

Ordinary pnpm dev is a front-end-only workflow and bypasses authentication by default. Use the Cloudflare command when testing accounts, organizations, D1, client authorization, R2 delivery, or production routing.

After signing in, /account provides passkey and session management. Organization owners and administrators use /admin for retail-client creation, invitations, member-to-client analyst/viewer grants, and recent audit activity. Always test these pages through Wrangler rather than the front-end-only Vite workflow because their data and authorization come from the Worker and D1.

Before the first deployment, create the remote national-data R2 bucket, the separate private customer-data R2 bucket, and the D1 database; replace the placeholder D1 identifier in wrangler.jsonc, apply the migrations, and configure BETTER_AUTH_SECRET and MAILGUN_API_KEY as Worker secrets. Publish and activate a validated data release from the separate data-pipeline repository, then deploy the application.

Terminal window
cd web
pnpm exec wrangler r2 bucket create lookalike-data
pnpm exec wrangler r2 bucket create lookalike-customer-data
pnpm deploy:cloudflare

The application repository deliberately has no remote data-upload command. Production releases are published by LookAlike-data-pipeline/scripts/publish-release.sh and activated separately after validation.

Remote resource creation and custom-domain attachment require the approved Cloudflare account and domain decisions. Do not run the remote commands against an unintended account.

This site is built with Astro and Starlight. It is separate from the app and serves as a reference for using and operating the explorer.

Terminal window
cd docs-site
pnpm install
pnpm build

This generates static HTML in docs-site/dist/.

The docs site is pure static HTML, deployed as its own Workers Static Assets Worker so it can stay public while the application remains invite-only. Its configuration lives in docs-site/wrangler.jsonc: Worker name lookalike-docs, assets served from ./dist, custom domain docs.lookalikeiq.com.

Terminal window
cd docs-site
pnpm deploy # builds, then runs wrangler deploy

The application is deployed at https://app.lookalikeiq.com, with https://lookalike.sean-525.workers.dev retained as a fallback. Both R2 buckets, D1 migrations, Better Auth secrets, release 2026-07-11-01, and the initial owner invitation are active. Magic-link email is delivered through Mailgun on mg.lookalikeiq.com. Live checks verified the health route, release pointer, a 100-byte PMTiles partial response, unauthenticated API rejection, and a Mailgun magic-link request. The documentation site has its deployment configuration in the repository but has not been deployed yet. Production sign-in completion, passkey registration, and visual browser review remain.

Before deploying, verify the build works and the app runs:

Terminal window
cd web
pnpm build:cloudflare
pnpm data:seed:local
pnpm exec wrangler dev

Then test with a production-like connection (e.g., in Chrome DevTools, set a slow 4G throttle) to confirm PMTiles loads without lag.

For the docs:

Terminal window
cd docs-site
pnpm build
pnpm preview # serves the docs locally at http://localhost:4173

The published national release 2026-07-11-01 totals approximately 51 MiB:

  • tracts.pmtiles — about 35 MiB (vector tiles of all census tract boundaries)
  • tracts/features.f32 — about 6.7 MiB (census features for all tracts, binary format)
  • tracts/labels.json — about 2.8 MiB (tract labels and metadata)
  • tracts/sims.u8 and tracts/has.u8 — 1.8 MB combined for the 11 built-in brands
  • Metro data — 1.9 MB combined
  • Store overlays — 0.7 MB combined
  • Other tract arrays and metadata — ~3.2 MB

The production Worker reads all of this from R2. A compatibility copy of the same release is committed at web/public/data/ for local development and the consistency tests; it is about 46 MiB because its tracts.pmtiles is an older build (see web/public/data/RELEASE.md). Ordinary Vite builds include that committed copy, and Wrangler excludes it from Workers Static Assets. There is no browser service worker; the app relies on HTTP cache headers and ETag revalidation.

If you need to update the data (e.g., new store locations, new demographic year), see Rebuilding the data.

The separate data-pipeline repository publishes immutable releases to R2. Application deployment and data publication are independent operations.