FullStack Course LogoFullStack Course

Advanced React Architecture

Advanced React Architecture Prerequisites and scope Complete 066–93 and 109–115. 089–93 establish ordinary testing, performance, concurrency, and capstone boundaries. This guide goes deeper: hydration correctness, Suspen

Prerequisites and scope

Complete 066–93 and 109–115. 089–93 establish ordinary testing, performance, concurrency, and capstone boundaries. This guide goes deeper: hydration correctness, Suspense and streaming, recovery, Server Components/framework boundaries, external stores, React Compiler, and advanced profiling. Labs use the canonical task-manager where possible; framework, database, and compiler examples are optional and must be labelled.

Hydration as a protocol

SSR sends HTML, then hydrateRoot attaches behavior. The first client render must match the server's output. Time, randomness, locale, browser-only APIs, responsive branching, and changed data are common causes of mismatch. Render deterministic placeholders, move browser reads behind an Effect or event, and use suppressHydrationWarning only for a narrow intentional text difference. It does not repair structural disagreement.

Lab: render the canonical task summary on the server with a fixed task fixture, hydrate it in a browser, capture console.error, then deliberately introduce Date.now() and a locale-dependent label. Fix each mismatch and record the before/after warning and HTML. Test an interrupted navigation and a hydration failure recovery path.

Suspense, streaming, and loading architecture

Suspense coordinates a component that can suspend and shows the nearest fallback. It is not a cache, request cancellation, authorization check, or error boundary. Put boundaries around independently useful task panels, keep fallbacks close enough to preserve context, and avoid replacing already useful content with a whole-page spinner. In a streaming framework, the server can flush a shell and later reveal suspended content; define what is safe to show before data arrives.

jsx
<ErrorBoundary fallback={<RetryPanel />}>
  <Suspense fallback={<p role="status">Loading suggestions...</p>}>
    <SuggestedTasks />
  </Suspense>
</ErrorBoundary>

Lab: in an optional Suspense-capable framework, stream a task shell and suggestions separately. Delay suggestions, verify the shell is usable, then reject the resource and use the error boundary's retry/reset action. Test focus, announcements, timeout, empty data, and a boundary that remounts after recovery. Keep the canonical Vite app's existing explicit loading state if it does not use Suspense.

Error recovery

Error boundaries catch descendant render, lifecycle, and constructor failures, not event-handler exceptions, Effect errors, arbitrary async rejections, or server authorization failures. Request errors need request-level UI; render failures need a boundary. Recovery must reset the failed subtree, preserve safe surrounding navigation, log a correlation ID, and avoid exposing stack traces or secrets.

Lab: add a development-only “throw in task row” switch behind an optional example. Capture the error, render a retry action keyed to the route/task ID, and prove that the rest of the application remains usable. Separately MSW-reject a request and show why the request error does not reach the boundary automatically.

Server Components and framework boundaries

In an RSC-capable framework, Server Components can read server resources and avoid shipping their implementation to the browser. They cannot use state, Effects, event handlers, or browser APIs. Client Components can, but a 'use client' boundary increases the client graph and accepts only values allowed by the framework's serialization contract. Functions, database connections, class instances, and secrets must not cross it. Neither boundary is authorization.

jsx
// Optional framework example, not canonical Vite code.
export async function TaskScreen({ taskId }) {
  const task = await db.tasks.find(taskId);
  return <TaskActions task={task} />;
}

Lab: measure the client bundle before and after moving a read-only task summary to a Server Component. Keep the interactive form as a Client Component, validate on the server, and directly test that another account cannot read or mutate the task. Record serialized props, bundle output, and the authorization response.

External stores and subscriptions

Use useSyncExternalStore when state is owned by a store outside React, such as browser storage, a websocket connection, or a library cache. Provide a stable subscribe, a cached getSnapshot, and getServerSnapshot when rendering on a server. The snapshot must be immutable from React's perspective; returning a new object on every call can create an infinite update loop or needless renders.

jsx
import { useSyncExternalStore } from 'react';
const store = { value: 0, listeners: new Set() };
const subscribe = (listener) => { store.listeners.add(listener); return () => store.listeners.delete(listener); };
const getSnapshot = () => store.value;
export function Counter() {
  const value = useSyncExternalStore(subscribe, getSnapshot, () => 0);
  return <output>{value}</output>;
}

Lab: adapt a tiny optional store to broadcast a task-count change between two mounted views. Test unsubscribe on unmount, a stable snapshot, server fallback, and no tearing during a transition. Do not replace the canonical local state with a store without documenting the ownership problem it solves.

React Compiler

The React Compiler can automatically optimize eligible components and reduce manual memoization. It does not make impure components correct, remove the need for stable keys, fix network waterfalls, or justify ignoring measurements. Follow the repository's configured compiler and lint guidance; do not add compiler configuration to the canonical project as part of this reading alone. Keep manual memo, useMemo, and useCallback where they communicate a deliberate boundary or are required by an external API, then verify compiler behavior with a production profile.

Lab: take the 091 measured row case, run it with the project's approved compiler configuration if available, and compare profile and bundle evidence. Test changed props, context updates, and side-effect-free rendering. Explain one optimization the compiler cannot perform.

Advanced profiling lab

Record a production trace for task search, a suspended suggestion panel, and a route transition. Correlate React commit ranges with browser long tasks, style/layout, memory, network waterfalls, and bundle chunks. Change one variable at a time: state locality, boundary placement, preload, or memoization. Include device, browser, data size, build hash, and trace files in the report. A faster trace that regresses focus, stale-data handling, or error recovery is not an improvement.

Edge cases and interview questions

Investigate hydration mismatch, duplicate Strict Mode subscriptions, stale external snapshots, a rejected streamed segment, an error boundary reset loop, non-serializable RSC props, and a client bundle that accidentally contains a secret. Ask: what does Suspense guarantee; what does an error boundary miss; why is getServerSnapshot needed; how does an RSC boundary affect bundling; when does streaming hurt UX; what can the Compiler not infer; and how do you prove an optimization helped?

Official references

Reader page: /guide/advanced-react-architecture