# Deploy to Cloudflare (Workers + static assets)

One Worker serves all of vid2context.com:

| Path | What |
|---|---|
| `/` and `/docs/*` | the landing site (`site/`) |
| `/app/` | the web app (`packages/web`), when it builds |
| `/chat/` | the chat app (`packages/chat`), when it builds |
| `/api/v1/*` | the HTTP API (`packages/server`) |
| `/mcp` | remote MCP, once `packages/server/src/workers.ts` routes it (prompt 46) |
| `/llms.txt`, `/llms-full.txt` | the repo's LLM-readable docs |

The config is the root `wrangler.jsonc`; the Worker entry is `packages/server/src/workers.ts`; static files come from `dist/assets/`, built by `npm run build:assets`.

## Commands

```sh
npm run build:assets            # assemble dist/assets (site, apps that build, llms.txt)
npm run dev:worker              # build assets, then wrangler dev on http://localhost:8787
npm run deploy                  # build assets, then wrangler deploy (needs CLOUDFLARE_API_TOKEN)
npx wrangler deploy --dry-run --outdir dist/worker   # bundle and validate without uploading
```

`build:assets` skips an app that is missing or does not build, and says why. Add `-- --strict` to make that an error (do this for production once `packages/web` and `packages/chat` exist): `npm run build:assets -- --strict && npx wrangler deploy`.

A dry run on 2026-11-14 bundled the Worker to 120 KiB (33 KiB gzipped); the free plan allows 3 MiB compressed. No `nodejs_compat` flag is needed: the Workers registry contains only fetch-based providers.

## Credentials

`wrangler deploy` needs a Cloudflare API token with the "Edit Cloudflare Workers" template, in `CLOUDFLARE_API_TOKEN` (and `CLOUDFLARE_ACCOUNT_ID` if the token can see several accounts). Put both in `.env` or your shell; never in `wrangler.jsonc`.

## Secrets and variables

Provider keys and API keys are Worker secrets, set once per key:

```sh
npx wrangler secret put GROQ_API_KEY
npx wrangler secret put GEMINI_API_KEY
npx wrangler secret put VID2CONTEXT_API_KEYS     # comma-separated keys for /api/v1 when the API should not be open
```

Every variable in `.env.example` works the same way. `wrangler.jsonc` sets one plain variable, `VID2CONTEXT_PUBLIC=1`, which marks this deployment as the public, internet-facing instance.

## Optional bindings

All are commented out in `wrangler.jsonc`. Without them the Worker keeps the library in memory (per isolate), uses hosted and keyless rungs, and runs no background jobs. To turn one on, create the resource, uncomment its block, and uncomment the matching variable in `vars` (the storage provider reads the variable to find the binding).

| Binding | Create with | Variable | Used by |
|---|---|---|---|
| D1 `DB` | `npx wrangler d1 create vid2context` | `VID2CONTEXT_D1=DB` | the library: contexts, collections, search (providers/storage-cloudflare) |
| R2 `MEDIA` | `npx wrangler r2 bucket create vid2context-media` | `VID2CONTEXT_R2=MEDIA` | large contexts and uploads |
| KV `KV` | `npx wrangler kv namespace create vid2context` | `VID2CONTEXT_KV=KV` | KvStore: sessions, API keys, webhooks, caches |
| Workers AI `AI` | nothing to create | none | free Whisper and LLM rungs through the binding (providers/cloudflare-ai) |
| Queue `JOBS_QUEUE` | `npx wrangler queues create vid2context-jobs` | none yet | background jobs (packages/jobs); needs a `queue` handler in the Worker entry |
| Cron trigger | nothing to create | none | watch-list refresh (prompt 41); needs a `scheduled` handler in the Worker entry |

Free limits that matter (Workers Free plan, as published by Cloudflare): 100,000 requests per day, 10 ms CPU per request (I/O wait such as calling YouTube or an LLM does not count), D1 5 GB, R2 10 GB, KV 100,000 reads per day, Workers AI 10,000 neurons per day. Long speech-to-text jobs belong on Node or in a queue consumer, not in a request.

## What runs on Workers

| Works | Does not |
|---|---|
| YouTube metadata and captions (innertube, plain fetch) | yt-dlp, ffmpeg, whisper.cpp and other binaries |
| Hosted and keyless speech-to-text for uploads within each rung's size limit | Local files (there is no filesystem; the media-file source is not in the Workers registry) |
| Every LLM rung that speaks HTTP, including keyless OVH and Pollinations | Local models (Ollama, LM Studio) unless exposed over HTTPS |
| Workers AI through the `AI` binding | Long-audio splitting (needs ffmpeg) |

YouTube blocks many datacenter IPs. Cloudflare's egress IPs are shared and sometimes rate-limited: when captions fail with `blocked`, the API says so with a hint. A Cloudflare Tunnel to a machine at home (see the remote MCP guide) avoids that.

## Domain

- `vid2context.com` and `www.vid2context.com` are custom domains in `routes`; Cloudflare creates the DNS records and certificates on the first deploy. The Worker redirects `www.` to the bare domain.
- `vidtocontext.com`: add it to the same Cloudflare account, then create a redirect rule (Rules, Redirect Rules, "Redirect from root to WWW" style, custom): when hostname equals `vidtocontext.com` or `www.vidtocontext.com`, redirect to `https://vid2context.com${path}` keeping the query string, status 301. The zone needs a proxied DNS record (for example an `A` record to `192.0.2.1`, proxied) so requests reach the rule.
- `vid2context.<account>.workers.dev` stays on as a fallback URL (`workers_dev: true`).

## SPA routing for the apps

`/app/*` and `/chat/*` deep links must serve each app's `index.html`. Workers static assets cannot do this per path: `not_found_handling` is global, and `_redirects` rewrites apply even when a file exists, so `/app/* /app/index.html 200` would also rewrite the app's own JavaScript. The Worker does it instead with `fetchAssetWithSpaFallback` from `deploy/serverless/cloudflare/spa-fallback.ts`: assets first, and only on a 404 for a non-file path under an app mount, that app's index. It is verified in the Workers runtime by `deploy/serverless/test/worker.test.ts` (`WRANGLER_TEST=1`). Wiring it into `packages/server/src/workers.ts` is one line, listed for prompt 46.

## Verify a deploy

```sh
curl https://vid2context.com/api/v1/health
curl "https://vid2context.com/api/v1/transcript?url=https://youtu.be/jNQXAC9IVRw&timestamps=1"
npx wrangler tail          # live logs
```
