What a React build actually ships with in 2026
Most React agencies still ship create-react-app-style single-page apps with client-side routing, a spinner on every navigation, and a JavaScript bundle crossing 500KB gzipped. That is not what React looks like in 2026.
A real React build today is Next.js with the App Router, Server Components where data lives on the server, client components where interaction lives on the client, streaming for anything over 100ms, and a design system living in the repo - not in a node_module you cannot touch.
When React (not Astro, Svelte, or plain HTML) is the right call
- Interactive dashboards with complex client state, filters, and real-time updates.
- Auth-gated products with role-based access and user-specific views.
- Shared component libraries across a web app and a React Native mobile app.
- Admin panels where the UI is genuinely an application, not a page.
- Forms with complex validation and cross-field rules (insurance, fintech, healthcare).
When it is not
- Marketing and content sites - Astro or plain HTML + CSS wins on performance and is cheaper to maintain.
- Apps that are 95% static - no server state, no user interaction - Astro again.
- Tiny interactive widgets embedded in someone else’s page - plain JS or Preact.
Server Components in practice
The rule we use per component:
- Reads data from the database or an API? Server component.
- Holds user state or listens to the browser (inputs, scrolls, effects)? Client component.
- Renders once, then the user interacts? Server shell + client island.
- Needs a third-party library that touches window? Client component, isolated.
Bundle size drops 30 to 60% on most dashboards we touch when the split is clean. LCP improves by more than the numbers suggest, because the browser has less JS to parse before paint.
Our React capabilities
- Next.js 15 App Router with Server Components and Server Actions.
- TanStack Query for client-side server state with caching, retries, and optimistic updates.
- Zustand for cross-page client state. Redux only on projects that already use it.
- React Hook Form + Zod for forms shared between client and server schemas.
- shadcn/ui on Radix for ARIA-correct components that live in your repo.
- Tailwind with design tokens in CSS variables. Light/dark from the same tokens.
- Sentry with session replay. Every error repros in minutes, not hours.
- Playwright covering critical flows against preview URLs. CI blocks merges on regressions.
- Storybook when a component library needs to be reviewed across teams.
Case studies
- Cross-border mobile top-up - reseller dashboard in React - React web reseller dashboard sharing form logic, auth, and component patterns with the React Native consumer app. One FastAPI backend, two surfaces. Multi-gateway payments (Stripe, PayPal) and a webhook-driven ledger under the hood.
- Live auction bidding - operator console in React - React web operator console for user verification, vendor and vehicle onboarding, auction scheduling, and live event monitoring. Same FastAPI + WebSocket backend as the React Native buyer app. Server-authoritative timer; admin can schedule and close auctions without deploying code.
How we work on a React engagement
Week 1 - architecture. Routes, data boundaries, server-vs-client component split, auth strategy, deploy target. Written down, reviewed, signed off before any TSX.
Weeks 2 to 3 - the spine. Shell layout, auth, first real data fetch, design token system in place. Deployed to a preview environment by Friday of week 2.
Weeks 4 to 10 - features, one per week. Demoed on real network conditions, not localhost. Lighthouse runs on every PR. Regressions block merge.
Weeks 11 to 12 - polish, accessibility, launch. WCAG 2.2 AA keyboard pass, screen-reader pass, analytics, error boundaries, CWV dashboards.
TanStack Query, React Hook Form, Zod - the client-side stack we trust
- TanStack Query for anything fetched client-side that lives longer than a render. Caching, retries, refetch-on-focus, optimistic updates - configured once, used everywhere.
- React Hook Form + Zod for forms. The Zod schema validates the form AND the server input. Client-server drift on forms is a whole class of bugs that disappears.
- Zustand for cross-page client state when needed.
Design tokens, not Figma-to-code plugins
Every component hits CSS variables. Light and dark mode from the same tokens. When the designer changes brand colour, one variable moves and the whole app follows. No theme-provider spaghetti.
shadcn/ui gives us Radix-correct primitives wrapped in Tailwind, living in our repo. We change the styles without fighting a library.
Deploys and CI - boring by design
- GitHub Actions: type-check, unit tests, Playwright against a preview URL on every PR.
- Preview deploys per branch. Product reviews happen on real URLs, not localhost screenshots.
- Production behind a protected branch. Rollback is one click.
- Sentry wired from day one. Every error has a user session replay attached.
What we will not do
- CSS-in-JS with a runtime on new builds. Performance tax for ergonomics that Tailwind + CSS variables already give you.
- GraphQL where REST + OpenAPI does the job. We default to typed REST. GraphQL only when there is a concrete reason (federation, client-specified shapes).
- Redux when Zustand or TanStack Query covers it. Less ceremony, same outcome.
- Opinionated without switching. The point is not the stack. The point is the app shipping on time with the bugs caught.
Why teams pick our React delivery
We use Server Components where they earn their place. A static product page loads faster with RSC. A rich form-heavy editor does not. The split is per component, written down in the architecture doc, not decided by feeling on a Tuesday.
Types carry from backend to frontend: OpenAPI from FastAPI, Zod schemas shared between client forms and server validators, TypeScript strict throughout. Design tokens in CSS variables, Radix + axe-core for accessibility, senior engineers on the product, full IP transfer at launch. The point is not the stack. The point is the app shipping on time with the bugs caught.