React SPA¶
Status: Authored
Client-rendered React applications built as static files, with no server render step and no server of their own.
Full rubric¶
Reference implementation¶
vibecodeqa/ref-react-spa is the forkable template for this stack. It shows the expected repo shape, CI gates, static build path, Playwright smoke coverage, and tracked VCQA report without tying the standard to any product. It complements, rather than replaces, the official React and Vite starter paths.
| Score evidence | Value |
|---|---|
| Repository | vibecodeqa/ref-react-spa (published) |
| VCQA report | A 94/100 |
| Verification | self-reported |
| Assessed commit | 54ac82b |
| Assessed on | 2026-07-23 |
| CI run | success (2026-07-23) |
| 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¶
The reference repo is meant to be inspected, not treated as a black box. These files show the stack contract in code:
| Evidence | Where to look | What it proves |
|---|---|---|
| Static build path | vite.config.ts, index.html |
The app builds to static assets with a relative base and needs no server of its own. |
| Client entry and routing | src/main.tsx, src/App.tsx |
Routing is client-side only; there is no server render step. |
| Failure containment | src/ErrorBoundary.tsx |
A render error degrades one boundary instead of blanking the page. |
| Public client configuration | .env.example |
Every VITE_* value is declared as public configuration, so nothing secret can arrive by habit. |
| Behaviour tests | src/App.test.tsx, src/ErrorBoundary.test.tsx |
Components are tested through user-visible behaviour, not internals. |
| Deep-link smoke coverage | tests/e2e/home.spec.ts, playwright.config.ts |
The built app is exercised as a browser sees it, including the routes a fallback must serve. |
| Quality gates | .github/workflows/ci.yml |
Lint, typecheck, tests, and build run before changes are trusted. |
| Score evidence | docs/vcqa-report.md |
The template carries a tracked VCQA score and visible gaps. |
What this teaches¶
Choose this stack when the whole application can be delivered as a folder of static files that any CDN, object store, or Pages host can serve, and every request that needs a secret is somebody else's server. It fits dashboards over an existing API, internal tools, admin consoles, marketing apps with client-side interactivity, and the frontend half of a product whose backend is a separate deployable.
The lesson the stack teaches is a boundary, not a framework. A static SPA ships its
entire source to the browser: there is no server-side step in which a secret can be
kept, a permission checked, or a page rendered. So authorization, secrets, and data
ownership all live somewhere else, and the app's job is to be honest about that. Most
serious defects in this archetype come from pretending otherwise - a route guard treated
as access control, an API key in VITE_*, a deep link that 404s because nobody configured
the fallback.
Do not choose this stack if you need server rendering, per-request personalization before first paint, an API of your own, or SEO that depends on server-rendered HTML. Those are different archetypes with different rubrics.
Decision matrix¶
| Need | Better fit |
|---|---|
| Static client-rendered app talking to somebody else's API | React SPA. |
The same app plus a same-origin /api/* of your own |
Cloudflare Pages Fullstack, which composes this standard. |
| Server-rendered HTML, streaming, or per-request personalization | React SSR or a framework-mode app; not this standard. |
| SEO that depends on server-rendered markup | SSR or prerendering; a client-rendered shell is not enough. |
| A desktop shell around the same UI | Tauri React Desktop (planned) plus this standard for the web layer. |
| Secrets or privileged data the browser must not see | A server surface of some kind. No configuration of a static SPA makes this safe. |
Aliases¶
react-spa-static
Scope¶
Client-rendered React applications built as static files, with no server render step and no server of their own.
Not in scope¶
- React SSR or framework-mode apps
- Cloudflare Pages Functions APIs
- native desktop shells
Upstream references¶
This standard cites upstream doctrine rather than restating it. React, Router, and Vite own component, routing, and build guidance; VCQA owns only the static-boundary glue.
- React documentation
- Build a React app from scratch
- Rules of Hooks
- React Router documentation
- Vite guide
- Vite: deploying a static site
- Vite: env variables and modes
- TSConfig reference
- Vitest guide
- Testing Library guiding principles
- WCAG 2.2
- OWASP Top 10
Composes¶
VCQA-owned rule surface¶
- static SPA boundary.
- client env vars are non-secret only.
- SPA routing fallback.
- build output and asset hygiene.
Detection signals¶
reactandreact-dom- Vite or another static app build tool
- no server/framework runtime dependency
- no
functions/server slice
Combination-born guidelines¶
VITE_*variables are public client configuration, never secrets.- Deep links require SPA fallback or hash routing because no server route renders them.
- Client auth guards improve UX but do not authorize access to private data.
Rule highlights¶
The full rubric is the source of truth for judgment. These are the rules that decide whether a repo is this archetype at all:
- R-SETUP-1: It builds to static assets.
vite buildproduces a folder that needs no running server of its own to answer a request. - R-ROUTE-1: Routing is client-side only. There is no server render step.
- R-BUILD-2: Assets load from a relative base. The app must work under a path prefix or behind a proxy, not only at the domain root.
- R-BUILD-3: Deep links do not 404. The host serves
index.htmlfor unknown paths, or the app uses hash routing. - R-DATA-1: No private secret reaches the bundle. API keys, tokens, and database credentials are not present in client source or build output.
Limitations¶
- This standard cannot judge your backend. It stops at the network boundary. Whatever serves the data the SPA reads is judged by its own standard, and a perfect React SPA score says nothing about that surface.
- It cannot prove authorization. Client-side guards are UX. VCQA can detect a missing server boundary only when the repo also contains the server.
- Hosting is judged from repo evidence. SPA fallback and cache headers are usually configured in the host, so a repo that configures them outside version control can look compliant to a scanner and still break deep links in production.
- Build-time secret scanning is source-shaped. A secret injected by a CI variable at build time will not appear in the repository, only in the deployed bundle.
- The rubric targets a Vite/React/TypeScript baseline. Another static bundler is still in scope, but the detection signals and edition targets are written for that baseline.
Anti-patterns¶
- Treating a React route guard as access control for private data.
- Putting an API key, token, or database credential in a
VITE_*variable. - Shipping absolute asset paths that break under a path prefix or proxy.
- Deploying without SPA fallback and discovering it via 404s on shared deep links.
- Adding a small server "just for this one endpoint" without moving to a fullstack standard, so half the app is judged by the wrong rubric.
- Testing implementation details instead of user-visible behaviour, then rewriting every test on the next refactor.
Benefits¶
- vibecodeqa/app web dashboard.
- Cloudflare SaaS example frontend.
Maintenance¶
| Maintenance | Value |
|---|---|
| Latest edition | React SPA v1 |
| Pin reports and scans to | /standards/react-spa/v1/ |
| Last reviewed | 2026-07 |
| Next review due | 2027-07 |
| Edition targets | react 19, vite 8, tailwind 4, typescript 6, node >=20.19; prefer 22.12+ or 24 LTS |
| Lifecycle | active |
| Errata | none |
| Composes | react, react-router, vite, typescript, web-accessibility, web-security, vitest, playwright |
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.