Prompt and context
The page contains navigation, article content, recommendations, comments, and personalized actions. The article should become visible quickly; a slow module must not block the first bytes or take down the whole page when one service fails. Design streaming SSR with Suspense and explain how the server sends the shell, how boundaries complete, how failures degrade, and how the client recovers after scripts load.
React documents that streaming can send the shell and fallbacks first, then replace them as boundaries complete. The interview tests whether you can turn that mechanism into controlled timeout, error, cache, and monitoring behavior.
What the interviewer evaluates
Cover the stable shell versus asynchronous boundaries, request deduplication, boundary timeouts, server errors, client retries, cache variants, cancellation, accessibility, and Core Web Vitals. State which content must be synchronous and which can be delayed.
Clarifying questions to ask
- Is the first-screen target TTFB, LCP, or time to interaction, and what is each budget?
- What availability and privacy level applies to article, recommendations, comments, and personalization?
- Is the page cached at a CDN, and which user, region, or experiment variants exist?
- On a slow-module failure, should the UI show empty state, stale data, or retry?
- Which core paths must work if client JavaScript fails?
A 30-second answer
“Send a shell that does not depend on slow data, then split recommendations, comments, and personalization into Suspense boundaries with explicit budgets. Each boundary has a server timeout, observable errors, and an acceptable fallback, so a failure stays local. The client owns bounded retries and interaction after hydration. Cache only safe public fragments, and monitor TTFB, LCP, INP, errors, and boundary latency.”
Step-by-step deep dive
Step 1: Partition the shell and boundaries
Complete routing, title, article structure, and primary semantics synchronously. Put recommendations, comments, and personalization in separate boundaries. A boundary should represent a user-understandable area, not an arbitrary collection of remote dependencies.
shell: navigation + heading + article outline
boundary A: recommendations, budget 300 ms
boundary B: comments, budget 500 ms
boundary C: personalized actions, private and uncachedKeep heading, dimensions, and semantics in each fallback to avoid layout shift. Do not fragment the page so far that users cannot understand what is loading.
Step 2: Build streaming and cancellation
Use the framework’s streaming server-rendering API so the shell enters the response first and boundaries follow as data resolves. Attach a request deadline; after timeout, cancel the remote call and emit an acceptable fallback. The client should not wait for a server task that has already been cancelled.
Record boundary start, completion, timeout, and error events. When the client disconnects, cancel unfinished fetches so abandoned pages do not consume database or recommendation capacity.
Step 3: Handle server and client errors
An error inside a server boundary should resolve to that boundary’s fallback while preserving the shell and completed siblings. Attach a stable boundary identifier and request trace ID for operators, while user-facing copy only says that the section is temporarily unavailable.
After client code loads, allow bounded, backoff-based retries for eligible boundaries. Retries need an attempt limit, idempotency conditions, and cancellation. If client scripts fail, article text, links, and core forms should remain usable.
Step 4: Define cache boundaries
Cache public article content and non-user modules with keys for route, language, region, and content version. Personalized boundaries must not enter a public HTML cache; experiments must be explicit in the key or isolated at the edge.
A cache hit must not hide stale source data. Record generation time, expiry, and version for each fragment, and use a consistent freshness policy. Be cautious about caching the final concatenated stream; safe fragments are usually the better boundary.
Step 5: Preserve accessibility and layout stability
Keep the same semantic headings, landmarks, and dimension constraints in fallback and final content. Replacement must not move keyboard focus to an invisible node. Dynamic regions need an appropriate status announcement without repeatedly reading the whole page.
Reserve image and media dimensions and keep skeleton geometry stable; monitor CLS. Keep the article’s key content inside an early visible boundary, and do not put the LCP element behind an uncontrolled long-tail request.
Step 6: Add release and observability gates
Pre-release tests cover slow dependencies, 500 responses, disconnects, client-script failure, cache pollution, and cancellation. In production, measure TTFB, LCP, INP, boundary p50/p95, timeout rate, fallback rate, and retry success by route, boundary, and dependency.
During a canary, observe the shell and critical boundaries before opening high-risk personalization. If errors or tail latency cross a threshold, revert the boundary composition or static fallback instead of widening traffic.
A strong sample answer
I would define first-screen budgets and availability levels, then split the article shell, recommendations, comments, and personalization into independent Suspense boundaries. Each boundary gets a timeout, fallback, cancellation path, and trace ID; failures stay local. Public fragments use safe cache dimensions, while personalization never enters a shared cache. I would test disconnects and script failure, and monitor LCP, INP, CLS, boundary p95, and fallback rate.
Common mistakes
- Wrapping the entire page in one boundary → the slowest dependency blocks everything → split by user-understandable region.
- Giving a fallback no fixed dimensions → replacement creates CLS → reserve space and preserve semantics.
- Putting personalized HTML in a public cache → user data can leak → isolate private fragments and include safe key dimensions.
- Retrying forever after a server timeout → dependency pressure is amplified → use budgets, backoff, limits, and cancellation.
- Testing only success → disconnects or script failure make the page unusable → test errors, cancellation, and no-script degradation.
Follow-up questions and responses
Follow-up 1: Are more boundaries always better?
No. Boundaries should map to independent user regions and failure domains. Excessively fine boundaries add fallback noise, monitoring cost, and cache variants; coarse boundaries enlarge the blocking domain.
Follow-up 2: How do you keep one error from ending the stream?
Keep recovery inside the boundary’s server and client paths, preserve the already-sent shell, and provide a stable fallback for each boundary. Test failures after part of the response has already been sent.
Follow-up 3: Which metrics prove the design works?
Track TTFB, LCP, INP, CLS, boundary p95, timeout rate, fallback rate, and retry success together. Average response time alone hides tail latency and local failures.
Follow-up 4: How do you prevent experiment or user-data cache leaks?
Include user, experiment, region, language, and content-version safety dimensions in the key. Fragments whose safety cannot be proven stay out of public caches, and cross-user replay tests verify isolation.