Data + structure, zero JS.
Render on the server, stream HTML to the client, fetch data inline. Default for content, layouts, and anything read-only. The single biggest bundle-size win since tree-shaking.
A Server-Components-first React practice for SaaS products, content platforms, and client portals. Next.js 16 App Router, Suspense streaming, and the sharp server-client boundary modern React actually runs on.
Modern React is no longer one thing. In 2026 every component is one of three shapes: a Server Component that renders on the server and ships zero JS, a Client Component that hydrates in the browser for interactivity, or a Server Action that runs on the server but is called from the client. Knowing which shape each component is doing is the central architectural discipline. Codebases that treat every component the same way end up with hydration taxes they cannot pay and bundle sizes they cannot explain.
Render on the server, stream HTML to the client, fetch data inline. Default for content, layouts, and anything read-only. The single biggest bundle-size win since tree-shaking.
Marked with use client. Hydrate in the browser; handle state, effects, events. The surface area of client components is your bundle size.
Async functions marked use server. Run on server, called from client. Replace REST mutation endpoints. Type-safe end-to-end, no manual API layer.
Three React frameworks cover almost every modern project. Next.js 16 App Router is our default because it is where React Server Components live as a first-class concept. Remix wins when framework-agnostic routing and nested error boundaries are the architectural requirement. Vite SPA is acceptable only for pure client-side dashboards with no SEO and no initial-load performance constraints.
| Vite SPA | Next.js 16 App Router | Remix | |
|---|---|---|---|
| rendering | client only | RSC + SSR + ISR + static | SSR + streaming + optional RSC |
| data fetching | client-side via useEffect / SWR / TanStack | Server Components fetch inline | loaders (server-side) + actions |
| bundle size | largest (all code ships) | smallest (RSC ships zero JS) | smaller than SPA, larger than RSC |
| SEO + CWV | weak; hydration tax | best available for React | strong |
| best for | internal dashboards, auth-gated | 80% of modern builds | form-heavy platforms, nested routing |
The hardest thing about modern React is knowing where the server stops and the client begins. The animation below draws the boundary on a typical component tree: everything above the amber line renders on the server and streams as HTML; everything below hydrates on the client.
Server components fetch data directly inline. No useEffect. No loading state passed through props. No SWR wrapper. Data fetching lives in the component that renders it.
Client components are islands inside server pages: the comment input, the cart button, the modal trigger. The rest stays server-side. Bundle stays small.
Wrap slow parts of the page in Suspense. Fast parts render first, slow parts stream in as data arrives. Users see meaningful content in hundreds of milliseconds, not seconds.
Forms, updates, deletes via Server Actions. No REST endpoint. No tRPC router. Type-safe from form submit to database. The write path most React apps have been missing.
RSC plus Server Actions eliminate most of what client state was used for. Remote data, form submission state, cache invalidation all move server-side. What remains is genuinely client-only: UI state, derived values, cross-component ephemeral state. The decision matrix below is what we use on new builds.
| what you need | what we reach for | why |
|---|---|---|
| simple UI state | useState | no library, no overhead |
| shared UI state in a subtree | useContext | colocated, explicit |
| cross-component client store | Zustand | small, typed, no boilerplate |
| atomic fine-grained reactivity | Jotai | when component re-renders must be surgical |
| remote data (client-fetched only) | TanStack Query | cache, refetch, invalidate done right |
| form state | React Hook Form + Zod | validated, typed, performant |
| template | LCP | INP | CLS | JS bundle (gzip) |
|---|---|---|---|---|
| marketing page | < 1.6s | < 150ms | < 0.05 | < 40 KB |
| content / article | < 1.8s | < 150ms | < 0.05 | < 60 KB |
| authenticated dashboard | < 2.2s | < 180ms | < 0.08 | < 140 KB |
| form / checkout | < 2.0s | < 180ms | < 0.08 | < 100 KB |
Vertical SaaS with authenticated dashboard, public marketing, Stripe billing. Server Components throughout, client islands for editor and chart interactivity. Ship to Vercel edge, Postgres on Neon. 14 weeks.
Large content platform on Next.js 12 Pages Router. Migrated route by route to App Router with Server Components. 18-week migration, no traffic drop during transition. 62 percent bundle size reduction on content pages.
2 weeks. Boundary + bundle + state review. Refundable. Contact for quote.
10-18 weeks. Next.js 16 from scratch. Contact for quote.
12-20 weeks. SPA / Pages Router → App Router. Contact for quote.
20+ hrs/week named engineers. Month to month. Contact for quote.
No for new projects. CRA has been deprecated since 2023 and Vite SPA is a good development tool but the wrong production default. For a new React project in 2026, start with Next.js 16 App Router or Remix if you need framework-agnostic routing. Vite + React Router is acceptable only when the app is a pure interactive dashboard with no SEO surface, no initial-load performance requirements, and no server-side rendering needs. In practice, that is under 15 percent of briefs.
SSR (Server-Side Rendering) renders the full React tree on the server, ships HTML plus the full JavaScript bundle, and hydrates everything on the client. Server Components render on the server and ship zero JavaScript for the server-rendered parts; only Client Components ship JS. The result is dramatically smaller bundles, faster Time to Interactive, and a natural way to fetch data close to where it's rendered. SSR was about when to render; Server Components are about what to ship.
US and UK senior React engineers are $150 to $210 per hour at boutique agencies; Indian mid-market senior engineers are $95 to $150 per hour. A Next.js 16 App Router build runs 10 to 18 weeks depending on scope. A migration from SPA or Pages Router to App Router runs 12 to 20 weeks. A 2-week architecture audit on an existing codebase is refundable against any engagement that follows. Retainer engagements start at 20 hours per week of named engineer time. Scoped quote in 48 hours.
No, and you probably should not. Next.js App Router allows incremental adoption: new routes on App Router, legacy routes on Pages Router side by side. We typically migrate route by route, starting with the highest-traffic content pages (biggest bundle-size win from Server Components) and leaving interactive dashboards on the existing router until the migration naturally reaches them. A full codebase rewrite is the wrong default; the right default is a planned 4 to 8 month incremental migration with a per-route test.
By default none. React Server Components plus Server Actions eliminate most of what client state was used for: remote data, form state, cache invalidation. What remains is genuinely client-only state: UI state (modals, drawers, tabs), derived values, and cross-component ephemeral state. For that, useState and useContext are the right default. If you need more, Zustand for small stores, Jotai for atomic fine-grained reactivity, TanStack Query for any data still fetched client-side. We avoid Redux in new builds; its conceptual weight does not match the modern React runtime.
strict true on day one, noUncheckedIndexedAccess true on day two, exactOptionalPropertyTypes true when the codebase is clean enough to absorb it. No any. No unknown cast without runtime validation. Zod or Valibot on every API boundary. We generate TypeScript types from the database schema (Prisma, Drizzle, or Kysely) and from tRPC or Server Actions so the frontend always knows what the backend returns without manual type definitions. Strict TypeScript on React pays back its setup cost in the first month of maintenance.
Three layers. Vitest for unit tests on pure functions, utility modules, and component logic. React Testing Library for component behavior tests, focused on what the user sees and interacts with, not implementation details. Playwright for end-to-end tests on critical user journeys (signup, checkout, primary conversion flows). MSW for mocking HTTP at the service-worker level so tests are isolated. Coverage target is 70 percent overall, 90 percent on critical paths. We do not chase 100 percent coverage; it is an anti-pattern.
You do. Private GitHub repository under your organization from day one. Deployment on your Vercel, Netlify, Cloudflare Pages, or AWS account. Domain on your registrar. Third-party services (Sentry, Clerk, Stripe) under your billing. DH engineers get named access, removable any time. On exit we transfer the repository, document the deployment pipeline, record a Loom walkthrough of the architecture, and leave a 30-day support tail. No lock-in through code, config, or vendor choice.
Two weeks. Boundary review, bundle analysis, state management, upgrade path. Refundable against any engagement that follows. Scoped quote in 48 hours.