# Deploying

The chosen Cloudflare deployment and local production-runtime workflow.

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.

## Deploying the app

### Build the Cloudflare bundle

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

```
cd webpnpm installpnpm 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.

### Output structure

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.

### Hosting requirement: HTTP range requests

**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:

```
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`.

### Test the production runtime locally

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

```
cd webpnpm data:seed:localcp .dev.vars.example .dev.varspnpm db:migrate:localpnpm auth:invite:local you@example.com --bootstrappnpm 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.

### Deploy to Cloudflare

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.

```
cd webpnpm exec wrangler r2 bucket create lookalike-datapnpm exec wrangler r2 bucket create lookalike-customer-datapnpm 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.

## Deploying the documentation site

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.

### Build the docs

```
cd docs-sitepnpm installpnpm build
```

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

### Deploy the docs

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`.

```
cd docs-sitepnpm deploy      # builds, then runs wrangler deploy
```

The app and the docs are deliberately on separate subdomains of `lookalikeiq.com`: `app.lookalikeiq.com` for the application and `docs.lookalikeiq.com` for this site. They are separate Workers with separate deployments, so publishing documentation never touches the application or its data bindings.

## Current deployment status

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.

## Testing before deployment

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

```
cd webpnpm build:cloudflarepnpm data:seed:localpnpm 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:

```
cd docs-sitepnpm buildpnpm preview          # serves the docs locally at http://localhost:4173
```

## Data asset size

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.

Each user’s first session may retrieve much of the data package. Immutable release paths and Cloudflare cache integration are planned before customer onboarding so repeat sessions do not repeatedly read unchanged objects from R2.

## Rebuilding the data pipeline

If you need to update the data (e.g., new store locations, new demographic year), see [Rebuilding the data](https://docs.lookalikeiq.com/operate/rebuild-data/).

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

Canonical URL: https://docs.lookalikeiq.com/operate/deploy/
