Skip to content

Add RSC migration guide series (#2459)#2460

Open
AbanoubGhadban wants to merge 9 commits intomasterfrom
2459-add-rsc-migration-guide
Open

Add RSC migration guide series (#2459)#2460
AbanoubGhadban wants to merge 9 commits intomasterfrom
2459-add-rsc-migration-guide

Conversation

@AbanoubGhadban
Copy link
Collaborator

@AbanoubGhadban AbanoubGhadban commented Feb 21, 2026

Summary

  • Adds a comprehensive 5-part migration guide series covering how to restructure an existing React app for React Server Components
  • Focuses on the React-side challenges (component tree restructuring, context handling, data fetching patterns) rather than framework configuration
  • Based on extensive research from real-world migrations (Mux 50K lines, WorkOS, Frigade), official React docs, and community best practices

Articles created:

  1. Component Tree Restructuring Patterns - Top-down migration strategy, donut pattern, pushing state to leaves, async components with Suspense
  2. Context, Providers, and State Management - Provider wrapper pattern, React.cache(), Redux/Zustand migration, auth/theme/i18n patterns
  3. Data Fetching Migration - useEffect replacement, TanStack Query/SWR migration, waterfall avoidance, streaming with use() hook
  4. Third-Party Library Compatibility - Thin wrapper pattern, CSS-in-JS alternatives, UI/form/animation/chart library compatibility matrix, barrel file problem
  5. Troubleshooting and Common Pitfalls - Serialization boundaries, import chain contamination, hydration debugging, error catalog, testing strategies, TypeScript issues

Closes #2459

Test plan

  • Verify all markdown files render correctly on GitHub
  • Verify all internal cross-references between articles work
  • Review code examples for accuracy
  • Check that existing docs links are not broken

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Added a complete RSC migration suite: migration roadmap and phased quick-start, component-tree restructuring patterns, context & state strategies, data-fetching migration approaches, third‑party library compatibility patterns, and troubleshooting/pitfalls — each with decision guides, checklists, phased steps, practical examples/snippets, and recommended next steps.

Five-part guide covering how to restructure an existing React app for
React Server Components: component tree patterns, context/state management,
data fetching migration, third-party library compatibility, and
troubleshooting. Focuses on the React-side challenges rather than
framework configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds six new migration guides under docs/migrating/ that provide phased guidance for migrating React apps to React Server Components, covering an entry roadmap, component patterns, context/state, data fetching, third‑party library compatibility, and troubleshooting.

Changes

Cohort / File(s) Summary
Migration Foundation & Overview
docs/migrating/migrating-to-rsc.md
New entry‑point migration guide: rationale, prerequisites, three‑phase migration approach, quick‑start strategy, component audit checklist, cross‑references, and migration roadmap.
Component Restructuring Patterns
docs/migrating/rsc-component-patterns.md
New patterns doc describing top‑down migration phases and five refactor patterns (push state to leaves, Donut/children-as-props, wrapper extraction, async+Suspense, server→client promise handoff), decision guide, common mistakes, and example snippets.
Context & State Management
docs/migrating/rsc-context-and-state.md
New guide on provider wrapper patterns, composed providers, React.cache() usage, server↔client data sharing, per‑request stores for Redux/Zustand, provider examples, migration checklist, and constraints.
Data Fetching Migration
docs/migrating/rsc-data-fetching.md
New comprehensive data‑fetching migration guide: async server components, DB/server‑only access, migrating React Query/SWR, server actions for mutations, Suspense/loading strategies, deduplication, and waterfall avoidance patterns.
Third‑Party Library Compatibility
docs/migrating/rsc-third-party-libs.md
New compatibility guide: wrapper/export patterns, CSS‑in‑JS considerations, UI/form/animation library guidance, server/client guards, barrel file solutions, and a compatibility decision matrix with examples.
Troubleshooting & Pitfalls
docs/migrating/rsc-troubleshooting.md
New troubleshooting reference covering serialization boundaries, import‑chain contamination, accidental client components, hydration mismatches, error boundaries, performance pitfalls, testing, TypeScript considerations, and diagnostics.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

  • #2459 — Add migration guide: Migrating existing React on Rails projects to React Server Components (RSC): Matches the added docs/migrating/migrating-to-rsc.md and its acceptance criteria for a Rails‑focused migration guide.
  • shakacode/react_on_rails#2426 — RSC troubleshooting guidance: The new rsc-troubleshooting.md implements symptom→cause→fix troubleshooting content requested in that issue.

Poem

🐰 I hopped through docs both near and far,

Six little guides to raise the bar.
State tucked down and fetches sped,
Libraries wrapped and pitfalls shed.
Hop onward — migrate with flair!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add RSC migration guide series (#2459)' accurately describes the main change: adding a five-part migration guide series for React Server Components.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from #2459: comprehensive migration documentation covering prerequisites, step-by-step guidance, common pitfalls, verification strategies, and cross-references across five detailed articles.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the #2459 objective to create RSC migration guidance; no unrelated modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2459-add-rsc-migration-guide

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link

greptile-apps bot commented Feb 21, 2026

Greptile Summary

Adds a comprehensive 5-part RSC migration guide series (2,564 new lines) focusing on React-side restructuring challenges rather than framework configuration.

Content Overview:

  • Index page with navigation, quick-start strategy, and component audit checklist
  • Component patterns covering top-down migration, donut pattern, state extraction, and async components with Suspense
  • Context & state explaining provider wrappers, React.cache(), Redux/Zustand migration, and auth/theme/i18n patterns
  • Data fetching covering migration from useEffect/React Query/SWR to async Server Components, waterfall avoidance, and streaming
  • Third-party libraries with compatibility matrices for CSS-in-JS, UI frameworks, forms, animation, and charts
  • Troubleshooting covering serialization boundaries, hydration mismatches, TypeScript issues, testing strategies, and error catalog

Quality:

  • Well-structured with clear navigation between articles
  • Includes practical before/after code examples throughout
  • References real-world migrations (Mux 50K lines, WorkOS, Frigade)
  • Comprehensive decision matrices and troubleshooting tables
  • All internal cross-references verified working
  • External reference to ../pro/major-performance-breakthroughs-upgrade-guide.md exists

Documentation-only change: No code, config, or test modifications. Safe to merge.

Confidence Score: 5/5

  • This PR is safe to merge with no risk - it only adds documentation
  • Documentation-only PR adding 2,564 lines of well-structured RSC migration guides. All internal cross-references verified, external file references exist, content is technically accurate with concrete examples and real-world case studies. No code, configuration, or test changes that could introduce runtime issues.
  • No files require special attention

Important Files Changed

Filename Overview
docs/migrating/migrating-to-rsc.md Adds comprehensive overview and index for RSC migration guide series with clear navigation and prerequisites
docs/migrating/rsc-component-patterns.md Detailed guide on restructuring component trees with concrete patterns and before/after examples
docs/migrating/rsc-context-and-state.md Comprehensive coverage of Context migration patterns, state management libraries, and provider composition
docs/migrating/rsc-data-fetching.md Thorough explanation of data fetching migration patterns from client-side to server-side with practical examples
docs/migrating/rsc-third-party-libs.md Extensive compatibility matrix and wrapper patterns for third-party libraries including CSS-in-JS, UI frameworks, and utilities
docs/migrating/rsc-troubleshooting.md Detailed troubleshooting guide covering serialization, hydration, testing, TypeScript, and common error messages with solutions

Last reviewed commit: 438c99f

@claude
Copy link

claude bot commented Feb 21, 2026

PR Review: RSC Migration Guide Series

This is a substantial documentation contribution that covers React Server Components migration comprehensively. The structure is logical, the patterns are technically sound, and the cross-referencing between articles is consistent. Below are issues ranging from code bugs to scope concerns.


Bug: Wrong import in error boundary example

In docs/migrating/rsc-troubleshooting.md (line 219):

import { useRouter, startTransition } from 'next/navigation';

startTransition is not exported from next/navigation — it comes from react. This will produce a runtime error when copied:

import { useRouter } from 'next/navigation';
import { startTransition } from 'react';

Concern: Serialization table may be inaccurate for React 19

In docs/migrating/rsc-troubleshooting.md (lines 13–20), the table lists Map, Set, WeakMap, WeakSet as "Not Allowed" across the serialization boundary. This was true in earlier React Server Components implementations, but React 19's Flight protocol added support for Map, Set, typed arrays (e.g. Uint8Array), and several other built-ins. The table should be verified against the React 19 Flight source and either corrected or qualified with a version note.


Bug: addToCart is undefined in the refactored "After" example

In docs/migrating/rsc-component-patterns.md (line 137), the refactored AddToCartButton.jsx calls addToCart(productId, quantity) but never imports or defines it. The "Before" example has the same call — where it's presumably defined elsewhere — but the "After" example is presented as a self-contained new file. This leaves readers wondering where addToCart should come from (Server Action? utility import?). The example should show the import or clarify that it's a placeholder.


Scope concern: Heavy Next.js coupling in a "React-side" guide

The PR description says these guides focus on "React-side challenges… rather than framework configuration," but in practice most concrete examples depend on Next.js-specific APIs:

  • import { cookies } from 'next/headers' (rsc-context-and-state.md, lines 370, 401)
  • import { revalidatePath } from 'next/cache' (rsc-data-fetching.md, rsc-third-party-libs.md)
  • import { useRouter } from 'next/navigation' (rsc-troubleshooting.md)
  • optimizePackageImports in next.config.js (rsc-third-party-libs.md)
  • next-intl with getMessages(), getLocale() (rsc-context-and-state.md)
  • The entire "Params Must Be Awaited (Next.js 15+)" TypeScript section (rsc-troubleshooting.md)

React on Rails Pro implements RSC differently from Next.js (different renderer, different cache invalidation, no next/headers). Readers following this guide with a React on Rails app will not be able to use these patterns directly. The guide should either (a) explicitly note which examples are Next.js-specific and point to React on Rails Pro equivalents, or (b) abstract the framework-specific parts into clearly-labeled callouts.


Minor: New React 19 Context shorthand not labeled

In docs/migrating/rsc-context-and-state.md (line 192):

return <UserContext value={userPromise}>{children}</UserContext>;

This uses the React 19 shorthand context syntax (omitting .Provider). Since the prerequisite section does list React 19+, this is technically valid — but many readers will be confused since <Context.Provider value={...}> is more widely known. Adding a comment like // React 19+ shorthand; React 18 uses <UserContext.Provider value={...}> would prevent confusion.


Minor: if (!mounted) return null pattern needs a caveat

In docs/migrating/rsc-troubleshooting.md (lines 187–196), the "mounted" pattern is presented as a standard fix for hydration mismatches:

if (!mounted) return null; // Server render returns null

This causes Cumulative Layout Shift (CLS) — the component leaves blank space on first paint and then pops in. For content like a theme toggle button, the layout shift is minor. But for larger content blocks this pattern is a performance anti-pattern. The example should note that it's only appropriate for small, isolated UI elements, and that for larger content the cookie-based approach shown in the theme provider section is preferred.


Minor: docs/README.md not updated

The docs README "Migrating" section (line 18–20) lists three migration guides but does not link to the new RSC migration series. Users browsing GitHub will not discover this content without a direct link.

```jsx
'use client';

import { useRouter, startTransition } from 'next/navigation';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startTransition is not exported from next/navigation — it comes from react. This code will throw at runtime.

Suggested change
import { useRouter, startTransition } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { startTransition } from 'react';

|---------|-------------|
| Strings, numbers, booleans, `null`, `undefined` | Functions (except Server Actions) |
| Plain objects and arrays | Class instances |
| `Date` objects | `Map`, `Set`, `WeakMap`, `WeakSet` |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row may be inaccurate for React 19. The Flight protocol in React 19 added native serialization support for Map, Set, and typed arrays (e.g. Uint8Array). Please verify against the React 19 changelog and the Flight source, then either correct the table or add a version qualifier like "(React 18 only)".

value={quantity}
onChange={(e) => setQuantity(Number(e.target.value))}
/>
<button onClick={() => addToCart(productId, quantity)}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addToCart is called here but is never imported or defined in this file. The "After" example is presented as a new, self-contained Client Component, so readers will be puzzled about where this function comes from. Consider adding an import showing the intended pattern — e.g. a Server Action:

// AddToCartButton.jsx -- Client Component (interactive leaf)
'use client';

import { useState } from 'react';
import { addToCart } from '../actions'; // Server Action for mutation

export default function AddToCartButton({ productId }) {

export const UserContext = createContext(null);

export default function UserProvider({ children, userPromise }) {
return <UserContext value={userPromise}>{children}</UserContext>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the React 19 shorthand context syntax. React 18 users need <UserContext.Provider value={userPromise}>. Since readers may not be on React 19 despite the prerequisites listing it, a brief comment would prevent confusion:

Suggested change
return <UserContext value={userPromise}>{children}</UserContext>;
return <UserContext value={userPromise}>{children}</UserContext>; // React 19+ shorthand; use <UserContext.Provider value={...}> in React 18


useEffect(() => setMounted(true), []);

if (!mounted) return null; // Server render returns null
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if (!mounted) return null pattern causes Cumulative Layout Shift (CLS) — the element occupies no space on first paint, then pops in after hydration. It's acceptable for small isolated controls (like a single toggle button), but should not be used for any content that affects layout. Consider adding a caveat:

Note: Returning null before mount causes layout shift. Only use this pattern for small, positionally stable UI elements (icon buttons, toggles). For anything that affects page layout, read the preference from a server-readable cookie (see the Theme Provider section above) to render the correct value on first paint.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/migrating/migrating-to-rsc.md`:
- Around line 1-110: Add the new RSC migration articles to the docs navigation
by editing docs/README.md: under the "🏗️ **Migrating from Other Solutions**"
section, add links to migrating-to-rsc.md and the related pages
(rsc-component-patterns.md, rsc-context-and-state.md, rsc-data-fetching.md,
rsc-third-party-libs.md, rsc-troubleshooting.md) so they appear alongside the
existing migration entries like "From react-rails" and "From Webpack to Rspack";
ensure each entry uses the same markdown list style and relative link format as
the other migration guides.

In `@docs/migrating/rsc-component-patterns.md`:
- Around line 36-54: The fenced code block containing the tree diagram (starting
with "App ('use client')" and the Before/After layout) lacks a language
specifier, causing MD040; update the opening fence from ``` to ```text (or
another appropriate language) so the block is explicitly marked (e.g., change
the diagram's opening ``` to ```text) to satisfy the markdownlint rule.

In `@docs/migrating/rsc-context-and-state.md`:
- Around line 366-415: Summary: The examples use Next.js-specific APIs (import {
cookies } from 'next/headers' and await cookies()) without calling that out. Add
a brief "Note (Next.js):" callout above both examples (the Auth Provider Layout
using verifyToken and AuthProvider, and the Theme Provider Layout reading theme)
stating that cookies() and the async await cookies() API are Next.js-specific
(Next.js 15+), that React on Rails reads request cookies differently, and link
or point readers to the React on Rails Pro docs for the equivalent approach;
keep the note concise and model its style on the existing callout in
migrating-to-rsc.md.
- Around line 183-194: The docs mix React 18 explicit provider syntax
(ThemeContext.Provider) and the React 19 shorthand (UserContext value={...}),
which is confusing; pick one approach and make it consistent: either update the
UserProvider example to use the explicit UserContext.Provider with a value prop
(referencing UserProvider and UserContext) to match ThemeContext.Provider, or
convert ThemeContext.Provider usages to the React 19 shorthand and add a brief
note explaining that <Context value={...}> is the React 19 shorthand; ensure all
examples and the surrounding text consistently reference the chosen style.

In `@docs/migrating/rsc-data-fetching.md`:
- Around line 494-522: The example uses Next.js-specific API revalidatePath
imported from 'next/cache' inside the createComment server action (actions.js)
but doesn't warn readers that this is Next.js-only; add a short disclaimer above
or beside the code block stating that the snippet requires Next.js Server
Actions and revalidatePath from 'next/cache' and link or suggest alternative
APIs (e.g., React on Rails Pro streaming/revalidation) for non-Next.js
environments so users of CommentForm and createComment know this won’t work
outside Next.js.
- Around line 468-480: The examples show improper use of await inside non-async
functions; change both Profile definitions to be async so the await is valid and
the cache-placement lesson is preserved: mark the component function Profile as
async in both examples, leaving the module-level const getUser =
cache(fetchUser) and the in-component const getUser = cache(fetchUser) variants
intact so readers can see the difference in cache placement while ensuring
getUser, cache, fetchUser, and Profile compile correctly.

In `@docs/migrating/rsc-troubleshooting.md`:
- Around line 87-93: The two fenced code blocks that render the import-chain
diagram starting with "ClientComponent.jsx ('use client')" and the
testing-strategy diagram are missing language specifiers (MD040); update each
opening fence from ``` to a fenced block with a language identifier such as
```text (or ```bash/```text if you prefer) so the blocks become ```text ... ```;
locate the blocks by the visible strings "ClientComponent.jsx ('use client')"
and the "testing-strategy" diagram and add the language identifier to each
opening fence.
- Around line 216-247: The example imports startTransition from the wrong
module; change the imports so startTransition comes from 'react' while useRouter
remains imported from 'next/navigation' (update the import statement(s) that
reference startTransition and useRouter), and ensure the retry function in
ErrorFallback still calls startTransition(() => { router.refresh();
resetErrorBoundary(); });; also add a brief note near
PageErrorBoundary/ErrorFallback clarifying that useRouter and router.refresh()
are Next.js-specific APIs so readers using other frameworks (e.g., React on
Rails) won’t be confused.

Comment on lines 1 to 110
# Migrating Your React App to React Server Components

This guide covers the React-side challenges of migrating an existing React application to React Server Components (RSC). It focuses on how to restructure your component tree, handle Context and state management, migrate data fetching patterns, deal with third-party library compatibility, and avoid common pitfalls.

> **Note:** For React on Rails-specific configuration (enabling RSC support, Webpack setup, node renderer, view helpers), see the [React on Rails Pro RSC documentation](https://www.shakacode.com/react-on-rails-pro/docs/react-server-components/tutorial/) and the [performance breakthroughs guide](../pro/major-performance-breakthroughs-upgrade-guide.md).

## Why Migrate?

React Server Components offer significant performance benefits when used correctly:

- **62% reduction** in client-side bundle size ([Frigade case study](https://frigade.com/blog/bundle-size-reduction-with-rsc-and-frigade))
- **63% improvement** in Google Speed Index
- Total blocking time reduced **from 110ms to 1ms**
- Server-only dependencies (date-fns, marked, sanitize-html) never ship to the client

However, these benefits require intentional architecture changes. Simply adding `'use client'` everywhere preserves the status quo. The guides below walk you through the restructuring needed to capture real gains.

## Article Series

This migration guide is organized as a series of focused articles. We recommend reading them in order, but each is self-contained:

### 1. [Component Tree Restructuring Patterns](rsc-component-patterns.md)

How to restructure your component tree for RSC. Covers:

- The top-down migration strategy (start at layouts, push `'use client'` to leaves)
- The "donut pattern" for wrapping server content in client interactivity
- Splitting mixed components into server and client parts
- Passing Server Components as children to Client Components
- Before/after examples of common restructuring patterns

### 2. [Context, Providers, and State Management](rsc-context-and-state.md)

How to handle React Context and global state in an RSC world. Covers:

- Why Context doesn't work in Server Components and what to do about it
- The provider wrapper pattern (creating `'use client'` provider components)
- Composing multiple providers without "provider hell"
- Migrating Redux, Zustand, and Jotai to work alongside RSC
- Using `React.cache()` as a server-side alternative to Context
- Theme, auth, and i18n provider patterns

### 3. [Data Fetching Migration](rsc-data-fetching.md)

How to migrate from client-side data fetching to server component patterns. Covers:

- Replacing `useEffect` + `fetch` with async Server Components
- Migrating from React Query / TanStack Query (prefetch + hydrate pattern)
- Migrating from SWR (fallback data pattern)
- Avoiding server-side waterfalls with parallel fetching
- Streaming data with the `use()` hook and Suspense
- When to keep client-side data fetching

### 4. [Third-Party Library Compatibility](rsc-third-party-libs.md)

How to handle libraries that aren't RSC-compatible. Covers:

- Creating thin `'use client'` wrapper files
- CSS-in-JS migration (styled-components, Emotion alternatives)
- UI library compatibility (MUI, Chakra, Radix, shadcn/ui)
- Form, animation, charting, and date library status
- The barrel file problem and `optimizePackageImports`
- Using `server-only` and `client-only` packages

### 5. [Troubleshooting and Common Pitfalls](rsc-troubleshooting.md)

How to debug and avoid common problems. Covers:

- Serialization boundary issues (what can cross server-to-client)
- Import chain contamination and accidental client components
- Hydration mismatch debugging
- Error boundary limitations with RSC
- Testing strategies (unit, integration, E2E)
- TypeScript considerations
- Performance monitoring and bundle analysis tools
- Common error messages and their solutions

## Quick-Start Migration Strategy

If you want the shortest path to RSC benefits, follow this strategy from [Mux's migration of 50,000 lines](https://www.mux.com/blog/what-are-react-server-components):

1. **Add `'use client'` to your app entry point** -- everything works as before, nothing breaks
2. **Progressively push the directive lower** -- move `'use client'` from parent components to child components
3. **Adopt advanced patterns** -- add Suspense boundaries, streaming, and server-side data fetching

This three-phase approach lets you migrate incrementally without ever breaking your app.

## Component Audit Checklist

Before you start, audit your components using this classification:

| Category | Criteria | Action |
|----------|----------|--------|
| **Server-ready** (green) | No hooks, no browser APIs, no event handlers | Remove `'use client'` -- these are Server Components by default |
| **Refactorable** (yellow) | Mix of data fetching and interactivity | Split into a Server Component (data) + Client Component (interaction) |
| **Client-only** (red) | Uses `useState`, `useEffect`, event handlers, browser APIs | Keep `'use client'` -- these remain Client Components |

## Prerequisites

- React 19+
- React on Rails 15+ and React on Rails Pro 4+ (for React on Rails projects)
- Node.js 20+
- Understanding of the [server vs client component mental model](https://react.dev/reference/rsc/server-components)

## References

- [React Server Components RFC](https://react.dev/reference/rsc/server-components)
- [React `'use client'` directive](https://react.dev/reference/rsc/use-client)
- [React on Rails Pro RSC tutorial](https://www.shakacode.com/react-on-rails-pro/docs/react-server-components/tutorial/)
- [React on Rails Pro RSC purpose and benefits](https://www.shakacode.com/react-on-rails-pro/docs/react-server-components/purpose-and-benefits/)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find potential sidebar/navigation config files
echo "=== Searching for sidebar/navigation config files ==="
find docs -type f \( -name "*sidebar*" -o -name "*nav*" -o -name "*index*" \) | head -20

echo ""
echo "=== Looking for JS config files in docs root ==="
fd --type f -e js -e json -e yaml -e yml "." docs/ | grep -E "(sidebar|nav|index|config)" | head -20

echo ""
echo "=== Checking for docusaurus or similar framework config ==="
ls -la docs/ 2>/dev/null | head -20

echo ""
echo "=== Looking in docs/migrating/ ==="
ls -la docs/migrating/ 2>/dev/null

Repository: shakacode/react_on_rails

Length of output: 2508


🏁 Script executed:

#!/bin/bash
# Search for references to the new migration guide files
echo "=== Searching for references to new migration files in docs ==="
rg "rsc-component-patterns|rsc-context-and-state|rsc-data-fetching|rsc-third-party-libs|rsc-troubleshooting" docs/ --type md

echo ""
echo "=== Searching for migrating-to-rsc references ==="
rg "migrating-to-rsc" docs/ --type md

Repository: shakacode/react_on_rails

Length of output: 3900


🏁 Script executed:

#!/bin/bash
# Look for issue `#2459` or related context
echo "=== Searching for issue 2459 ==="
rg "2459" . --type md --type txt 2>/dev/null | head -20

echo ""
echo "=== Checking git commits for issue reference ==="
git log --oneline --all 2>/dev/null | grep -i "2459\|rsc\|migration" | head -20

Repository: shakacode/react_on_rails

Length of output: 1687


🏁 Script executed:

#!/bin/bash
# Search for sidebar/nav config in root and common locations
echo "=== Searching for sidebar config in entire repo root ==="
find . -maxdepth 2 -type f \( -name "*sidebar*" -o -name "*nav*" \) 2>/dev/null | grep -v node_modules | grep -v ".git"

echo ""
echo "=== Check docs/README.md ==="
cat docs/README.md 2>/dev/null | head -50

echo ""
echo "=== Search for docusaurus or documentation config ==="
fd --type f "sidebar|config" . --max-depth 2 2>/dev/null | grep -v node_modules | grep -v ".git" | head -20

Repository: shakacode/react_on_rails

Length of output: 3164


🏁 Script executed:

#!/bin/bash
# Look for documentation structure and any listing/index
echo "=== Checking for _category_.json or similar index files in docs ==="
find docs -name "_category_*" -o -name "index*" 2>/dev/null

echo ""
echo "=== Check if there's a table of contents or index ==="
find docs -type f -name "*.md" | xargs grep -l "Table of Contents\|table of contents" 2>/dev/null | head -5

echo ""
echo "=== List all markdown files in docs/migrating/ ==="
ls -1 docs/migrating/*.md

Repository: shakacode/react_on_rails

Length of output: 843


🏁 Script executed:

#!/bin/bash
# Search for the actual PR/issue context to understand acceptance criteria
echo "=== Searching for 2459 in any documentation ==="
rg "2459" . --type md --type txt --type json 2>/dev/null

echo ""
echo "=== Look for CHANGELOG or PR description ==="
git log 438c99f9 --format="%B" 2>/dev/null | head -50

Repository: shakacode/react_on_rails

Length of output: 588


Add RSC migration series to docs navigation — missing from README.md

The new RSC migration guides (migrating-to-rsc.md, rsc-component-patterns.md, rsc-context-and-state.md, rsc-data-fetching.md, rsc-third-party-libs.md, and rsc-troubleshooting.md) are internally cross-linked but not listed in docs/README.md. They should be added to the "🏗️ Migrating from Other Solutions" section alongside other migration guides like "From react-rails" and "From Webpack to Rspack".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/migrating-to-rsc.md` around lines 1 - 110, Add the new RSC
migration articles to the docs navigation by editing docs/README.md: under the
"🏗️ **Migrating from Other Solutions**" section, add links to
migrating-to-rsc.md and the related pages (rsc-component-patterns.md,
rsc-context-and-state.md, rsc-data-fetching.md, rsc-third-party-libs.md,
rsc-troubleshooting.md) so they appear alongside the existing migration entries
like "From react-rails" and "From Webpack to Rspack"; ensure each entry uses the
same markdown list style and relative link format as the other migration guides.

Comment on lines +36 to +54
```
Before:
App ('use client') <-- Everything is client-side
├── Layout
│ ├── Header
│ │ ├── Logo
│ │ └── SearchBar
│ ├── Content
│ └── Footer

After:
App <-- Server Component (no directive)
├── Layout <-- Server Component
│ ├── Header <-- Server Component
│ │ ├── Logo <-- Server Component
│ │ └── SearchBar ('use client') <-- Only this needs client
│ ├── Content <-- Server Component
│ └── Footer <-- Server Component
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language identifier to fenced code block (MD040).

The tree-diagram block is missing a language specifier, triggering a markdownlint warning.

🔧 Proposed fix
-```
+```text
 Before:
 App ('use client')           <-- Everything is client-side
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 36-36: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 36 - 54, The fenced
code block containing the tree diagram (starting with "App ('use client')" and
the Before/After layout) lacks a language specifier, causing MD040; update the
opening fence from ``` to ```text (or another appropriate language) so the block
is explicitly marked (e.g., change the diagram's opening ``` to ```text) to
satisfy the markdownlint rule.

Comment on lines +183 to +194
```jsx
// user-provider.jsx
'use client';

import { createContext } from 'react';

export const UserContext = createContext(null);

export default function UserProvider({ children, userPromise }) {
return <UserContext value={userPromise}>{children}</UserContext>;
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent Context provider syntax across Pattern 1 and Pattern 4.

Pattern 1 (Line 46) uses <ThemeContext.Provider value={...}> (React 18 syntax), while Pattern 4 (Line 192) uses <UserContext value={userPromise}> (the new React 19 shorthand). Mixing both without explanation is likely to confuse readers who expect consistency. Either standardize on one style or add a brief note explaining that <Context value={...}> is the React 19 shorthand.

🔧 Suggested clarification (option A — standardize on React 19 syntax throughout)
-    <ThemeContext.Provider value={{ theme, setTheme }}>
-      {children}
-    </ThemeContext.Provider>
+    <ThemeContext value={{ theme, setTheme }}>
+      {children}
+    </ThemeContext>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-context-and-state.md` around lines 183 - 194, The docs mix
React 18 explicit provider syntax (ThemeContext.Provider) and the React 19
shorthand (UserContext value={...}), which is confusing; pick one approach and
make it consistent: either update the UserProvider example to use the explicit
UserContext.Provider with a value prop (referencing UserProvider and
UserContext) to match ThemeContext.Provider, or convert ThemeContext.Provider
usages to the React 19 shorthand and add a brief note explaining that <Context
value={...}> is the React 19 shorthand; ensure all examples and the surrounding
text consistently reference the chosen style.

Comment on lines +366 to +415
Read auth state on the server, pass to a Client Component provider:

```jsx
// layout.jsx -- Server Component
import { cookies } from 'next/headers';
import { verifyToken } from './lib/auth';
import AuthProvider from './auth-provider';

export default async function Layout({ children }) {
const cookieStore = await cookies();
const token = cookieStore.get('session')?.value;
const user = token ? await verifyToken(token) : null;

return (
<html>
<body>
<AuthProvider user={user}>{children}</AuthProvider>
</body>
</html>
);
}
```

**Key constraints:**

- Server Components can read `HttpOnly` cookies that client JavaScript cannot access (security advantage)
- Cookies are read-only in Server Components -- use Server Actions or middleware to set cookies
- Session refresh must happen in middleware, before the Server Component renders

### Theme Provider (No Flash of Wrong Theme)

For server-side theme rendering without flicker, store the preference in a cookie:

```jsx
// layout.jsx -- Server Component
import { cookies } from 'next/headers';

export default async function Layout({ children }) {
const cookieStore = await cookies();
const theme = cookieStore.get('theme')?.value || 'light';

return (
<html className={theme}>
<body>{children}</body>
</html>
);
}
```

The correct CSS class is applied during SSR -- no flash of the wrong theme on initial load. A Client Component can update the cookie via a Server Action when the user toggles themes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

cookies() from next/headers and await cookies() are Next.js-specific — add disclaimers.

The Auth Provider and Theme Provider examples both import cookies from 'next/headers', which is a Next.js API. The await cookies() call (Line 375, 405) is a Next.js 15+ async cookies API. React on Rails users accessing request cookies do so through different mechanisms. These sections should carry a > **Note (Next.js):** callout, similar to how the opening note in migrating-to-rsc.md points to React on Rails Pro documentation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-context-and-state.md` around lines 366 - 415, Summary: The
examples use Next.js-specific APIs (import { cookies } from 'next/headers' and
await cookies()) without calling that out. Add a brief "Note (Next.js):" callout
above both examples (the Auth Provider Layout using verifyToken and
AuthProvider, and the Theme Provider Layout reading theme) stating that
cookies() and the async await cookies() API are Next.js-specific (Next.js 15+),
that React on Rails reads request cookies differently, and link or point readers
to the React on Rails Pro docs for the equivalent approach; keep the note
concise and model its style on the existing callout in migrating-to-rsc.md.

Comment on lines +468 to +480
```jsx
// WRONG: Creating cache inside a component
function Profile({ userId }) {
const getUser = cache(fetchUser); // New cache every render!
const user = await getUser(userId);
}

// CORRECT: Define at module level
const getUser = cache(fetchUser);
function Profile({ userId }) {
const user = await getUser(userId);
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Both WRONG and CORRECT examples use await inside non-async functions — syntax error.

Neither Profile function is declared async, yet both use await. This is a syntax error that would cause the examples to fail if copied, and obscures the actual lesson (cache placement vs. async usage).

🐛 Proposed fix
 // WRONG: Creating cache inside a component
-function Profile({ userId }) {
+async function Profile({ userId }) {
   const getUser = cache(fetchUser); // New cache every render!
   const user = await getUser(userId);
 }
 
 // CORRECT: Define at module level
 const getUser = cache(fetchUser);
-function Profile({ userId }) {
+async function Profile({ userId }) {
   const user = await getUser(userId);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-data-fetching.md` around lines 468 - 480, The examples
show improper use of await inside non-async functions; change both Profile
definitions to be async so the await is valid and the cache-placement lesson is
preserved: mark the component function Profile as async in both examples,
leaving the module-level const getUser = cache(fetchUser) and the in-component
const getUser = cache(fetchUser) variants intact so readers can see the
difference in cache placement while ensuring getUser, cache, fetchUser, and
Profile compile correctly.

Comment on lines 494 to 522
```jsx
// actions.js
'use server';

import { revalidatePath } from 'next/cache';

export async function createComment(formData) {
const content = formData.get('content');
const postId = formData.get('postId');

await db.comments.create({ data: { content, postId } });
revalidatePath(`/posts/${postId}`); // Triggers re-render with fresh data
}
```

```jsx
// CommentForm.jsx -- works without JavaScript (progressive enhancement)
import { createComment } from './actions';

export default function CommentForm({ postId }) {
return (
<form action={createComment}>
<input type="hidden" name="postId" value={postId} />
<textarea name="content" />
<button type="submit">Post Comment</button>
</form>
);
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

revalidatePath is a Next.js-specific API — add a disclaimer.

revalidatePath is imported from 'next/cache' with no indication that this is Next.js-only. React on Rails users will not have this module available. A brief note (e.g., > **Note (Next.js):** The following example uses Next.js Server Actions and \revalidatePath`. For React on Rails, use React on Rails Pro's streaming/revalidation APIs.`) would prevent confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-data-fetching.md` around lines 494 - 522, The example uses
Next.js-specific API revalidatePath imported from 'next/cache' inside the
createComment server action (actions.js) but doesn't warn readers that this is
Next.js-only; add a short disclaimer above or beside the code block stating that
the snippet requires Next.js Server Actions and revalidatePath from 'next/cache'
and link or suggest alternative APIs (e.g., React on Rails Pro
streaming/revalidation) for non-Next.js environments so users of CommentForm and
createComment know this won’t work outside Next.js.

Comment on lines +87 to +93
```
ClientComponent.jsx ('use client')
├── imports utils.js → becomes client code
│ └── imports heavy-lib.js → becomes client code (100KB wasted)
├── imports helpers.js → becomes client code
│ └── imports db-utils.js → becomes client code (SECURITY RISK)
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language identifiers to fenced code blocks (MD040).

Both the import-chain diagram at Line 87 and the testing-strategy diagram at Line 322 are missing language specifiers.

🔧 Proposed fix
-```
+```text
 ClientComponent.jsx ('use client')
 ├── imports utils.js          → becomes client code
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 87-87: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-troubleshooting.md` around lines 87 - 93, The two fenced
code blocks that render the import-chain diagram starting with
"ClientComponent.jsx ('use client')" and the testing-strategy diagram are
missing language specifiers (MD040); update each opening fence from ``` to a
fenced block with a language identifier such as ```text (or ```bash/```text if
you prefer) so the blocks become ```text ... ```; locate the blocks by the
visible strings "ClientComponent.jsx ('use client')" and the "testing-strategy"
diagram and add the language identifier to each opening fence.

Comment on lines +216 to +247
```jsx
'use client';

import { useRouter, startTransition } from 'next/navigation';
import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
const router = useRouter();

function retry() {
startTransition(() => {
router.refresh(); // Re-renders Server Components on the server
resetErrorBoundary(); // Resets client error boundary state
});
}

return (
<div>
<p>Something went wrong</p>
<button onClick={retry}>Retry</button>
</div>
);
}

export default function PageErrorBoundary({ children }) {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
{children}
</ErrorBoundary>
);
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

startTransition import React vs next/navigation documentation

💡 Result:

startTransition is a React API, so it’s imported from react, not from Next.js:

import { startTransition } from "react";

React docs show this exact import for startTransition. [1]

next/navigation exports routing utilities like useRouter (App Router) and others, but it does not export startTransition—router hooks are imported like:

import { useRouter } from "next/navigation";

(And in the Pages Router it’s next/router.) [2]

Typical Next.js App Router usage together:

import { startTransition } from "react";
import { useRouter } from "next/navigation";

const router = useRouter();
startTransition(() => router.push("/dashboard"));

Sources: React startTransition reference [1]; Next.js routing API reference (router import paths) [2].

[1] (react.dev)
[2] (nextjs.org)

Citations:


🏁 Script executed:

# Check if the file exists and examine the content at the specified lines
fd rsc-troubleshooting.md

Repository: shakacode/react_on_rails

Length of output: 105


🏁 Script executed:

# If file exists, check the content around lines 216-247
if [ -f docs/migrating/rsc-troubleshooting.md ]; then
  sed -n '216,247p' docs/migrating/rsc-troubleshooting.md
fi

Repository: shakacode/react_on_rails

Length of output: 792


startTransition must be imported from 'react', not 'next/navigation'.

startTransition is a React API and is not exported by next/navigation. This will cause a runtime error. Separate the imports:

-import { useRouter, startTransition } from 'next/navigation';
+import { useRouter } from 'next/navigation';
+import { startTransition } from 'react';

Additionally, useRouter and router.refresh() are Next.js-specific APIs. Consider adding a note to clarify this example is for Next.js, as React on Rails users may encounter confusion.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```jsx
'use client';
import { useRouter, startTransition } from 'next/navigation';
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
const router = useRouter();
function retry() {
startTransition(() => {
router.refresh(); // Re-renders Server Components on the server
resetErrorBoundary(); // Resets client error boundary state
});
}
return (
<div>
<p>Something went wrong</p>
<button onClick={retry}>Retry</button>
</div>
);
}
export default function PageErrorBoundary({ children }) {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
{children}
</ErrorBoundary>
);
}
```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-troubleshooting.md` around lines 216 - 247, The example
imports startTransition from the wrong module; change the imports so
startTransition comes from 'react' while useRouter remains imported from
'next/navigation' (update the import statement(s) that reference startTransition
and useRouter), and ensure the retry function in ErrorFallback still calls
startTransition(() => { router.refresh(); resetErrorBoundary(); });; also add a
brief note near PageErrorBoundary/ErrorFallback clarifying that useRouter and
router.refresh() are Next.js-specific APIs so readers using other frameworks
(e.g., React on Rails) won’t be confused.

Visual diagram showing the three-phase RSC migration process:
Phase 1 (all client) → Phase 2 (push boundary down) → Phase 3 (split mixed components).
Uses animated transitions with color-coded server (cyan) and client (orange) components.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Feb 21, 2026

Review: RSC Migration Guide Series

This is a comprehensive and well-structured documentation series. The content is accurate in its high-level concepts and the code examples demonstrate real-world patterns effectively. A few issues need addressing before merging.

Critical Bug

startTransition wrong import in rsc-troubleshooting.md:
startTransition is exported from react, not next/navigation. The import on line 219 will fail at runtime.

Code Accuracy Issues

"Solution 4: Mixed Strategy" in rsc-data-fetching.md is incomplete:
The example passes promise={reviewsPromise} to ReviewsSection and RelatedSection without defining those components or how they consume the promise. If they are Server Components, there is no need to pass the promise — just call the data function directly (React.cache handles deduplication). If they are Client Components using use(), the code needs 'use client' and the consumption pattern shown. As written, this example is misleading.

Inconsistent React Context API in rsc-context-and-state.md:
Line 192 uses React 19's new shorthand (<UserContext value={...}>) while line 46 uses the older <ThemeContext.Provider value={...}>. Both are valid, but mixing them in the same article without explanation will confuse readers. Either use consistent syntax throughout, or explicitly call out the React 19 shorthand when you introduce it.

@types/react version in the TypeScript section:
The fix recommends @types/react 18.2.8+, but the prerequisites state React 19+ is required. React 19 users should be on @types/react@19 — the 18.x recommendation is outdated for this guide's target audience.

Security Gap

Server Actions need an authentication note in rsc-data-fetching.md:
The createComment Server Action example has no auth check. Server Actions are public HTTP endpoints — anyone can POST to them directly. The first place Server Actions appear should warn readers of this, or at minimum include a comment like // Always verify the user is authenticated before mutating data. The Troubleshooting guide's TypeScript section covers runtime validation, but that is too late in the series for this critical point.

Documentation Quality

Framework-specific APIs are unlabeled:
The intro claims to cover "React-side challenges" but all five articles use Next.js-specific APIs without consistent labeling: cookies() from next/headers, revalidatePath from next/cache, optimizePackageImports in next.config.js, useRouter from next/navigation, and the params: Promise<{id: string}> shape (Next.js 15 behavior). The examples should be labeled as "Next.js example" where relevant, or the intro should acknowledge Next.js as the primary target framework.

"Mounted" pattern needs a stronger caveat:
The if (!mounted) return null pattern in the troubleshooting guide reliably causes CLS (Cumulative Layout Shift) because the server renders null and the client renders actual content after hydration. It should be marked as a last resort and the guide should note it will hurt Core Web Vitals scores. It is often a sign that the component needs architectural rethinking rather than this workaround.

Positive Notes

  • The top-down migration strategy is well-explained and the three-phase approach is sound
  • The React.cache() deduplication examples, including the common mistakes, are accurate and valuable
  • The library compatibility matrix is comprehensive and useful
  • The "donut pattern" and children-as-props explanations are clear and accurate
  • The Redux/Zustand per-request store factory pattern is correctly implemented with useRef
  • The serialization boundary table and error message catalog are practical references

```jsx
'use client';

import { useRouter, startTransition } from 'next/navigation';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: startTransition is not exported from next/navigation

startTransition is a React API, not a Next.js API. This import will fail at runtime with a "module not found" or undefined error.

Suggested change
import { useRouter, startTransition } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { startTransition } from 'react';

<div>
<ProductDetail product={product} />
<Suspense fallback={<ReviewsSkeleton />}>
<ReviewsSection promise={reviewsPromise} />
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete example: ReviewsSection and RelatedSection are never defined

The example passes promise={reviewsPromise} as a prop but never shows what these components look like or how they consume the promise. This creates ambiguity about the intended pattern:

  • If they are Server Components, passing a promise this way adds no value — they should simply call getReviews(productId) directly inside themselves. React.cache() ensures no duplicate fetch.
  • If they are Client Components using use(), they need 'use client' and a use(promise) call, which should be shown.

Consider adding a follow-up component definition, e.g.:

// ReviewsSection.jsx -- Client Component
'use client';
import { use } from 'react';

export default function ReviewsSection({ promise }) {
  const reviews = use(promise);
  return <ReviewList reviews={reviews} />;
}

Or, if keeping them as Server Components, remove the promise-passing and call getReviews(productId) directly inside ReviewsSection, which is simpler and equally performant thanks to React.cache().

export const UserContext = createContext(null);

export default function UserProvider({ children, userPromise }) {
return <UserContext value={userPromise}>{children}</UserContext>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent Context API syntax in this article

This uses React 19's new shorthand <Context value={...}>, but earlier examples in the same file (line 46) use the older <Context.Provider value={...}> syntax. Both are valid, but mixing them without explanation will confuse readers, particularly those who haven't yet upgraded to React 19.

Since the prerequisites list React 19+, using the new shorthand consistently is fine — but you should either:

  1. Use the new syntax consistently throughout all examples in the article, or
  2. Add a brief note the first time it appears: // React 19+ shorthand for <UserContext.Provider value={...}>


**Error:** `"'App' cannot be used as a JSX component. Its return type 'Promise<JSX.Element>' is not a valid JSX element type."`

**Fix:** Upgrade to TypeScript 5.1.2+ with `@types/react` 18.2.8+, or omit the explicit return type annotation:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outdated @types/react version for a React 19+ guide

@types/react 18.2.8 introduced async component support for React 18, but this guide's prerequisites require React 19+. For React 19 projects the correct package is @types/react@19 (or @types/react@^19). Recommending an 18.x version here may cause readers to install the wrong type package.

Suggested change
**Fix:** Upgrade to TypeScript 5.1.2+ with `@types/react` 18.2.8+, or omit the explicit return type annotation:
**Fix:** Upgrade to TypeScript 5.1.2+ with `@types/react@19` (React 19 projects) or `@types/react` 18.2.8+ (React 18 projects), or omit the explicit return type annotation:


import { revalidatePath } from 'next/cache';

export async function createComment(formData) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing security note: Server Actions are public HTTP endpoints

Server Actions are exposed as POST endpoints that anyone can call — they are not restricted to your own UI. This example has no authentication or authorization check. A reader who copies this directly will have an unauthenticated mutation endpoint.

Consider adding a comment and note:

export async function createComment(formData) {
  // Server Actions are public endpoints — always authenticate
  const session = await getSession();
  if (!session?.userId) throw new Error('Unauthorized');

  const content = formData.get('content');
  // ...
}

And add a callout after the example: Security: Always verify the caller is authenticated and authorized before performing mutations. Never trust formData values without validation — see the runtime validation example in the Troubleshooting guide.


useEffect(() => setMounted(true), []);

if (!mounted) return null; // Server render returns null
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caveat needed: this pattern harms Core Web Vitals

if (!mounted) return null means the server renders nothing for this element, then the client renders actual content after hydration. This causes:

  1. CLS (Cumulative Layout Shift) — content pops in after the initial paint, which Google measures and penalizes
  2. Flash of missing content — visible to users on slower connections

This should be presented as a last resort with a clear warning. A better fix is usually architectural: either read the value from a cookie on the server (to avoid the mismatch entirely), or accept a controlled mismatch with suppressHydrationWarning on a non-critical element.

Consider adding: // Warning: causes CLS. Prefer reading from cookies on the server, or use suppressHydrationWarning for non-layout-critical elements.

2. **Tree-shaking failure:** Bundlers struggle to eliminate unused exports
3. **Mixed server/client exports:** Known bugs in Next.js when a barrel file exports both server and client components

### The Solution: `optimizePackageImports`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optimizePackageImports is a Next.js-only feature

This configuration option is specific to Next.js (next.config.js) and is not available in other RSC-supporting frameworks (Waku, Parcel RSC, etc.). Since the guide aims to cover React-side challenges generally, this section should be labeled as a Next.js-specific solution.

Consider prefixing the heading or adding a note: Next.js only: optimizePackageImports is a Next.js-specific feature. Other frameworks may handle barrel file optimization differently (e.g., via bundler plugins or sideEffects: false in package.json).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/migrating/rsc-component-patterns.md`:
- Around line 108-122: The "After" example dropped the module export for the
server component—restore the export so consumers don't break: add the missing
export for ProductPage (e.g., export default async function ProductPage(...) or
export default ProductPage at the end) so the function ProductPage remains the
default export and matches the "Before" example.
- Around line 276-307: The snippet is missing imports and the Stats component
isn't exported; update Dashboard to import Stats, RevenueChart, RecentOrders,
StatsSkeleton, ChartSkeleton, and TableSkeleton (so Dashboard can render those
symbols), and make the Stats component an exported module (export default Stats
or export a named Stats and ensure Dashboard imports it accordingly) so the
runtime import resolves; adjust import/export consistently between Dashboard and
Stats.
- Around line 336-352: Add a React version note before the Comments component
showing that use(commentsPromise) requires React 19; update the docs around the
Comments component (the Comments function and its use(commentsPromise) call) to
include a short callout that this pattern depends on React 19 (React 18 lacks
use()) so readers on React 18 will encounter errors. Keep the callout concise
and place it immediately above the code block that begins with "'use client';"
and the Comments component.

---

Duplicate comments:
In `@docs/migrating/rsc-component-patterns.md`:
- Around line 40-58: The fenced code block containing the Before/After tree
diagram (the block starting with ``` and showing App, Layout, Header, Logo,
SearchBar, Content, Footer) is missing a language specifier; add an appropriate
specifier such as ```text (or ```diff if preferred) to the opening fence so the
diagram is rendered with correct formatting and syntax highlighting.

Comment on lines +108 to +122
```jsx
// ProductPage.jsx -- Server Component (no directive)
async function ProductPage({ productId }) {
const product = await getProduct(productId);

return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<ProductSpecs specs={product.specs} />
<ReviewList reviews={product.reviews} />
<AddToCartButton productId={product.id} />
</div>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

export default missing from ProductPage in the "After" example.

The "Before" snippet (line 73) has export default function ProductPage, but the "After" snippet drops the export, creating an inconsistency. Readers migrating their own code might incorrectly remove the export during the refactor.

📝 Proposed fix
-async function ProductPage({ productId }) {
+export default async function ProductPage({ productId }) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 108 - 122, The "After"
example dropped the module export for the server component—restore the export so
consumers don't break: add the missing export for ProductPage (e.g., export
default async function ProductPage(...) or export default ProductPage at the
end) so the function ProductPage remains the default export and matches the
"Before" example.

Comment on lines +276 to +307
```jsx
// Dashboard.jsx -- Server Component
import { Suspense } from 'react';

export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<StatsSkeleton />}>
<Stats /> {/* Fetches and renders independently */}
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart /> {/* Fetches and renders independently */}
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<RecentOrders /> {/* Fetches and renders independently */}
</Suspense>
</div>
);
}

// Stats.jsx -- Async Server Component
async function Stats() {
const stats = await getStats(); // Direct server-side fetch
return (
<div>
<span>Revenue: {stats.revenue}</span>
<span>Users: {stats.users}</span>
</div>
);
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Pattern 4 code example has two correctness issues: missing imports and a missing export.

  1. Dashboard renders <Stats />, <RevenueChart />, <RecentOrders />, <StatsSkeleton />, <ChartSkeleton />, and <TableSkeleton /> but imports none of them. Readers copying the snippet will get "X is not defined" errors.
  2. Stats is defined as async function Stats() (unexported named function), so Dashboard's (implied) import Stats from './Stats' would fail at runtime — it must be a default or named export.
📝 Proposed fix
 // Dashboard.jsx -- Server Component
 import { Suspense } from 'react';
+import Stats from './Stats';
+import RevenueChart from './RevenueChart';
+import RecentOrders from './RecentOrders';
+import { StatsSkeleton, ChartSkeleton, TableSkeleton } from './Skeletons';
 
 export default function Dashboard() {
 // Stats.jsx -- Async Server Component
-async function Stats() {
+export default async function Stats() {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 276 - 307, The snippet
is missing imports and the Stats component isn't exported; update Dashboard to
import Stats, RevenueChart, RecentOrders, StatsSkeleton, ChartSkeleton, and
TableSkeleton (so Dashboard can render those symbols), and make the Stats
component an exported module (export default Stats or export a named Stats and
ensure Dashboard imports it accordingly) so the runtime import resolves; adjust
import/export consistently between Dashboard and Stats.

Comment on lines +336 to +352
```jsx
// Comments.jsx -- Client Component
'use client';

import { use } from 'react';

export default function Comments({ commentsPromise }) {
const comments = use(commentsPromise); // Resolves the promise
return (
<ul>
{comments.map(c => <li key={c.id}>{c.text}</li>)}
</ul>
);
}
```

**Benefits:** The post renders immediately. Comments stream in when ready. The promise starts on the server (close to the data source) but resolves on the client.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

React 18 vs React 19 use() hook promise support stability

💡 Result:

React 18 vs React 19 for use() + Promise support + stability

React 18 (incl. 18.2 / 18.3)

  • No use() API for consuming Promises/Context in render (it’s a React 19 feature).
  • React published React 18.3 (same behavior as 18.2) mainly to surface warnings for React 19 deprecations/breaking changes, and recommends upgrading to 18.3 first before moving to 19. [3]

React 19

  • Stable release: React 19 is stable as of December 5, 2024 (official React team post; date updated to reflect stable release). [1]
  • What use(promise) does: use(Promise) lets a component suspend during render until the Promise resolves, integrating with <Suspense> fallbacks and Error Boundaries for rejected Promises. [2]
  • Key limitation (important for “promise support stability”):
    • use does not support Promises created in render (React warns about “uncached promise”). You’re expected to pass a cached/stable Promise (commonly: created outside the client render path—often from a Server Component or a Suspense-aware library/framework). [1][2]
    • React docs explicitly note that Promises created in Client Components are recreated on every render, while Promises passed from Server Components to Client Components are stable across re-renders. [2]

Practical takeaway

  • If you want stable, supported use(Promise) behavior, React 19 is the correct baseline—but you must structure code so the Promise is stable/cached, not created inline during client render. [1][2]
  • If you’re still on React 18, go to 18.3 first to catch upgrade warnings, then move to 19. [3]

Sources: React v19 release post [1], official use API reference [2], React 19 upgrade guide (incl. React 18.3 recommendation) [3].


Add a React 19 version requirement callout for the use(promise) pattern.

Pattern 5 uses use(promise) to resolve a server-sent Promise on the client—this API is only available in React 19. React 18 does not have a use() hook and readers using React 18 will encounter errors if they copy this pattern.

Add a version note before the code block:

+> **Requires React 19+.** The `use(promise)` pattern for server-to-client promise handoff is not available in React 18.
+
 ```jsx
 // Comments.jsx -- Client Component
 'use client';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 336 - 352, Add a React
version note before the Comments component showing that use(commentsPromise)
requires React 19; update the docs around the Comments component (the Comments
function and its use(commentsPromise) call) to include a short callout that this
pattern depends on React 19 (React 18 lacks use()) so readers on React 18 will
encounter errors. Keep the callout concise and place it immediately above the
code block that begins with "'use client';" and the Comments component.

- All three trees now show ProductPage (not Content)
- Phase 2: SearchBar + ProductPage are client (~40% bundle)
- Phase 3: ProductPage converts to server, only AddCart is client (~15%)
- Phase 2 bundle bar is visibly larger than Phase 3
- Orange-to-cyan stroke animation on ProductPage in Phase 3 highlights conversion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
'use client';

import { useRouter, startTransition } from 'next/navigation';
import { ErrorBoundary } from 'react-error-boundary';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: startTransition is not exported from next/navigation

startTransition is a React API and must be imported from react, not next/navigation. This import will fail at runtime (the value will be undefined, and calling undefined() throws a TypeError when the user clicks "Retry").

Suggested change
import { ErrorBoundary } from 'react-error-boundary';
import { useRouter } from 'next/navigation';
import { startTransition } from 'react';
import { ErrorBoundary } from 'react-error-boundary';


```jsx
// Post.jsx -- Server Component
import { preloadComments, getComments } from '../lib/data';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing import: getPost is used but not imported

getPost(postId) is called on the next line, but only preloadComments and getComments are imported from '../lib/data'. Readers following along will get a ReferenceError.

Suggested change
import { preloadComments, getComments } from '../lib/data';
import { preloadComments, getComments, getPost } from '../lib/data';


export default function UserProvider({ children, userPromise }) {
return <UserContext value={userPromise}>{children}</UserContext>;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context API syntax inconsistency with the guide's own React 19 requirement

Pattern 1 (ThemeProvider, line 46) uses <ThemeContext.Provider value={...}> (the pre-React 19 syntax), while Pattern 4 here uses <UserContext value={userPromise}> (the React 19 "value as prop" shorthand).

Since migrating-to-rsc.md lists React 19+ as a prerequisite, both syntaxes are technically valid, but mixing them in the same guide file without explanation will confuse readers. Either standardise on one form, or add a note explaining that React 19 allows the shorter form.

import AuthProvider from './auth-provider';

export default async function Layout({ children }) {
const cookieStore = await cookies();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These examples are Next.js-specific, not React on Rails patterns

cookies() from next/headers, verifyToken() from middleware, revalidatePath(), await cookies() — these are all Next.js App Router APIs that don't exist in a React on Rails project. Readers following this guide for a React on Rails migration will hit immediate errors trying to replicate these patterns.

Consider either:

  1. Adding a clear callout box like > **Note:** The following examples use Next.js App Router APIs. For React on Rails, …
  2. Replacing with framework-agnostic equivalents (e.g., reading cookies from a plain HTTP library, or pointing to the React on Rails Pro auth documentation)

// actions.js
'use server';

import { revalidatePath } from 'next/cache';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next.js-specific API without framing

revalidatePath is a Next.js API. Likewise, the import { revalidatePath } from 'next/cache' line a few lines above will fail in any non-Next.js RSC environment (including React on Rails Pro). The same applies to HydrationBoundary from @tanstack/react-query and dehydrate — these work in Next.js but the dehydration/hydration approach may differ in other frameworks.

A short callout noting "This example targets Next.js App Router. For React on Rails, see the Pro RSC docs" would make this much clearer for the target audience.

// UserProfile.jsx -- Server Component (no directive)

export default async function UserProfile({ userId }) {
const user = await fetch(`https://api.example.com/users/${userId}`)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fetch to absolute URL silently fails in many server environments

The "Before" example fetches from a relative URL (/api/users/${userId}), while the "After" example switches to an absolute URL (https://api.example.com/...). The change in base URL is intentional and correct (Node.js fetch requires absolute URLs), but it's not called out in the "What changed" list below. A reader may replicate the relative URL from their existing code and get a silent runtime failure.

Worth adding a bullet: "- Used an absolute URL (required in server-side fetch)"

| **CSS Modules** | Built into most frameworks. No runtime overhead. |
| **Panda CSS** | Zero-runtime, type-safe, created by Chakra UI team. RSC-compatible by design. |
| **Pigment CSS** | Created by MUI. Compiles to CSS Modules. Still unstable as of early 2026. |
| **vanilla-extract** | TypeScript-native. Known issue: `.css.ts` imports in RSC may need `swc-plugin-vanilla-extract` workaround. |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Time-sensitive claim that will become stale

"Still unstable as of early 2026" will be incorrect once Pigment CSS reaches stability. Consider replacing with a link to the library's release notes or stability page so readers always get current status rather than a snapshot that grows outdated.

</time>
```

This suppresses the warning but does not fix the mismatch -- use it only for non-critical content.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suppressHydrationWarning only suppresses one level deep

The text says "This suppresses the warning but does not fix the mismatch", which is correct, but is missing an important constraint: suppressHydrationWarning only suppresses warnings on the element itself, not on its children. Readers using it on a wrapper element expecting it to silence nested mismatches will still see errors. Worth adding that note here.

}
```

### Runtime Validation for Server Actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security: Server Actions are public endpoints note deserves higher prominence

This is the most security-critical point in the entire guide series. A TypeScript type signature on a Server Action provides zero protection at runtime — any HTTP client can POST to it with arbitrary data. This single paragraph buries a critical security rule that deserves its own dedicated callout block (e.g., a > ⚠️ Security blockquote) and should also be mentioned in rsc-data-fetching.md when Server Actions are introduced there.

For third-party packages, Next.js can automatically transform barrel imports into direct imports:

```js
// next.config.js
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optimizePackageImports is a Next.js-specific config key.

This setting only works with Next.js. The series introduction notes it covers "React-side challenges" and links to React on Rails Pro for framework config, but this section drops into Next.js-specific next.config.js config without any qualifier.

React on Rails users using Webpack/Rspack would need a different approach (e.g., sideEffects: false in package.json, or explicit direct imports). Worth adding a note like:

Note: optimizePackageImports is a Next.js feature. For React on Rails (Webpack/Rspack), avoid barrel-file imports by using direct module paths, or configure your bundler's tree-shaking for the package.

@claude
Copy link

claude bot commented Feb 24, 2026

Review: RSC Migration Guide Series

This is a well-researched and comprehensive documentation series. The structure is logical, the before/after code examples are genuinely useful, and the depth on non-obvious topics (chunk contamination in Mistake 3, use() promise stability, React.cache() scoping) is above average. A few issues need attention before merge:


Bugs in Code Examples

1. Wrong import source for startTransition (rsc-troubleshooting.md:219)

startTransition is exported from react, not next/navigation. The current import will cause a build error. See inline comment.

2. addToCart called but never imported (rsc-component-patterns.md:141)

The AddToCartButton example calls addToCart(productId, quantity) without importing or defining it. Readers copying the example will get a ReferenceError. See inline comment.


API Inconsistency

3. Mixed React Context syntax in the same guide (rsc-context-and-state.md)

Pattern 1 uses <ThemeContext.Provider value={...}> (compatible with React 18+), while Pattern 4 uses <UserContext value={...}> (React 19 shorthand only). Since the prerequisites list React 19+ as a requirement, the shorthand is valid — but the inconsistency within the same document is confusing. Either standardize on one style throughout, or add a note on the first occurrence of the shorthand explaining it's React 19+.


Accuracy

4. RSC payload description is imprecise (rsc-troubleshooting.md:312)

Describing it as content "sent twice" misrepresents the format. The RSC payload (React Flight wire format) is structurally different from the initial HTML — it's a compact serialized component tree for client-side hydration, not a duplicate of the HTML. See inline comment for suggested rewording.


Framework Scope

5. Next.js-specific APIs used throughout without qualification

Numerous examples rely on APIs that are Next.js-only:

  • cookies() / revalidatePath() / revalidateTag() from next/headers and next/cache
  • optimizePackageImports in next.config.js (see inline comment on rsc-third-party-libs.md:259)
  • useRouter from next/navigation
  • next-intl, next-themes
  • params as a Promise (Next.js 15 change)

The intro correctly scopes to React-side concerns and links out to React on Rails Pro for framework config. But sections like the Server Actions mutation example and the barrel file solution silently assume Next.js. React on Rails users on Webpack/Rspack need different guidance for some of these. A callout at the top of affected sections (similar to what the intro does) would prevent confusion.


Security

6. Server Action mutation example has no input validation (rsc-data-fetching.md:589)

The createComment Server Action stores raw formData values without validation. Server Actions are public HTTP endpoints — anyone can POST arbitrary values to them. The rsc-troubleshooting.md guide does cover Zod-based validation, but there's no cross-reference from this example. At minimum, add a note here pointing to that section. See inline comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (8)
docs/migrating/rsc-component-patterns.md (4)

108-122: ⚠️ Potential issue | 🟡 Minor

Unresolved: ProductPage still missing export default in the "After" snippet.

The "Before" snippet at Line 73 has export default function ProductPage, but the "After" block at Line 110 drops the export. Inconsistency persists from the previous review round.

📝 Proposed fix
-async function ProductPage({ productId }) {
+export default async function ProductPage({ productId }) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 108 - 122, The
ProductPage "After" example is missing the export; update the exported server
component declaration so it matches the "Before" snippet by making the
ProductPage function the default export (i.e., restore the export default on the
ProductPage symbol), ensuring the documented "After" snippet shows export
default ProductPage to keep examples consistent.

40-58: ⚠️ Potential issue | 🟡 Minor

Unresolved: fenced code block still missing a language specifier (MD040).

The tree diagram block at Line 40 has no language identifier, which triggers a markdownlint MD040 warning. This was flagged in a previous review round and is still unaddressed.

🔧 Proposed fix
-```
+```text
 Before:
 App ('use client')           <-- Everything is client-side
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 40 - 58, The fenced
code block showing the component tree (the block that begins with the triple
backticks and lines like "App ('use client')           <-- Everything is
client-side") lacks a language specifier and triggers MD040; fix it by adding a
language identifier (e.g., "text") right after the opening ``` so the block
becomes ```text, leaving the tree contents unchanged, ensuring markdownlint
stops warning about the missing language.

336-352: ⚠️ Potential issue | 🟡 Minor

Unresolved: no React 19 version callout before the use(commentsPromise) example.

use(promise) is a React 19-only API. Readers on React 18 will hit a runtime error. A version note was requested in the prior review but has not been added.

📝 Proposed fix
+> **Requires React 19+.** The `use(promise)` pattern for server-to-client promise handoff is not available in React 18.
+
 ```jsx
 // Comments.jsx -- Client Component
 'use client';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 336 - 352, The example
uses the React 19-only API use(commentsPromise) inside the Comments component,
which will error on React 18; add a clear React version callout immediately
before the Comments.jsx example stating that use(promise) requires React 19+ and
will not work in React 18, and suggest the React 18 alternative (e.g., useEffect
+ useState to resolve comments or using server-side Suspense patterns) so
readers know how to adapt the Comments component (look for the Comments function
and the use(commentsPromise) call to insert the note).

276-307: ⚠️ Potential issue | 🟡 Minor

Unresolved: Pattern 4 still missing imports in Dashboard and export default on Stats.

Dashboard renders <Stats />, <RevenueChart />, <RecentOrders />, <StatsSkeleton />, <ChartSkeleton />, and <TableSkeleton /> without importing any of them. Stats (Line 298) is also not exported. Both issues were flagged in the prior review and remain.

📝 Proposed fix
 // Dashboard.jsx -- Server Component
 import { Suspense } from 'react';
+import Stats from './Stats';
+import RevenueChart from './RevenueChart';
+import RecentOrders from './RecentOrders';
+import { StatsSkeleton, ChartSkeleton, TableSkeleton } from './Skeletons';
-async function Stats() {
+export default async function Stats() {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 276 - 307, Dashboard
is missing imports for the child components it renders and Stats is not
exported; add import statements for Stats, RevenueChart, RecentOrders,
StatsSkeleton, ChartSkeleton, and TableSkeleton at the top of the file and
export the Stats component (e.g., export default or named export consistent with
Dashboard's usage) so that <Dashboard /> can render <Stats />, <RevenueChart />,
and <RecentOrders /> and their skeletons without unresolved references.
docs/migrating/rsc-troubleshooting.md (4)

216-247: ⚠️ Potential issue | 🔴 Critical

Unresolved critical: startTransition is still incorrectly imported from 'next/navigation'.

startTransition is a React core API and is not exported by next/navigation. This import will cause a runtime error for anyone copying the snippet. This was flagged as critical in the previous review.

🐛 Proposed fix
-import { useRouter, startTransition } from 'next/navigation';
+import { useRouter } from 'next/navigation';
+import { startTransition } from 'react';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-troubleshooting.md` around lines 216 - 247, The snippet
incorrectly imports startTransition from 'next/navigation' causing runtime
errors; update the imports so startTransition is imported from React and
useRouter remains from 'next/navigation' (i.e., remove startTransition from the
next/navigation import and add import { startTransition } from 'react'), then
ensure the ErrorFallback component (and its retry function which calls
startTransition and router.refresh) continues to use those corrected imports so
retry and PageErrorBoundary work as intended.

87-93: ⚠️ Potential issue | 🟡 Minor

Unresolved: import-chain diagram block still missing a language specifier (MD040).

The fenced code block at Line 87 has no language identifier. This was flagged in the previous review and remains.

🔧 Proposed fix
-```
+```text
 ClientComponent.jsx ('use client')
 ├── imports utils.js          → becomes client code
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-troubleshooting.md` around lines 87 - 93, The fenced code
block showing the import-chain diagram (starting with "ClientComponent.jsx ('use
client')") is missing a language identifier; update the block delimiter from ```
to ```text so the block is explicitly marked as plain text (e.g., replace the
opening ``` with ```text for the diagram block containing "ClientComponent.jsx
('use client')" and its tree lines) to satisfy MD040.

322-338: ⚠️ Potential issue | 🟡 Minor

Unresolved: testing-strategy diagram block still missing a language specifier (MD040).

The fenced code block at Line 322 has no language identifier. This was flagged in the previous review and remains.

🔧 Proposed fix
-```
+```text
 Unit Tests (Vitest/Jest)
 ├── Client Components -- full support with hooks mocking
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-troubleshooting.md` around lines 322 - 338, The fenced
diagram block starting with "Unit Tests (Vitest/Jest)" lacks a language
specifier; update the opening fence from ``` to ```text (or another appropriate
language like ```md) so the block becomes a fenced code block with a language
identifier (e.g., change the fence that precedes "Unit Tests (Vitest/Jest)" to
```text).

465-470: ⚠️ Potential issue | 🟡 Minor

Unresolved: fetch(process.env.DATABASE_URL) is still a misleading example.

DATABASE_URL is a database connection string (e.g., postgresql://user:pass@host/db), not an HTTP URL. fetch() will throw on non-HTTP schemes, so the // Works comment is incorrect. This was flagged in the previous review.

📝 Proposed fix
 async function DBComponent() {
-  const data = await fetch(process.env.DATABASE_URL);  // Works
-  const secret = process.env.API_SECRET;               // Works
+  // Full access to all env vars — DB credentials, secrets, internal URLs
+  const db = new DatabaseClient(process.env.DATABASE_URL);  // Works
+  const secret = process.env.API_SECRET;                    // Works
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-troubleshooting.md` around lines 465 - 470, The example
DBComponent is misleading because it calls fetch(process.env.DATABASE_URL) even
though DATABASE_URL is a DB connection string; update the example to be correct
by either (a) replacing fetch(process.env.DATABASE_URL) with an HTTP fetch using
a proper env var (e.g., process.env.API_URL) and keep the "// Works" comment, or
(b) illustrate connecting to a database using a DB client (e.g., show creating a
client and connecting with process.env.DATABASE_URL) and change the comment
accordingly; locate the DBComponent function in the snippet and modify the call
and comment so the code matches the intended resource type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/migrating/rsc-component-patterns.md`:
- Line 368: The table cell text contains "markdown" in lowercase; update that
cell to use the proper noun "Markdown" (e.g., change "Heavy dependencies
(markdown parsers, formatters)" to "Heavy dependencies (Markdown parsers,
formatters)") so the term is capitalized correctly in the row that currently
reads "Heavy dependencies (markdown parsers, formatters) | Server | Dependencies
stay off client bundle".

---

Duplicate comments:
In `@docs/migrating/rsc-component-patterns.md`:
- Around line 108-122: The ProductPage "After" example is missing the export;
update the exported server component declaration so it matches the "Before"
snippet by making the ProductPage function the default export (i.e., restore the
export default on the ProductPage symbol), ensuring the documented "After"
snippet shows export default ProductPage to keep examples consistent.
- Around line 40-58: The fenced code block showing the component tree (the block
that begins with the triple backticks and lines like "App ('use client')        
<-- Everything is client-side") lacks a language specifier and triggers MD040;
fix it by adding a language identifier (e.g., "text") right after the opening
``` so the block becomes ```text, leaving the tree contents unchanged, ensuring
markdownlint stops warning about the missing language.
- Around line 336-352: The example uses the React 19-only API
use(commentsPromise) inside the Comments component, which will error on React
18; add a clear React version callout immediately before the Comments.jsx
example stating that use(promise) requires React 19+ and will not work in React
18, and suggest the React 18 alternative (e.g., useEffect + useState to resolve
comments or using server-side Suspense patterns) so readers know how to adapt
the Comments component (look for the Comments function and the
use(commentsPromise) call to insert the note).
- Around line 276-307: Dashboard is missing imports for the child components it
renders and Stats is not exported; add import statements for Stats,
RevenueChart, RecentOrders, StatsSkeleton, ChartSkeleton, and TableSkeleton at
the top of the file and export the Stats component (e.g., export default or
named export consistent with Dashboard's usage) so that <Dashboard /> can render
<Stats />, <RevenueChart />, and <RecentOrders /> and their skeletons without
unresolved references.

In `@docs/migrating/rsc-troubleshooting.md`:
- Around line 216-247: The snippet incorrectly imports startTransition from
'next/navigation' causing runtime errors; update the imports so startTransition
is imported from React and useRouter remains from 'next/navigation' (i.e.,
remove startTransition from the next/navigation import and add import {
startTransition } from 'react'), then ensure the ErrorFallback component (and
its retry function which calls startTransition and router.refresh) continues to
use those corrected imports so retry and PageErrorBoundary work as intended.
- Around line 87-93: The fenced code block showing the import-chain diagram
(starting with "ClientComponent.jsx ('use client')") is missing a language
identifier; update the block delimiter from ``` to ```text so the block is
explicitly marked as plain text (e.g., replace the opening ``` with ```text for
the diagram block containing "ClientComponent.jsx ('use client')" and its tree
lines) to satisfy MD040.
- Around line 322-338: The fenced diagram block starting with "Unit Tests
(Vitest/Jest)" lacks a language specifier; update the opening fence from ``` to
```text (or another appropriate language like ```md) so the block becomes a
fenced code block with a language identifier (e.g., change the fence that
precedes "Unit Tests (Vitest/Jest)" to ```text).
- Around line 465-470: The example DBComponent is misleading because it calls
fetch(process.env.DATABASE_URL) even though DATABASE_URL is a DB connection
string; update the example to be correct by either (a) replacing
fetch(process.env.DATABASE_URL) with an HTTP fetch using a proper env var (e.g.,
process.env.API_URL) and keep the "// Works" comment, or (b) illustrate
connecting to a database using a DB client (e.g., show creating a client and
connecting with process.env.DATABASE_URL) and change the comment accordingly;
locate the DBComponent function in the snippet and modify the call and comment
so the code matches the intended resource type.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 743266e and e2b9bb8.

📒 Files selected for processing (2)
  • docs/migrating/rsc-component-patterns.md
  • docs/migrating/rsc-troubleshooting.md

| Data fetching (database, API) | Server | Direct backend access, no bundle cost |
| Rendering static/display-only content | Server | No JavaScript shipped |
| Using server-only secrets (API keys) | Server | Never exposed to client |
| Heavy dependencies (markdown parsers, formatters) | Server | Dependencies stay off client bundle |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor: capitalize "Markdown" as a proper noun.

"markdown" in the table cell should be "Markdown" — it is a proper noun.

📝 Fix
-| Heavy dependencies (markdown parsers, formatters) | Server | Dependencies stay off client bundle |
+| Heavy dependencies (Markdown parsers, formatters) | Server | Dependencies stay off client bundle |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| Heavy dependencies (markdown parsers, formatters) | Server | Dependencies stay off client bundle |
| Heavy dependencies (Markdown parsers, formatters) | Server | Dependencies stay off client bundle |
🧰 Tools
🪛 LanguageTool

[uncategorized] ~368-~368: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...posed to client | | Heavy dependencies (markdown parsers, formatters) | Server | Depende...

(MARKDOWN_NNP)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` at line 368, The table cell text
contains "markdown" in lowercase; update that cell to use the proper noun
"Markdown" (e.g., change "Heavy dependencies (markdown parsers, formatters)" to
"Heavy dependencies (Markdown parsers, formatters)") so the term is capitalized
correctly in the row that currently reads "Heavy dependencies (markdown parsers,
formatters) | Server | Dependencies stay off client bundle".

The chunk contamination problem depends on webpack's chunk group iteration
order, which is deterministic per build but unpredictable and can change
when components are added/removed. Revised the documentation to:

- Lead with how to detect the problem (inspect react-client-manifest.json,
  check Network tab, use webpack-bundle-analyzer)
- Explain the mechanism honestly (last-write-wins, iteration-order-dependent)
- Present the wrapper as a fix when detected + preventive measure
- Remove prescriptive "BAD/GOOD" framing in favor of diagnostic guidance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (8)
docs/migrating/rsc-component-patterns.md (5)

368-368: ⚠️ Potential issue | 🟡 Minor

Capitalize “Markdown.”

Proper noun in doc tables.

📝 Proposed fix
-| Heavy dependencies (markdown parsers, formatters) | Server | Dependencies stay off client bundle |
+| Heavy dependencies (Markdown parsers, formatters) | Server | Dependencies stay off client bundle |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` at line 368, Update the table cell
text to capitalize the proper noun "Markdown" — locate the row containing "Heavy
dependencies (markdown parsers, formatters) | Server | Dependencies stay off
client bundle" and change "markdown" to "Markdown" so it reads "Heavy
dependencies (Markdown parsers, formatters) | Server | Dependencies stay off
client bundle".

40-58: ⚠️ Potential issue | 🟡 Minor

Add a language identifier to the tree diagram fence (MD040).

This fenced block still lacks a language specifier.

🔧 Proposed fix
-```
+```text
 Before:
 App ('use client')           <-- Everything is client-side
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 40 - 58, Update the
fenced code block that contains the component tree diagram so it includes a
language identifier (e.g., ```text) on the opening fence; locate the block
showing "Before:" / "After:" with App, Layout, Header, Logo, SearchBar, Content,
Footer and change the opening ``` to ```text to satisfy the MD040 lint rule.

336-344: ⚠️ Potential issue | 🟡 Minor

Add a React 19+ requirement callout for use(promise).

use() in client components is React 19+; without a version note, React 18 users will hit errors.

Does React 19 introduce the `use(promise)` API and is it unavailable in React 18?
📝 Proposed fix
+> **Requires React 19+.** The `use(promise)` pattern is not available in React 18.
+
 ```jsx
 // Comments.jsx -- Client Component
 'use client';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 336 - 344, Add a clear
callout above the Comments.jsx example stating that use(promise) in client
components requires React 19 or later and is not available in React 18; update
the text near the Comments component and the use(commentsPromise) example to
explicitly mention the React 19+ requirement and that React 18 users will see
errors if they try to use this API.

108-122: ⚠️ Potential issue | 🟡 Minor

Restore the default export in the “After” ProductPage snippet.

The “Before” snippet is a default export; the “After” snippet should match to avoid misleading readers.

📝 Proposed fix
-async function ProductPage({ productId }) {
+export default async function ProductPage({ productId }) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 108 - 122, The "After"
snippet for the ProductPage server component is missing the default export and
should match the "Before" example; update the ProductPage definition by adding a
default export for the ProductPage component (export default ProductPage) so the
component is exported the same way as the "Before" snippet and readers aren't
misled—look for the ProductPage function/component declaration in the snippet
and append the default export statement.

276-307: ⚠️ Potential issue | 🟡 Minor

Pattern 4 example is missing imports and an export for Stats.

Readers copying this will hit undefined component errors and a failed import of Stats.

📝 Proposed fix
 // Dashboard.jsx -- Server Component
 import { Suspense } from 'react';
+import Stats from './Stats';
+import RevenueChart from './RevenueChart';
+import RecentOrders from './RecentOrders';
+import { StatsSkeleton, ChartSkeleton, TableSkeleton } from './Skeletons';
 // Stats.jsx -- Async Server Component
-async function Stats() {
+export default async function Stats() {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-component-patterns.md` around lines 276 - 307, The example
is missing an export for the Stats component and necessary imports used by both
files; export the async Stats function (e.g., export default async function
Stats()) and add imports for getStats in Stats.jsx, and import the skeleton
components and child components (Stats, RevenueChart, RecentOrders) into
Dashboard.jsx (ensure import names/paths match the example filenames), so the
components resolve without undefined errors.
docs/migrating/rsc-troubleshooting.md (3)

219-230: ⚠️ Potential issue | 🔴 Critical

Import startTransition from react, not next/navigation.

startTransition is a React API; next/navigation does not export it. Also consider noting this snippet is Next.js-specific.

Does `next/navigation` export `startTransition`, or should it be imported from `react`?
📝 Proposed fix
-import { useRouter, startTransition } from 'next/navigation';
+import { useRouter } from 'next/navigation';
+import { startTransition } from 'react';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-troubleshooting.md` around lines 219 - 230, The snippet
imports startTransition from next/navigation but startTransition is a React API;
update the import so startTransition is imported from 'react' (leave useRouter
from 'next/navigation'), and ensure the ErrorFallback component and its retry
function still call startTransition(() => { router.refresh();
resetErrorBoundary(); }) unchanged; also add a short comment noting this snippet
is Next.js-specific if docs require clarification.

87-93: ⚠️ Potential issue | 🟡 Minor

Add language identifiers to both diagram fences (MD040).

Both fenced blocks are missing a language specifier.

🔧 Proposed fix
-```
+```text
 ClientComponent.jsx ('use client')
 ├── imports utils.js          → becomes client code
-```
+```text
 Unit Tests (Vitest/Jest)
 ├── Client Components -- full support with hooks mocking

Also applies to: 322-338

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-troubleshooting.md` around lines 87 - 93, The two fenced
diagram blocks (the one starting with "ClientComponent.jsx ('use client')" and
the one starting "Unit Tests (Vitest/Jest)") lack language specifiers; update
both opening triple-backtick fences to include a language (e.g., ```text) so
markdown lint rule MD040 is satisfied, and apply the same fix to the other
affected fenced block around lines 322-338 in the same file.

465-470: ⚠️ Potential issue | 🟡 Minor

Avoid fetch(process.env.DATABASE_URL) — it’s not an HTTP URL.

DATABASE_URL is typically a DB connection string, so Fetch will fail and the comment is misleading.

📝 Proposed fix
 async function DBComponent() {
-  const data = await fetch(process.env.DATABASE_URL);  // Works
-  const secret = process.env.API_SECRET;               // Works
+  const db = new DatabaseClient(process.env.DATABASE_URL); // Works
+  const secret = process.env.API_SECRET;                  // Works
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/migrating/rsc-troubleshooting.md` around lines 465 - 470, The example in
DBComponent incorrectly uses fetch(process.env.DATABASE_URL) where DATABASE_URL
is a database connection string, not an HTTP URL; update the example to either
call fetch with a proper HTTP endpoint environment variable (e.g.,
process.env.API_URL) or show using a database client to connect with the
connection string (e.g., reference DBComponent and replace
fetch(process.env.DATABASE_URL) with an appropriate DB client call using
process.env.DATABASE_URL), and update the inline comment to reflect the correct
usage (e.g., "fetch an HTTP API" vs "connect to DB").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docs/migrating/rsc-component-patterns.md`:
- Line 368: Update the table cell text to capitalize the proper noun "Markdown"
— locate the row containing "Heavy dependencies (markdown parsers, formatters) |
Server | Dependencies stay off client bundle" and change "markdown" to
"Markdown" so it reads "Heavy dependencies (Markdown parsers, formatters) |
Server | Dependencies stay off client bundle".
- Around line 40-58: Update the fenced code block that contains the component
tree diagram so it includes a language identifier (e.g., ```text) on the opening
fence; locate the block showing "Before:" / "After:" with App, Layout, Header,
Logo, SearchBar, Content, Footer and change the opening ``` to ```text to
satisfy the MD040 lint rule.
- Around line 336-344: Add a clear callout above the Comments.jsx example
stating that use(promise) in client components requires React 19 or later and is
not available in React 18; update the text near the Comments component and the
use(commentsPromise) example to explicitly mention the React 19+ requirement and
that React 18 users will see errors if they try to use this API.
- Around line 108-122: The "After" snippet for the ProductPage server component
is missing the default export and should match the "Before" example; update the
ProductPage definition by adding a default export for the ProductPage component
(export default ProductPage) so the component is exported the same way as the
"Before" snippet and readers aren't misled—look for the ProductPage
function/component declaration in the snippet and append the default export
statement.
- Around line 276-307: The example is missing an export for the Stats component
and necessary imports used by both files; export the async Stats function (e.g.,
export default async function Stats()) and add imports for getStats in
Stats.jsx, and import the skeleton components and child components (Stats,
RevenueChart, RecentOrders) into Dashboard.jsx (ensure import names/paths match
the example filenames), so the components resolve without undefined errors.

In `@docs/migrating/rsc-troubleshooting.md`:
- Around line 219-230: The snippet imports startTransition from next/navigation
but startTransition is a React API; update the import so startTransition is
imported from 'react' (leave useRouter from 'next/navigation'), and ensure the
ErrorFallback component and its retry function still call startTransition(() =>
{ router.refresh(); resetErrorBoundary(); }) unchanged; also add a short comment
noting this snippet is Next.js-specific if docs require clarification.
- Around line 87-93: The two fenced diagram blocks (the one starting with
"ClientComponent.jsx ('use client')" and the one starting "Unit Tests
(Vitest/Jest)") lack language specifiers; update both opening triple-backtick
fences to include a language (e.g., ```text) so markdown lint rule MD040 is
satisfied, and apply the same fix to the other affected fenced block around
lines 322-338 in the same file.
- Around line 465-470: The example in DBComponent incorrectly uses
fetch(process.env.DATABASE_URL) where DATABASE_URL is a database connection
string, not an HTTP URL; update the example to either call fetch with a proper
HTTP endpoint environment variable (e.g., process.env.API_URL) or show using a
database client to connect with the connection string (e.g., reference
DBComponent and replace fetch(process.env.DATABASE_URL) with an appropriate DB
client call using process.env.DATABASE_URL), and update the inline comment to
reflect the correct usage (e.g., "fetch an HTTP API" vs "connect to DB").

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2b9bb8 and 57cdea9.

📒 Files selected for processing (2)
  • docs/migrating/rsc-component-patterns.md
  • docs/migrating/rsc-troubleshooting.md

@claude
Copy link

claude bot commented Feb 24, 2026

Review: RSC Migration Guide Series

This is a solid, well-structured set of articles that covers real-world RSC migration challenges with accurate patterns. The content is genuinely useful — the donut pattern, chunk contamination walkthrough, use() hook pitfalls, and state library guidance are all well-explained. A few issues below, ranging from code bugs to a structural concern.


Structural concern: Next.js-specific APIs mixed into framework-agnostic content

The intro (migrating-to-rsc.md) correctly frames this series as covering "React-side challenges" rather than framework configuration. However, several articles include Next.js-specific APIs without making that clear:

  • cookies() from next/headers — Auth and Theme provider examples in rsc-context-and-state.md
  • revalidatePath() from next/cache — Server Actions in rsc-data-fetching.md
  • useRouter from next/navigation — Error boundary retry in rsc-troubleshooting.md
  • optimizePackageImports — Next.js config option in rsc-third-party-libs.md
  • NextIntlClientProvider, getMessages, getLocale from next-intl/server — i18n example

React on Rails users won't have these APIs available. Each article should include a note like:

Note: Code examples using next/headers, next/cache, or next/navigation are Next.js-specific. For React on Rails, adapt these to your server-side setup (e.g., read cookies from the Rails request, use turbo-stream for cache invalidation).


Code bugs in examples

  1. addToCart is undefined in AddToCartButton.jsx (rsc-component-patterns.md): The extracted client component calls addToCart(productId, quantity) but never imports or defines it. Readers copying this example will hit a ReferenceError.

  2. startTransition wrong import source (rsc-troubleshooting.md): The error boundary retry example imports startTransition from next/navigation. It should come from react. This is the standard import regardless of framework.


Security gap in Server Action example

The createComment function in rsc-data-fetching.md takes raw formData and inserts directly into the database with no validation:

const content = formData.get('content');
const postId = formData.get('postId');
await db.comments.create({ data: { content, postId } });

Server Actions are public HTTP endpoints. The Zod validation pattern is correctly shown later in rsc-troubleshooting.md (TypeScript section), but this earlier example sets a bad precedent. It should either add a validation step or include a comment like // Always validate formData in production — see the TypeScript section for a Zod example.


Minor: React 19 Context syntax used without noting it

rsc-context-and-state.md uses <UserContext value={userPromise}>{children}</UserContext> (no .Provider) in the "Sharing Data Between Server and Client Components" example. This is valid React 19 syntax, but React 18 projects would need <UserContext.Provider value={...}>. The prerequisites section lists "React 19+" so this is technically covered, but a brief inline note would help readers who haven't upgraded yet.


Overall: the content is thorough and well-sourced. The main work needed is (1) adding framework-specific disclaimers consistently, (2) fixing the two code bugs, and (3) addressing the Server Action validation gap.

value={quantity}
onChange={(e) => setQuantity(Number(e.target.value))}
/>
<button onClick={() => addToCart(productId, quantity)}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addToCart is used here but is never imported or defined anywhere in AddToCartButton.jsx. Readers who copy this example will get a ReferenceError at runtime. This needs either an import statement (e.g., import { addToCart } from '../lib/cart';) or an inline definition.

Suggested change
<button onClick={() => addToCart(productId, quantity)}>
<button onClick={() => addToCart(productId, quantity)}>

Suggested fix at the top of the file:

'use client';

import { useState } from 'react';
import { addToCart } from '../lib/cart'; // <-- add this import

export default function AddToCartButton({ productId }) {

export const UserContext = createContext(null);

export default function UserProvider({ children, userPromise }) {
return <UserContext value={userPromise}>{children}</UserContext>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <UserContext value={...}> syntax (without .Provider) was introduced in React 19. React 18 users would need <UserContext.Provider value={userPromise}>. The prerequisites section mentions React 19+, but a brief note here would prevent confusion for anyone who hasn't fully internalized that requirement:

Suggested change
return <UserContext value={userPromise}>{children}</UserContext>;
export default function UserProvider({ children, userPromise }) {
// React 19+ syntax: <Context value={...}> replaces <Context.Provider value={...}>
return <UserContext value={userPromise}>{children}</UserContext>;
}

import { revalidatePath } from 'next/cache';

export async function createComment(formData) {
const content = formData.get('content');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security gap: Server Actions are public HTTP endpoints reachable by any client. formData.get('content') and formData.get('postId') are unvalidated before hitting the database. postId in particular — if it's not verified to belong to the current user, this is an IDOR vulnerability.

The Zod pattern shown later in rsc-troubleshooting.md is the right approach. Either add a validation step here or at minimum add a comment redirecting readers to that section:

Suggested change
const content = formData.get('content');
export async function createComment(formData) {
// Server Actions are public endpoints — always validate input.
// See the TypeScript section in rsc-troubleshooting.md for a Zod example.
const content = formData.get('content');
const postId = formData.get('postId');

```jsx
'use client';

import { useRouter, startTransition } from 'next/navigation';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startTransition is a core React API and should be imported from react, not next/navigation. The current import will break in any non-Next.js RSC setup (including React on Rails).

Suggested change
import { useRouter, startTransition } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { startTransition } from 'react';

…nation

- Add "'use client' Marks a Boundary, Not a Component Type" subsection
  to Mental Model Shift in component patterns (simple, no webpack details)
- Move chunk contamination from Common Mistakes to troubleshooting guide
  (next to existing Import Chain Contamination section), with full webpack
  diagnostic details including the redundant-directive risk
- Add cross-references from: entry point doc, third-party libs thin wrapper
  intro, and troubleshooting directive mistakes section
- Replace Mistake 3 in component patterns with brief summary + link

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Feb 28, 2026

Review: RSC Migration Guide Series

This is a well-structured, comprehensive series covering the right topics. The writing is clear, the patterns are accurate, and the cross-references between articles work correctly. A few issues worth addressing before merge:

Critical Bug

startTransition is imported from the wrong module in rsc-troubleshooting.md:

// Incorrect
import { useRouter, startTransition } from 'next/navigation';

// Correct
import { startTransition } from 'react';
import { useRouter } from 'next/navigation';

startTransition is not exported from next/navigation — it's a core React API. This will cause a runtime error for anyone copying the Error Boundary example.

Significant: Next.js-Specificity Not Framed for React on Rails Users

Many examples use Next.js-specific APIs (cookies() from next/headers, revalidatePath from next/cache, useRouter from next/navigation, params as Promise) without labeling them as such. This is fine for general RSC documentation, but this guide lives in the React on Rails repo, where users may be running Webpack + Rails without Next.js at all.

The intro (migrating-to-rsc.md) and each article's header would benefit from a brief note clarifying which patterns are framework-agnostic and which require Next.js. Something like:

Note: Some examples use Next.js APIs (next/headers, next/cache, etc.). Adapt these to your framework's equivalents if you're using React on Rails without Next.js.

Security: Server Action Examples Show No Input Validation

The Server Action examples in rsc-data-fetching.md and rsc-third-party-libs.md write FormData directly to the database without validation. rsc-troubleshooting.md does show Zod validation at the end, but the earlier examples set a bad precedent. The troubleshooting Zod example is good — consider adding a brief inline callout in the data fetching action examples pointing to it.

Minor: Incomplete Code Example

AddToCartButton.jsx in rsc-component-patterns.md calls addToCart(productId, quantity) without importing or defining it. This will confuse readers trying to run the example. A comment like // import { addToCart } from './cart-api' or converting it to a Server Action would make it self-contained.

Minor: styled-components Entry Slightly Confusing

The CSS-in-JS table says styled-components is in "Maintenance mode (March 2025). v6.3.0+ added RSC support" — the juxtaposition reads as contradictory. Consider splitting these into two cells or rephrasing: "v6.3.0+ added RSC support before entering maintenance mode in March 2025."


Overall this is high-quality documentation. The chunk contamination section in particular is excellent — that's a subtle, real-world issue that most guides skip.

Comment on lines +285 to +286
import { useRouter, startTransition } from 'next/navigation';
import { ErrorBoundary } from 'react-error-boundary';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startTransition is not exported from next/navigation — it's a React core API. This import will fail at runtime.

Suggested change
import { useRouter, startTransition } from 'next/navigation';
import { ErrorBoundary } from 'react-error-boundary';
import { startTransition } from 'react';
import { useRouter } from 'next/navigation';
import { ErrorBoundary } from 'react-error-boundary';

<button onClick={() => addToCart(productId, quantity)}>
Add to Cart
</button>
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addToCart is called here without being imported or defined anywhere in this file. Readers copying this example will get a ReferenceError. Consider either adding an import comment or converting the handler to a Server Action:

Suggested change
</div>
<button onClick={() => addToCart(productId, quantity)}>

A simple fix would be adding at the top of the file:

import { addToCart } from './cart-actions'; // or wherever your cart logic lives

Or make it a no-op stub with a comment indicating it should be replaced with a real implementation.

Comment on lines 582 to 589

```jsx
// actions.js
'use server';

import { revalidatePath } from 'next/cache';

export async function createComment(formData) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Server Actions are public HTTP endpoints — any client can call them with arbitrary formData. This example writes unvalidated user input directly to the database, which is unsafe and sets a bad precedent for readers.

Consider adding a brief inline note pointing to the Zod validation example in rsc-troubleshooting.md, or show minimal validation here:

export async function createComment(formData) {
  const content = formData.get('content');
  const postId = formData.get('postId');

  // Always validate Server Action inputs — they are public endpoints.
  // See the TypeScript section in rsc-troubleshooting.md for a full Zod example.
  if (typeof content !== 'string' || !content.trim() || typeof postId !== 'string') {
    return { error: 'Invalid input' };
  }

  await db.comments.create({ data: { content, postId } });
  revalidatePath(`/posts/${postId}`);
}


| Library | RSC Status | Notes |
|---------|-----------|-------|
| **styled-components** | Maintenance mode (March 2025). v6.3.0+ added RSC support via React's `<style>` tag hoisting. | The maintainer stated: "For new projects, I would not recommend adopting styled-components." React Context dependency is the root incompatibility. |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The juxtaposition of "Maintenance mode" and "v6.3.0+ added RSC support" in the same cell reads as contradictory. Consider clarifying the timeline:

| styled-components | Partial (v6.3.0+) | Added RSC support via React's <style> tag hoisting before entering maintenance mode in March 2025. The maintainer recommends against adopting it for new projects. React Context dependency is the root incompatibility. |

Rails users shouldn't bypass Rails layers to access the DB directly from
Server Components. Async props let Rails send data incrementally through
its normal controller/view layers, with Suspense-based streaming. Added
dedicated section with ERB helper, component pattern, TypeScript typing,
and cross-reference from Direct Database Access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Feb 28, 2026

PR Review: RSC Migration Guide Series

This is a well-structured and comprehensive documentation addition. The multi-article format, progressive migration strategy, and code examples are genuinely helpful. A few issues to address:


Bug: Wrong import for startTransition in error boundary example

In rsc-troubleshooting.md, the retry example imports startTransition from next/navigation, but startTransition is a React API, not a Next.js navigation API:

// Current (incorrect)
import { useRouter, startTransition } from 'next/navigation';

// Should be:
import { useRouter } from 'next/navigation';
import { startTransition } from 'react';

Misleading waterfall example — silent logic change

In rsc-troubleshooting.md's server waterfall section, the "BAD" example fetches user first because getStats and getPosts depend on user.id. The "GOOD" example silently changes the data dependency by using userId directly:

// BAD: Sequential (user.id needed for other calls)
const user = await getUser();
const stats = await getStats(user.id);  // depends on user
const posts = await getPosts(user.id);  // depends on user

// GOOD: shows userId passed as prop — but this only works
// if userId is already available as an input
const [user, stats, posts] = await Promise.all([
  getUser(userId),
  getStats(userId),  // ← changed from user.id
  getPosts(userId),  // ← changed from user.id
]);

Readers will assume the only change was adding Promise.all, missing that the real prerequisite is having userId available upfront. The example should explicitly note this refactoring step, or show a case without true data dependencies to avoid the confusion.


Performance claims missing citations

migrating-to-rsc.md cites the Frigade case study (good), but two other specific figures lack a source:

  • "63% improvement in Google Speed Index"
  • "Total blocking time reduced from 110ms to 1ms"

Please add citations or attribute these to a specific study/case.


SVG SMIL animations may not render on GitHub

The top-down-migration.svg uses SVG SMIL animations (<animate> elements). GitHub's SVG rendering sanitizes JavaScript and may strip or ignore SMIL animations, meaning the animated sequence will show as a static final-state image (or nothing) without the intended progressive reveal. A static multi-panel diagram or a raster image export would be more reliably rendered.


Minor: forwardRef framing in third-party libs section

rsc-third-party-libs.md lists forwardRef as something "Server Components cannot use." Since React 19 (the stated minimum requirement) deprecated forwardRef in favor of refs as plain props, this reads as a Server Components restriction when it's actually a React 19 deprecation. Worth noting the distinction to avoid confusion.


Testing claim may be outdated

rsc-troubleshooting.md states "Vitest does not support async Server Components as of early 2026." The React team has been actively working on test utilities for RSC, and @testing-library/react added RSC support in recent versions. This claim should reference a specific version or issue tracker for accuracy.

```jsx
'use client';

import { useRouter, startTransition } from 'next/navigation';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startTransition is a React API, not part of next/navigation. This import will fail at runtime.

Suggested change
import { useRouter, startTransition } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { startTransition } from 'react';

const posts = await getPosts(user.id); // 250ms (waits for stats)
}

// GOOD: Parallel fetching (300ms total)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "GOOD" example silently changes the inputs: the "BAD" version uses user.id for downstream calls (because user is fetched first), while this version uses userId as a pre-existing prop. These are different data dependency scenarios.

The example is misleading because it implies the only change is wrapping with Promise.all. Readers should understand that this refactoring is only possible when userId is available upfront — which may require restructuring callers to pass the ID rather than the hydrated object. Consider adding a comment clarifying this prerequisite.


- **62% reduction** in client-side bundle size ([Frigade case study](https://frigade.com/blog/bundle-size-reduction-with-rsc-and-frigade))
- **63% improvement** in Google Speed Index
- Total blocking time reduced **from 110ms to 1ms**
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Frigade citation is present for the bundle size reduction, but the next two metrics ("63% improvement in Google Speed Index" and "Total blocking time reduced from 110ms to 1ms") are uncited. Please add a source for these — if they're also from the Frigade case study, link there; otherwise attribute to the appropriate study.


### The Fundamental Challenge

Async Server Components are a new paradigm that existing testing tools were not designed for. **Vitest does not support async Server Components** as of early 2026.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This claim should be versioned and linked. @testing-library/react has been adding RSC support and the React team has published guidance on testing async Server Components. Saying "Vitest does not support" without a tracking issue or version reference will become outdated quickly and may already be inaccurate.

React on Rails users don't access the DB directly from Server Components
or fetch from API routes — they receive data from Rails as props. Removed
Direct Database Access section, replaced fetch() "After" example with
props-from-Rails pattern, replaced db/fetch calls in React.cache() and
Server Actions examples with generic functions, and added Rails controller
context for mutations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Feb 28, 2026

Review: RSC Migration Guide Series

This is a well-structured and comprehensive set of guides — the content is genuinely useful and the organization (five focused articles with cross-references) is solid. Good call on moving chunk contamination to the troubleshooting guide and keeping the component patterns article clean.

A few issues worth addressing before merge:


Bug: startTransition imported from the wrong package

In rsc-troubleshooting.md, the error boundary example imports startTransition from next/navigation, but it is a React export. next/navigation does not export startTransition.

Fix: import startTransition from 'react' separately.


Technically inaccurate: use() hook error explanation

rsc-data-fetching.md explains the uncached promise error as React tracking promises by "call-position index across re-renders". This is the rules-of-hooks tracking mechanism, not what causes the uncached promise error. The actual reason: every re-render creates a new promise object with a different reference, so React cannot determine whether it is the same pending operation or a brand-new one — it throws rather than risk infinite suspension loops. The fix recommendations are correct; just the mechanical explanation is off.


Anti-pattern: Server Action calling an API route

The Server Actions example in rsc-data-fetching.md shows a Server Action making an outbound HTTP fetch('/api/comments', ...) request to an API route on the same server. This is an anti-pattern — it wastes a network hop for no reason. Server Actions have direct access to the server environment and should call the service/controller layer directly. In a React on Rails context this is particularly confusing since the point is that Rails handles mutations through its normal controller layer.


Next.js-specific APIs presented without React on Rails alternatives

Several patterns use Next.js APIs that are not available in React on Rails projects:

  • next/headers (import { cookies } from 'next/headers') — used in the Auth Provider and Theme Provider examples in rsc-context-and-state.md
  • next/navigation (useRouter) — used in the error boundary retry pattern in rsc-troubleshooting.md
  • next-themes — used in the multi-provider composition example
  • next-intl — used as the primary i18n example
  • revalidatePath — called in the Server Action form example in rsc-third-party-libs.md
  • optimizePackageImports — presented as a barrel-file solution but this is a Next.js-only config option

These patterns are fine illustrations of RSC concepts, but they will confuse React on Rails developers who try to apply them directly. They should either be labeled as "Next.js only — adapt for your framework" or replaced with framework-agnostic alternatives.


Minor: useMemo warning overstated

The useMemo example in the use() mistakes section quotes the React docs about React being able to "forget" memoized values. That optimization (evicting memos under memory pressure) is not triggered during normal rendering — it is a safety valve for extreme situations. The conclusion (do not use useMemo for this) is still correct, but the reasoning given may cause developers to distrust useMemo broadly. A more precise explanation: useMemo does not guarantee stable promise identity across Suspense boundaries when use() is involved.


Minor: Missing newline at end of SVG

docs/migrating/images/top-down-migration.svg is missing a trailing newline.

```jsx
'use client';

import { useRouter, startTransition } from 'next/navigation';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startTransition is not exported from next/navigation — it comes from React. This import will fail at runtime.

Suggested change
import { useRouter, startTransition } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { startTransition } from 'react';


> **"A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework."**

**Why it happens:** React tracks promises passed to `use()` by their call-position index across re-renders. On each render, it checks whether the promise at index N is the same object reference as the last render. When you create a promise inside a Client Component, every render produces a new promise instance -- React sees a different reference and throws.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "call-position index" explanation is the rules-of-hooks tracking mechanism and doesn't accurately describe why use() throws here. The real reason: every render creates a new Promise object with a different object reference. use() internally suspends on the promise's identity — if the reference changes between renders, React treats it as a new, uncached promise and throws rather than risk an infinite suspension loop.

Suggested replacement for this paragraph:

Why it happens: Every render creates a new Promise object with a different reference. use() internally tracks promise state by reference — when the reference changes between renders, React sees a brand-new uncached promise and throws to avoid infinite suspension loops. The only safe sources of a stable promise reference are: a Server Component prop (created once per request) or a Suspense-compatible library that manages promise identity internally (e.g. TanStack Query's cache key).

const content = formData.get('content');
const postId = formData.get('postId');

await fetch('/api/comments', {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Server Action making an HTTP request to an API route on the same server is an anti-pattern — it adds a full network round-trip for no benefit. Server Actions run in the server environment and can call the business/service layer directly.

For a React on Rails project, this mutation should go through a Rails controller via a standard form submission or API call from the client, not through a Server Action that re-fetches from the server. If a Server Action is appropriate here, replace the fetch with a direct service call:

export async function createComment(formData) {
  const content = formData.get('content');
  const postId = formData.get('postId');
  // Call service layer directly — no HTTP hop
  await CommentService.create({ content, postId });
}


```jsx
// layout.jsx -- Server Component
import { cookies } from 'next/headers';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

next/headers is a Next.js-only API. React on Rails does not expose this module. This example will not work as written in a React on Rails project.

Consider either labeling this section as "Next.js example — adapt for your framework" or replacing the cookie reading with a framework-agnostic approach (e.g., reading auth state from props passed from the Rails controller).

export async function submitForm(formData) {
const name = formData.get('name');
await db.users.create({ data: { name } });
revalidatePath('/users');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revalidatePath is a Next.js-only API (next/cache). It is not available in React on Rails. This will cause a runtime error in a React on Rails project.

Consider removing it from this example, or annotating the example as Next.js-specific. In React on Rails, cache invalidation is handled through Rails' cache mechanisms.

// next.config.js
module.exports = {
experimental: {
optimizePackageImports: ['lucide-react', '@mui/material', 'my-lib'],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optimizePackageImports is a Next.js-only configuration option under experimental. It is not available in React on Rails or other RSC frameworks. Consider labeling this section clearly as Next.js-specific, or noting the framework-agnostic alternative: use direct (non-barrel) imports in your own code, and for third-party packages, check if they support exports field path mapping in their package.json.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add migration guide: Migrating existing React on Rails projects to React Server Components (RSC)

1 participant