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 built on Next.js 16 App Router, Suspense streaming, and the server-client boundary that modern React expects.
Modern React is no longer one thing. In 2026, every component is one of three shapes: Server Component, Client Component, or Server Action.
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 afford and bundle sizes they cannot explain.
strict: true, no any). Three test layers: Vitest + React Testing Library + Playwright.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 you need framework-agnostic routing and nested error boundaries. Vite SPA is only for pure client-side dashboards: no SEO, no initial-load performance needs, and no server-side rendering.
| 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 this 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, and cache invalidation all move server-side. What remains is genuinely client-only: UI state, derived values, and cross-component ephemeral state. The matrix below shows what we reach for 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 |
TypeScript strict on day one and a three-layer test pyramid (unit, component, E2E) keep the bundle clean and regression surface small as the codebase scales past 50K lines.
We set per-template Core Web Vitals targets and bundle-size budgets, then verify against web.dev/vitals field data at every release.
Lab scores in CI are not enough. We test on real mid-tier mobile hardware to catch performance regressions early.
| 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 |
Two industry archetypes from real SaaS and content-platform engagements. One is a Next.js 16 build from scratch. The other is a Pages Router to App Router migration with zero traffic drop.
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.
The questions React teams ask most often before booking an architecture audit. When to use Vite SPA. RSC versus SSR. Migration scope. State management. TypeScript strictness. Testing. Exit ownership.
No for new projects. CRA has been deprecated since 2023. 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 for pure interactive dashboards with no SEO, no initial-load performance needs, and no server-side rendering. In practice, that covers under 15 percent of projects.
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 those 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 cost $150-210 per hour at boutique agencies. Indian mid-market senior engineers cost $95-150 per hour.
A Next.js 16 App Router build runs 10-18 weeks. A migration from SPA or Pages Router to App Router runs 12-20 weeks. A 2-week architecture audit is refundable against any future engagement. Retainer engagements start at 20 hours per week of named engineer time.
Get a scoped quote in 48 hours.
No. A full codebase rewrite is the wrong default.
Next.js App Router allows incremental adoption: new routes on App Router, legacy routes on Pages Router side by side. We migrate route by route, starting with highest-traffic content pages for the biggest bundle-size wins. Interactive dashboards stay on the existing router until the migration naturally reaches them.
Plan for 4-8 months of incremental migration with per-route testing.
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: UI state (modals, drawers, tabs), derived values, and cross-component ephemeral state. useState and useContext are the right default for that.
If you need more, use Zustand for small stores, Jotai for atomic fine-grained reactivity, or TanStack Query for client-side data fetching. We avoid Redux in new builds because its conceptual weight does not match modern React.
strict true on day one. noUncheckedIndexedAccess true on day two. exactOptionalPropertyTypes true when the codebase can 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.
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.
Published · Last updated .
Pinch, scroll or double-tap to zoom · drag to pan
Share your name and email so we can follow up.
Please enter your name and a valid email.