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
DBfor relational application data. - Private R2 binding
EXPORT_BUCKETfor generated export objects.
The app will keep local Node development separate from Cloudflare deployment:
npm run devremains 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.localvalue. - 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
DBand 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 COLUMNwhen 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.mdexports/<jobId>/optimized.docxexports/<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
POST /api/jobschecksx-api-key, normalizes input, writes the job to the active repository, and returns the candidate fact card.POST /api/jobs/:jobId/confirm-fact-cardchecks the key, loads the job, writes the brand template and confirmed fact card, and updates job status.POST /api/jobs/:jobId/optimizechecks 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.GET /api/jobs/:jobId/exports/:fileNamechecks 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_KEYorOPENAI_API_KEY.
Recommended non-secret vars:
LLM_PROVIDER.DEEPSEEK_BASE_URL.DEEPSEEK_MODEL.DEEPSEEK_THINKING.OPENAI_MODEL.APP_RUNTIME=cloudflarefor 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:
500with 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 testnpm run buildnpm run build:workernpx wrangler deploy --dry-run
Migration verification before production:
- Apply D1 migrations locally.
- Apply and verify against staging D1.
- Deploy staging Worker and smoke test protected API calls and exports.
- Apply migrations to production D1.
- 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.