Cloudflare Pages Fullstack¶
Status: Authored
A static frontend co-deployed with Cloudflare Pages Functions, usually with same-origin /api/* routes.
Full rubric¶
Reference implementation¶
| Score evidence | Value |
|---|---|
| Repository | vibecodeqa/ref-cloudflare-pages-fullstack (published) |
| VCQA report | 92/100 |
| Verification | self-reported |
| Assessed commit | 26b59c4 |
| Assessed on | 2026-07-25 |
| CI run | success (2026-07-25) |
| Independent assessment | None published yet. The score above is the reference repo's own claim, not third-party proof. |
| Catalog entry last verified | 2026-08-08 |
Reference template map¶
| Evidence | Where to look | What it proves |
|---|---|---|
| API namespace ownership | functions/api/ |
Pages Functions own /api/*; the routes exist as files, not as router config. |
| SPA fallback that spares the API | web/public/_redirects |
UI deep links reach index.html while /api/* stays out of the fallback. |
| Authorization before route logic | functions/_middleware.ts, functions/src/auth.ts |
Unauthenticated requests to protected routes fail server-side, not in the client. |
| Typed seam contract | shared/src/index.ts, web/src/api.ts |
Both halves compile against one contract instead of two hand-kept copies. |
| Environment separation | wrangler.toml, .env.example |
Preview and production variables and bindings are declared separately; client env is declared public. |
| Single-artifact deploy | .github/workflows/deploy.yml, scripts/check-deploy-shape.mjs |
Assets and Functions ship as one Pages deployment, and the shape is asserted rather than assumed. |
| Seam smoke evidence | scripts/smoke-pages.mjs, .github/workflows/ci.yml |
A deployed URL is asserted for both the deep-link fallback and the negative-auth path. |
| Operations | docs/runbook.md |
Deploy and rollback are written down, not tribal. |
| Score evidence | docs/vcqa-report.md |
The template carries a tracked VCQA score and visible gaps. |
What this teaches¶
Choose this stack when one product wants a static frontend and a small API that must share an origin: the same domain, the same deployment, the same release. The point is not that Cloudflare Pages can host functions - it is that co-deploying two halves creates a seam that neither half owns, and almost every failure in this archetype lives in that seam.
The seam has four parts, and this standard exists to make each one explicit. Route
ownership: /api/* belongs to Functions, and an SPA router that defines a colliding
route silently steals it. Authorization placement: the client guard and the server
check look interchangeable in a demo and are not. Environment scope: VITE_* is public
by construction while bindings and secrets are per-environment, so preview reusing a
production binding is a data incident, not a config nit. Release atomicity: assets and
Functions must move as one artifact, or a deploy leaves a frontend calling an API shape
that does not exist yet.
Do not choose this stack for a standalone Worker with no static frontend, for a frontend that talks only to somebody else's API (that is React SPA), or when you need a long-running server process. Pages Functions are request-scoped.
Decision matrix¶
| Need | Better fit |
|---|---|
| Static frontend plus a same-origin API of your own | Cloudflare Pages Fullstack. |
| Static frontend talking to an API you do not own | React SPA alone. |
| An API with no static frontend attached | A Workers standard; Pages adds nothing. |
| The same app plus a D1 database | This standard plus Cloudflare D1 App for schema, migrations, and query safety. |
| Remote MCP tools for AI clients | Cloudflare Worker MCP Server; do not smuggle tool endpoints into /api/*. |
| Per-customer deployable surfaces, tenant bindings, promotion gates | Tenant-Deployed Cloudflare SaaS, which composes this standard. |
| A long-running server, background workers, or persistent connections | Not this stack. Pages Functions are request-scoped. |
Upstream references¶
Cloudflare owns Pages and Functions doctrine; VCQA owns only the seam between the two halves.
- Cloudflare Pages Functions
- Pages Functions routing
- Pages Functions middleware
- Pages Functions bindings
- Vite: env variables and modes
- TSConfig reference
- GitHub Actions secure use
- GitHub Actions deployments and environments
- OWASP Top 10
Scope¶
A static frontend co-deployed with Cloudflare Pages Functions, usually with same-origin /api/* routes.
Not in scope¶
- standalone Workers without Pages
- database-specific D1 migration rules
- generic React component doctrine
Composes¶
VCQA-owned rule surface¶
- same-origin /api seam.
- functions route shape.
- middleware/auth placement.
- bindings and deployed vars.
- SPA/functions deploy assembly.
Detection signals¶
functions/directorywrangler.tomlor Pages project config- frontend build output deployed with Functions
Combination-born guidelines¶
- SPA routes and Functions routes must not collide; reserve
/api/*for Functions. - Protected API routes need middleware/server-side enforcement, not client-only guards.
- Bindings and secrets are environment-scoped; preview must not reuse production secrets accidentally.
Rule highlights¶
- R-SEAM-1: Reserve the API namespace. Pages Functions own
/api/*; the SPA router must not define user-facing routes that shadow that namespace. - R-SEAM-2: Deep links and API routes are tested together. A deployed preview must
prove that unknown UI routes fall back to
index.htmlwhile API routes still dispatch to Functions. - R-AUTH-1: Client guards are not authorization. Protected reads and writes require middleware or per-route authorization in Functions.
- R-ENV-1: Client env vars are public.
VITE_*values may configure URLs and feature flags, but secrets must live in Cloudflare bindings or deployment secrets. - R-BIND-1: Bindings are environment-scoped. Preview, staging, and production bindings are named and reviewed separately.
- R-DEPLOY-1: The frontend and Functions deploy as one artifact. The CI pipeline must assemble static assets and Functions into the same Pages deployment.
- R-TYPE-1: API contracts cross the seam. Request/response shapes used by the SPA and Functions should be typed or schema-validated at the boundary.
- R-OBS-1: Server errors are observable. Functions should return safe client errors while preserving enough server-side context for debugging.
Limitations¶
- Route collisions are only provable against a deployed URL. Static analysis can flag a suspicious SPA route, but the authoritative answer comes from a smoke test on a real preview deployment, which a repository scan cannot perform.
- Binding isolation is judged from declared config. If preview and production bindings are configured in the Cloudflare dashboard rather than in version control, the repo cannot show the separation and the check falls back to evidence-only.
- This standard does not own your data layer. D1 schema, migrations, and query safety belong to Cloudflare D1 App; KV and R2 usage patterns are not judged here beyond binding scope.
- It does not own component-level frontend quality. That is React SPA and the cross-cutting rubrics.
- Retained smoke evidence is evidence-only by default. A repo with correct behaviour and no retained transcript scores lower on proof, not on behaviour - the severity escalates only when the missing artifact hides a seam or auth failure.
Anti-patterns¶
- Treating a React route guard as the only protection for private data.
- Putting production secrets in
VITE_*variables or checked-in config. - Letting
/api/*be handled by the SPA fallback. - Deploying frontend assets without the matching Functions version.
- Sharing production bindings with preview deployments.
Benefits¶
- Cloudflare SaaS example app.
- future VCQA fullstack dashboard patterns.
Maintenance¶
| Maintenance | Value |
|---|---|
| Latest edition | Cloudflare Pages Fullstack v1 |
| Pin reports and scans to | /standards/cloudflare-pages-fullstack/v1/ |
| Last reviewed | 2026-07 |
| Next review due | 2027-07 |
| Edition targets | cloudflarePagesFunctions latest, typescript 6 |
| Lifecycle | active |
| Errata | none |
| Composes | react-spa, cloudflare-pages-functions, typescript, web-security, github-actions |
Editions are cut on material change, not on a calendar. A review that finds the edition
still correct moves Last reviewed forward without a new edition. Metadata lives in
standards/registry.json.