Challenge: Build a Streaming Article Viewer with Suspense
Build a streaming-first React Article Viewer with a sidebar list, instant preview, and a Suspense-streamed full body. Wrap the body in an Error Boundary with Retry, and add a lazy Help panel that preloads on hover/focus.
We'll cover the following...
Problem statement
You’re building a small Article Viewer UI for a larger product. The experience should feel streaming-first: users should see a stable shell and useful structure immediately, while slower regions progressively appear as data and code become available. Use Suspense boundaries to keep retries localized, design layout-stable skeletons for fallbacks, and prefer intent-based preloading (hover/focus/navigation) for optional panels so the UI feels progressive rather than blocked.
Success criteria
At no point should the shell disappear. The overall layout (sidebar container + main content container + help container) must remain mounted and visually stable during all loading/retry states.
Selecting an article should never cause the sidebar or preview to regress while slower regions load. Once the article list is visible, selecting an article must not re-suspend/blank the sidebar. Once the preview (title + short summary) is visible, it must remain visible while the full body streams in or retries.
The app has three core regions:
Left Sidebar (
Articles)Loads a list of articles.
Hovering over an article should preload its content (data preloading).
Main Content (
ArticleViewer)Selecting an article shows:
A fast
preview(title + short summary).A slower
full bodysection that streams in later under a nestedSuspenseboundary.If fetching fails, show a recovery UX with a
Retrybutton.
Right Panel (
Help)A small help panel loaded via
React.lazy().Should preload on intent (for example, when the user hovers Open Help).
Technical requirements: Implement the following features step by step:
Implement wrapper functions that call
fakeServer()using the correct request keys. These wrappers act like a real API layer, so the rest of the app can request data without knowing how the fake server works.fetchArticles()CallsfakeServer("articles")to fetch the article list.fetchArticlePreview(id)CallsfakeServer(`\preview:${id}`)to fetch title + summary for a specific article.fetchArticleBody(id)CallsfakeServer(\body:${id}`)` to fetch the full article body for a specific article.
Implement the ArticleList sidebar UI that:
Loads the article list using React Suspense (
use(getArticles())).Renders the list of articles as selectable buttons.
Highlights the currently selected article (
selectedId).Calls
onSelect(id)when a user clicks an item to change the selected article.Improves perceived performance by preloading article data (
preloadArticle(id)) on hover/focus/pointer intent.Adds basic accessibility using
role="listbox"andaria-selected.
ErrorBoundary+Retryfor the body:If the body fetch fails, show a recovery UI with a
Retrybutton.Retrymust invalidatebody:${id}in the cache and reset the boundary’s error state.
Lazy-load the Help panel:
Load the Help UI via
React.lazy().Preload the chunk on hover/focus (trigger
import()early).
Create a set of reusable skeleton/fallback components for
Suspenseboundaries, so the UI stays responsive while data loads. These components should:Be lightweight, deterministic, and render instantly.
Match the layout regions they represent (shell, sidebar, viewer, body) to minimize layout shift.
Provide clear user feedback (
“Booting…”, “Loading articles…”,etc.) while async data is still pending.
Project structure
Below is the hierarchy of the project files and folders: