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 httpOnly cookie; guards anonymous → 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 index file
  • 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 dangerouslySetInnerHTML on 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 → static dist/; 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/Domain decided 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

  1. Walking skeleton before features. One thin end-to-end slice beats five half-built screens.
  2. No phase skips. Tests after Phase 5 are cheap; tests retrofitted in Phase 7 are not.
  3. Deliverables are binary. Each phase ends with something that exists or does not.
  4. Cut scope, not structure. A smaller feature set in a sound codebase ships; the reverse rarely does.
  5. Re-estimate at each phase boundary. The roadmap is a living document — update it as reality diverges.

Express API · Next.js full-stack alternative

Built with LogoFlowershow