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.

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 body section that streams in later under a nested Suspense boundary.

      • If fetching fails, show a recovery UX with a Retry button.

  • 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() Calls fakeServer("articles") to fetch the article list.

    • fetchArticlePreview(id) Calls fakeServer(`\preview:${id}`) to fetch title + summary for a specific article.

    • fetchArticleBody(id) Calls fakeServer(\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" and aria-selected.

  • ErrorBoundary + Retry for the body:

    • If the body fetch fails, show a recovery UI with a Retry button.

    • Retry must invalidate body:${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 Suspense boundaries, 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: