# Cloudflare Workers Deployment Design ## Goal Convert the GEO Agent Article Optimizer into a Cloudflare Workers deployable app using the official Cloudflare bindings path for persistence and private exports. The deployed app must be a real production-shaped target, not only a static or partially working preview. ## Current State The app is a Next.js application with API routes. It currently stores data in a local SQLite file through `better-sqlite3` and writes exports under `data/exports//`. That is suitable for local MVP use, but it does not map to Cloudflare Workers because production Workers should not depend on a mutable local filesystem or native SQLite addons. ## Chosen Approach Use Next.js on Cloudflare Workers through the OpenNext Cloudflare adapter and Wrangler. Cloudflare production persistence will use: - D1 binding `DB` for relational application data. - Private R2 binding `EXPORT_BUCKET` for generated export objects. The app will keep local Node development separate from Cloudflare deployment: - `npm run dev` remains the local developer path. - Local Node tests can continue using local SQLite and filesystem exports. - Cloudflare preview/deploy scripts use OpenNext and Wrangler. - Production code paths access D1 and R2 only through bindings, not REST APIs. ## Environment Separation Local and online environments must be explicit. - Local development uses `.env.local`, local SQLite, and local export files. - Cloudflare preview uses Wrangler local bindings and Cloudflare-compatible runtime behavior. - Staging and production each get their own D1 database, R2 bucket, Worker name, and secrets. - Deployments are manual. No Git auto-deploy or automatic hot update workflow is configured. Recommended script split: - `dev`: local Next.js development. - `build`: regular Next.js build for local validation. - `build:worker`: OpenNext Cloudflare build. - `preview:worker`: Wrangler local preview. - `deploy:worker:staging`: explicit staging deploy. - `deploy:worker:production`: explicit production deploy. ## API Access Control All `/api/*` routes are protected by default with an API access key. - The expected secret is `API_ACCESS_KEY`. - Clients send the key in `x-api-key`. - Local development and tests may use an explicit test/dev bypass or a local `.env.local` value. - Production secrets are set through Wrangler secrets, not committed files. - Missing or incorrect keys return `401`. The first implementation can use a single shared key because the current app has no account model. The design should leave room for a future account/session layer without mixing that concern into the Cloudflare migration. ## Data Storage ### Repository Boundary API routes should not call `better-sqlite3` directly. Introduce a repository boundary for article jobs, brand templates, fact cards, optimized articles, and QA reports. Implementations: - Local Node implementation: uses existing SQLite behavior for tests and local development. - Cloudflare implementation: uses D1 binding `DB` and async queries. The route layer chooses the implementation based on runtime context. Online Workers must fail clearly if required bindings are missing instead of silently falling back to local storage. ### D1 Schema Management D1 schema is managed only through migrations. - Runtime code must not automatically create, rebuild, drop, or clear tables. - Initial schema lives in a D1 migration file. - Future schema changes use compatibility-first migrations, such as `ALTER TABLE ... ADD COLUMN` when possible. - If a change requires reshaping existing data, add a dedicated data migration script or migration step with clear staging validation instructions. - Before production deployment, migrations are applied and verified against local/staging D1 first, then applied to production. This replaces the current local `initializeSchema` runtime behavior for the Cloudflare path. Local test setup may still initialize local SQLite test databases because those are disposable test fixtures, not deployed production state. ## Export Storage Split export handling into rendering and storage. Rendering remains runtime-agnostic: - Markdown renderer. - QA JSON renderer. - DOCX renderer. Storage becomes environment-specific: - Local storage writes to `data/exports//`. - Cloudflare storage writes objects to private R2 through `EXPORT_BUCKET`. R2 object keys: - `exports//optimized.md` - `exports//optimized.docx` - `exports//qa_report.json` The R2 bucket is private. Do not configure a public bucket URL for app exports. Downloads go through the authenticated Worker API, which reads the object from R2 and returns it as an attachment. ## Request Flow 1. `POST /api/jobs` checks `x-api-key`, normalizes input, writes the job to the active repository, and returns the candidate fact card. 2. `POST /api/jobs/:jobId/confirm-fact-card` checks the key, loads the job, writes the brand template and confirmed fact card, and updates job status. 3. `POST /api/jobs/:jobId/optimize` checks the key, loads job and fact card, runs the optimization workflow, saves article and QA report, and writes export objects if QA does not fail. 4. `GET /api/jobs/:jobId/exports/:fileName` checks the key and returns the object from local storage or private R2. ## Wrangler And Bindings Use `wrangler.jsonc`. Required production bindings: - D1 database binding `DB`. - R2 bucket binding `EXPORT_BUCKET`. Required secrets: - `API_ACCESS_KEY`. - LLM provider secrets such as `DEEPSEEK_API_KEY` or `OPENAI_API_KEY`. Recommended non-secret vars: - `LLM_PROVIDER`. - `DEEPSEEK_BASE_URL`. - `DEEPSEEK_MODEL`. - `DEEPSEEK_THINKING`. - `OPENAI_MODEL`. - `APP_RUNTIME=cloudflare` for clarity if needed by runtime selection. Enable `nodejs_compat` because the app and dependencies use Node-compatible packages, including document generation and the OpenAI client. ## Error Handling - Missing API key: `401`. - Missing Cloudflare binding in Worker runtime: `500` with a concise operational error, without exposing secret values. - Missing job or export object: `404`. - Fact card not confirmed before optimization: `409`. - D1/R2 operation failures: structured server errors and enough logging context to identify the operation and job id. ## Testing And Verification Tests should cover both local behavior and Cloudflare-specific behavior. Local tests: - Existing repository tests continue to cover SQLite behavior. - Export renderer tests continue to cover Markdown, JSON, and DOCX output. - API tests cover access-key enforcement. Cloudflare-focused tests: - D1 repository tests using local D1/Miniflare-compatible bindings or Wrangler. - R2 export store tests for write, read, missing object, and content type. - Runtime selection tests proving Cloudflare path uses bindings and does not fall back to local files. Verification commands: - `npm test` - `npm run build` - `npm run build:worker` - `npx wrangler deploy --dry-run` Migration verification before production: 1. Apply D1 migrations locally. 2. Apply and verify against staging D1. 3. Deploy staging Worker and smoke test protected API calls and exports. 4. Apply migrations to production D1. 5. Deploy production Worker explicitly. ## Out Of Scope - User accounts and per-user permissions. - Public R2 asset hosting. - Git-triggered auto-deploy. - Replacing the existing LLM provider abstraction. - Batch queues or background workflow orchestration.