React Roadmap
React Roadmap
Express API · Next.js full-stack alternative
The ordered build plan for the React client of a PERN stack: nine phases from a blank repo to a monitored production deployment. One phase at a time — no phase starts before the previous deliverable exists. Estimates assume one experienced developer; solo, double them.
This roadmap is the R of PERN, the browser half. Pair it with the Express roadmap — its walking skeleton only completes when this client consumes it on the shared contract. This is an SPA build: if the product is content-heavy, SEO-driven, or you simply want one deployable instead of two, stop and take the Next.js roadmap instead — that fork is cheap to take in Phase 0 and expensive in Phase 7.
The checkboxes are the work. This file is what actually moves the project forward.
Phase 0 — Spec (½–2 days)
Goal: a written definition of the app before any tooling.
- One-paragraph product statement ("For [user], this app does [job], unlike [alt]")
- Up to 10 user stories for release 1 — resist more
- Screen inventory: name + one line per screen (this becomes your route table)
- Data model draft — must mirror the API's Phase 0: same nouns, same contract
- Non-functional requirements: SEO required? offline? i18n? PWA? accessibility target (WCAG 2.2 AA is the default)
- Fork made consciously: SPA (this file) vs Next.js — one sentence of rationale recorded
- API contract source picked: shared zod package (monorepo) or the OpenAPI document exported by the API's CI
Deliverable: one page and a route table a stranger could build from.
Watch for: "we can always add SSR later" is the most expensive sentence in frontend — decide the fork now.
Phase 1 — Foundation (1–2 days)
Goal: an empty but correct project: renders, type-checks, lints, CI green.
- Node 24 LTS installed (22.13+ floor), pnpm; pinned in
.nvmrc+engines - Vite + React 19 + TypeScript
strict(Vite is the default for an SPA — Next is a different roadmap) - React Router v7: route table from Phase 0, layout routes, 404 boundary
- ESLint 9 flat config (react-hooks + jsx-a11y plugins) + Prettier + husky + lint-staged
- Path aliases (
@/) and typed env:VITE_API_BASE_URL— client env vars are public by definition - Tailwind as styling layer; design tokens as CSS variables, light/dark from day one
- Root providers: QueryClient, theme, error boundary, toaster
- CI on every PR: lint → typecheck → test → build (GitHub Actions)
Deliverable: pnpm dev renders the shell; pnpm build green in CI.
Watch for: any VITE_* value ships inside the browser bundle — there is no such thing as a secret client env var.
Phase 2 — Walking skeleton (3–5 days)
Goal: one thin slice of real value, end to end: screen → state → API → data → back.
- Typed API client: fetch wrapper, base URL from env, normalized error shape, abort on unmount
- One feature delivered completely against the live Express API (land it in the same week as the API's Phase 2 — the contract is proven only when both sides meet)
- Loading, empty, error and offline states handled — not skipped
- Shared validation schemas imported (monorepo) or generated from OpenAPI — one source of truth, no hand-copied types
- TanStack Query configured: query keys, retry policy, stale times
- Vitest + React Testing Library smoke test of the slice; CI green
Deliverable: a demoable app with one genuinely useful feature, end to end.
Watch for: the skeleton is not done until an API error renders as a human sentence, not a raw 500.
Phase 3 — Core capabilities (1–2 weeks)
Goal: the machinery every later feature needs exists once, correctly.
- UI primitives: Button, Input, Card, List, Skeleton, Toast, Modal — token-driven and accessibility-labelled
- Form pattern: react-hook-form + zod + server-error mapping (422 field errors land on their fields)
- Auth flow: access token in memory, silent refresh driven by the
httpOnlycookie; guardsanonymous → authenticated; logout clears the query cache - State rule: server state lives in TanStack Query, client/UI state in Zustand, context only for theme/locale — never a global store for fetched data
- Routing as state: filters, pagination and selection mirrored in the URL (shareable, back-button correct)
- Error boundary per route + Sentry wired
Deliverable: new features become mostly composition.
Watch for: duplicating server data into a client store creates two sources of truth — the classic React outage.
Phase 4 — Feature build-out (weeks, variable)
Goal: the remaining product features, each as a vertical slice.
- Feature per slice, folder per feature, public API via its
indexfile - Optimistic updates only where a rollback is implementable (mutations with known inverses)
- Pagination/infinite scroll with prefetch-on-intent on every list
- Realtime where needed: SSE first, WebSocket when bidirectional, reconnect with backoff
- Accessibility labels and keyboard paths written as the UI is written, not retrofitted
- Route-level code splitting; heavy editors and charts lazy-loaded
Deliverable: feature-complete against the Phase 0 spec.
Phase 5 — Quality (3–5 days + ongoing)
Goal: confidence that change does not break things.
- Unit tests on hooks, formatters and reducers (Vitest)
- Component tests on critical flows with Testing Library (user-facing queries only)
- Playwright E2E smoke: onboarding → auth → the money path, against the staging API
- Performance budget set and enforced in CI: initial JS ≤ 200 KB gzipped, Lighthouse ≥ 90, zero layout shift on the money path
- Bundle analysis on a schedule; regressions explained, not accepted
- Accessibility pass: axe clean + screen-reader walkthrough of the money path
Deliverable: green suite in CI + recorded performance numbers.
Phase 6 — Hardening (2–4 days)
Goal: safe to hand to strangers.
- XSS review: no
dangerouslySetInnerHTMLon untrusted input; sanitiser with an explicit allowlist if rich text exists - CSP with the API origin allowlisted; HTTPS + secure cookies end to end
- No secrets in the bundle — grep the build output for the strings
- AuthZ UX re-check: hidden vs enforced (the API enforces, the UI only hides)
- Sentry release tracking + source maps uploaded; dependency scan in CI
Deliverable: a passed checklist and a live error dashboard.
Phase 7 — Release (2–5 days)
Goal: serving real users from a production domain.
-
pnpm build→ staticdist/; preview deployment per PR (Vercel/Netlify/Cloudflare Pages — pick one; default to what the team already knows) - Environments: distinct API base URL + domain per env (app. / staging. / prod.), config injected at build time
- DNS + cookie plan with the API written down:
app.example.com+api.example.com,SameSite/Domaindecided in advance — the classic PERN deploy bug - Rollback rehearsed: previous build, instant (keep the last N builds addressable)
- Smoke test runs against production after every promote
Deliverable: 1.0 serving real traffic; rollback rehearsed.
Phase 8 — Operate & iterate (forever, ~1 h/week)
Goal: the app gets better instead of older.
- Weekly triage: Sentry issues, Web Vitals from RUM (INP/LCP), failed API contracts
- Dependency cadence: Renovate weekly; React/Vite majors in a branch with the codemods
- Quarterly: Lighthouse + axe re-run against the recorded budget; bundle review
- Keep the shared contract honest: schema changes ship in the same PR as the API change
Rules that keep this roadmap honest
- Walking skeleton before features. One thin end-to-end slice beats five half-built screens.
- No phase skips. Tests after Phase 5 are cheap; tests retrofitted in Phase 7 are not.
- Deliverables are binary. Each phase ends with something that exists or does not.
- Cut scope, not structure. A smaller feature set in a sound codebase ships; the reverse rarely does.
- Re-estimate at each phase boundary. The roadmap is a living document — update it as reality diverges.