Files
GEOAgentArticleOptimizer/docs/superpowers/specs/2026-06-16-cloudflare-workers-deployment-design.md
T

7.4 KiB

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/<jobId>/. 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/<jobId>/.
  • Cloudflare storage writes objects to private R2 through EXPORT_BUCKET.

R2 object keys:

  • exports/<jobId>/optimized.md
  • exports/<jobId>/optimized.docx
  • exports/<jobId>/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.