Skip to content

Comments

Add RSC migration guide series (#2459)#2460

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

Add RSC migration guide series (#2459)#2460
AbanoubGhadban wants to merge 4 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 management guidance, data-fetching strategies, third-party library compatibility patterns, and a troubleshooting & pitfalls guide — all with practical checklists, migration phases, decision guides, and example snippets.

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

Walkthrough

Adds six new migration guides under docs/migrating/ for transitioning apps to React Server Components: an overview migration roadmap, component restructuring patterns, context/state strategies, data‑fetching patterns, third‑party library compatibility, and a troubleshooting/pitfalls guide.

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, and references.
Component Restructuring Patterns
docs/migrating/rsc-component-patterns.md
New patterns doc describing top‑down migration phases and five refactor patterns (state push, Donut, wrapper extraction, async+Suspense, server→client promise handoff), decision guide and pitfalls.
Context & State Management
docs/migrating/rsc-context-and-state.md
New guide covering provider wrapper patterns, composed providers, React.cache() usage, server↔client data sharing, per‑request stores for Redux/Zustand, provider examples, and a migration checklist.
Data Fetching Migration
docs/migrating/rsc-data-fetching.md
New comprehensive data-fetching migration guide: async server components, DB access, migrating React Query/SWR patterns, server actions for mutations, Suspense/loading strategies, deduplication, and waterfall avoidance.
Third‑Party Library Compatibility
docs/migrating/rsc-third-party-libs.md
New compatibility guide with wrapper/export patterns, CSS-in-JS considerations, UI/form/animation libs guidance, server/client guards, and a compatibility decision matrix.
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, 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): addressed by docs/migrating/migrating-to-rsc.md which follows the proposed structure and acceptance criteria.
  • shakacode/react_on_rails#2426 — RSC troubleshooting guidance: docs/migrating/rsc-troubleshooting.md implements a troubleshooting matrix and failure-mode guidance matching that issue.

Poem

🐰 With twitching nose and eager paw,

I hopped through docs to mend the maw.
Six lanterns guide the RSC night,
State, fetch, and libs now shining bright.
Hop on—migration’s in my sight! ✨

🚥 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)' clearly and concisely describes the main change: a new comprehensive migration guide series for React Server Components.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding-related requirements from issue #2459: provides migration prerequisites, step-by-step path, common pitfalls, verification guidance, and cross-references through six documentation files totaling 2,662 lines.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the RSC migration guide series objectives. The six documentation files collectively address component patterns, state/context handling, data fetching, third-party library compatibility, and troubleshooting—all within the linked issue's scope.
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.

@claude
Copy link

claude bot commented Feb 21, 2026

Review: RSC Migration Guide Series

This is a solid, well-structured documentation series that covers RSC migration comprehensively. The five-article layout, the before/after code pattern, and the decision matrices are all genuinely useful. The inline comments call out specifics; this top-level comment covers broader concerns.


Bug (must fix before merge)

startTransition imported from the wrong package in rsc-troubleshooting.md (the Error Boundary example). startTransition lives in react, not next/navigation. As written the code throws a TypeError at runtime. See inline comment on line 220.

Missing import in the preload pattern in rsc-data-fetching.md: getPost is called but not included in the import statement. See inline comment on line 316.


Framework-specificity mismatch

This is the most systemic concern. A significant portion of the code examples rely on Next.js App Router APIs that do not exist in React on Rails:

  • cookies() / await cookies() from next/headers
  • revalidatePath() from next/cache
  • HydrationBoundary / dehydrate from @tanstack/react-query in the Next.js style
  • getMessages() / getLocale() from next-intl/server
  • useRouter() from next/navigation

A reader using React on Rails Pro RSC support will immediately hit failures when they copy these examples. The intro in migrating-to-rsc.md does link to the Pro RSC docs, but that framing is easy to miss when a reader is deep in article 3 or 4.

Suggested fix: Add a short callout block at the top of each article where Next.js-specific code appears, such as:

Framework note: Some examples below use Next.js App Router APIs (e.g., next/headers, next/cache). For React on Rails, see the Pro RSC documentation for the equivalent setup.


Context API syntax inconsistency

rsc-context-and-state.md mixes two Context value syntaxes without explanation:

  • Pattern 1 uses <ThemeContext.Provider value={...}> (pre-React 19 style)
  • Pattern 4 uses <UserContext value={userPromise}> (React 19 shorthand)

Both are valid for React 19+, but switching between them without comment will confuse readers. Standardise on one form, or add a sentence noting that React 19 allows the shorthand.


Missing absolute-URL note in the data-fetching example

The 'Before' example uses a relative URL (/api/users/...), but the 'After' example correctly switches to an absolute URL. This change is necessary (Node.js fetch on the server requires absolute URLs), but it is not listed in the 'What changed' bullet points. Readers who keep the relative URL will get a silent failure.


Minor

  • Stale date claim in rsc-third-party-libs.md: 'Still unstable as of early 2026' for Pigment CSS will grow outdated. A link to the changelog/releases page is more durable.
  • suppressHydrationWarning scope: The troubleshooting entry is missing the important constraint that this attribute only suppresses the warning on the element itself, not on its children.
  • Security prominence: The 'Server Actions are public endpoints' note in the TypeScript section is the most security-critical point in the whole series and is easy to miss buried at the end. Consider promoting it to a highlighted callout block and cross-referencing it from the data fetching article where Server Actions are first introduced.

Add a "Common use() Mistakes in Client Components" subsection to the
data fetching guide explaining why creating promises inside Client
Components causes the "suspended by an uncached promise" runtime error.

Includes three WRONG examples (inline fetch, uncached variable, useMemo)
and two CORRECT approaches (Server Component prop, Suspense-compatible
library like TanStack Query). Cross-references added to component
patterns, context/state, and troubleshooting error catalog.

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

claude bot commented Feb 23, 2026

PR Review: RSC Migration Guide Series

This is a solid, well-structured documentation series that covers RSC migration comprehensively. The coverage of patterns (donut, provider wrapper, preload, promise handoff) is accurate and the before/after examples are genuinely useful. Below are issues I found ranging from technical bugs to audience-targeting concerns.

Technical Bugs

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

startTransition is exported from 'react', not 'next/navigation'. The current example will fail at runtime:

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

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

2. Misleading waterfall comment (rsc-data-fetching.md)

In the sequential fetch example, the comment states getPosts "waits for stats", but getPosts(user.id) only depends on user.id — which is already available after the first await. This is actually two independent serial awaits, not a three-deep dependency chain. The comment should say "waits for user" to be accurate.

3. addToCart is undefined (rsc-component-patterns.md)

The AddToCartButton example calls addToCart(productId, quantity) but addToCart is never imported or defined. Readers will wonder where it comes from. A comment like // imported from your cart service or a placeholder import would help.

Audience / Framework Targeting

The guides are presented as generic React migration guides but contain heavy Next.js-specific API usage without clear labeling:

  • cookies() from 'next/headers' (Auth Provider section)
  • revalidatePath() from 'next/cache' (Server Actions section)
  • router.refresh() from 'next/navigation' (Error Boundary section)
  • getServerSession() from NextAuth
  • params: Promise<{ id: string }> (Next.js 15+ specific)
  • optimizePackageImports in next.config.js

Since this doc lives in the React on Rails repo (not a Next.js project), readers may be confused. Consider either: (a) adding a note at the top of affected sections saying "Next.js example — adapt to your framework", or (b) adding React on Rails Pro equivalents alongside the Next.js examples.

Accuracy Issues

Vitest claim is too strong (rsc-troubleshooting.md)

"Vitest does not support async Server Components as of early 2026"

This is overstated. Vitest can render async Server Components (they're just async functions). What's actually unsupported is the RSC-specific runtime behavior (serialization, streaming, the React Flight protocol). The practical constraint is that you can't test the RSC execution environment with Vitest — but you can test the component output if you call it directly as an async function.

"async/await is not yet supported in Client Components" (rsc-troubleshooting.md)

The "not yet" framing implies this is a temporary gap. It's an intentional design constraint — Client Components cannot be async because they run in the browser hydration model. Consider rephrasing to "Client Components cannot be async" without the "not yet".

Minor Issues

  • Hard-coded version requirements in migrating-to-rsc.md (React on Rails 15+, React on Rails Pro 4+) will become stale. Consider linking to a compatibility matrix or changelog instead.
  • The styled-components "maintenance mode" note is accurate but time-stamped. If the library reverses course, the note will be misleading.
  • External metric claims ("62% reduction", "63% improvement") link to vendor case studies. Consider noting these are specific case-study results, not guaranteed outcomes.

```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 from react, not next/navigation

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

startTransition is part of the React API (import { startTransition } from 'react'). It has never been exported from next/navigation. This example will throw a runtime error as written.

const user = await getUser(); // 200ms
const stats = await getStats(user.id); // 300ms
const posts = await getPosts(user.id); // 250ms
// Total: 750ms (sequential)
Copy link

Choose a reason for hiding this comment

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

Misleading comment: getPosts doesn't wait for stats

Suggested change
// Total: 750ms (sequential)
const posts = await getPosts(user.id); // 250ms (also waits for user)

getPosts(user.id) only depends on user.id, which was already resolved by the first await. It does not wait for stats. The comment as written implies a three-level dependency chain that doesn't exist. The actual problem is two independent fetches running serially when they could be parallel — both depending only on user.id.

type="number"
value={quantity}
onChange={(e) => setQuantity(Number(e.target.value))}
/>
Copy link

Choose a reason for hiding this comment

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

addToCart is undefined — needs an import or a comment clarifying its source

The addToCart(productId, quantity) call on this line and line 144 references a function that is neither imported nor defined in the snippet. Readers will wonder where it comes from.

Consider adding a placeholder import at the top of the snippet:

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

import { useState } from 'react';
import { addToCart } from '../lib/cart'; // your cart service

Or at minimum add a comment: // addToCart imported from your cart service


### 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.

Overstated claim — Vitest can test async Server Components as plain async functions

The assertion "Vitest does not support async Server Components" is too broad. Async Server Components are just async functions that return JSX — they can be called and tested directly in Vitest:

import { render } from '@testing-library/react';
import UserProfile from './UserProfile';

it('renders user name', async () => {
  const jsx = await UserProfile({ userId: '1' }); // Call as async fn
  const { getByText } = render(jsx);
  expect(getByText('Alice')).toBeInTheDocument();
});

What Vitest cannot reliably test is the RSC runtime behavior: serialization across the server-client boundary, streaming, and the React Flight protocol. A more accurate statement:

"Vitest can test async Server Components by calling them as plain async functions. Testing RSC-specific runtime behavior (streaming, serialization boundaries, the Flight protocol) requires an E2E environment like Playwright."

| `"You're importing a component that needs useState/useEffect..."` | Using hooks in a Server Component | Add `'use client'` to the component file |
| `"Only plain objects, and a few built-ins, can be passed to Client Components..."` | Passing class instances or non-serializable values | Convert to plain objects with `.toJSON()` or manual serialization |
| `"async/await is not yet supported in Client Components"` | Making a Client Component async | Move async logic to a Server Component, or use `useEffect`/`use()` |
| `"A component was suspended by an uncached promise..."` | Creating a promise inside a Client Component and passing it to `use()` | Pass the promise from a Server Component as a prop, or use a Suspense-compatible library like TanStack Query. See [Common `use()` Mistakes](rsc-data-fetching.md#common-use-mistakes-in-client-components) |
Copy link

Choose a reason for hiding this comment

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

"not yet" implies a temporary limitation — it's an intentional design constraint

"async/await is not yet supported in Client Components"

The "not yet" framing suggests this will be supported in a future React version. It won't — Client Components are intentionally synchronous because they run during browser hydration where async execution doesn't fit the model. Consider:

"Client Components cannot be async — move async logic to a Server Component or use useEffect/use()"

```jsx
// layout.jsx -- Server Component
import { cookies } from 'next/headers';
import { verifyToken } from './lib/auth';
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 used without labeling

cookies() from 'next/headers' is a Next.js App Router API not available in React on Rails. Since this series is published in the React on Rails docs, readers may be confused about what's framework-agnostic vs. Next.js-specific.

Consider adding a note like:

Note: This example uses Next.js App Router APIs (cookies() from next/headers). For React on Rails, read the auth session from the Rails session or a cookie in your controller and pass it as a prop to the root component.

This applies to other Next.js-specific examples throughout the series (revalidatePath, params: Promise<...>, optimizePackageImports in next.config.js, etc.).

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-data-fetching.md (2)

557-569: ⚠️ Potential issue | 🟠 Major

Both Profile functions still use await without being async — syntax error.

Both the WRONG and CORRECT variants at Lines 559 and 566 declare function Profile(...) (non-async) but use await inside the body, which is a syntax error. Readers copying either snippet will hit SyntaxError: await is only valid in async functions.

🐛 Proposed fix
 // WRONG: Creating cache inside a component
-function Profile({ userId }) {
+async function Profile({ userId }) {
   const getUser = cache(fetchUser);
   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 557 - 569, Both example
snippets use await inside a non-async function (Profile), causing a syntax
error; update both WRONG and CORRECT examples so Profile is declared async
(e.g., async function Profile({ userId }) or an async arrow) and ensure any
top-level helper like getUser = cache(fetchUser) remains at module scope;
reference symbols: Profile, getUser, cache, fetchUser.

583-611: ⚠️ Potential issue | 🟠 Major

revalidatePath from 'next/cache' still has no Next.js-specific disclaimer.

revalidatePath is a Next.js API and is unavailable in React on Rails. Without a callout, React on Rails users reading this guide will be confused when the import fails.

📝 Proposed fix — add a framework note
+> **Note (Next.js):** The following example uses the Next.js `revalidatePath` API from `'next/cache'`.
+> For React on Rails, use the equivalent revalidation mechanism provided by [React on Rails Pro](https://www.shakacode.com/react-on-rails-pro/).
+
 ```jsx
 // actions.js
 'use server';
🤖 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 583 - 611, The example uses
Next.js's revalidatePath (imported from 'next/cache') inside actions.js
(function createComment) but the doc omits a framework disclaimer; update the
docs around the createComment/revalidatePath example to add a clear note that
revalidatePath is Next.js-specific and will not work in React on Rails (the
import will fail), reference the symbols revalidatePath and createComment and
the files actions.js and CommentForm.jsx so readers can find the snippet, and
optionally suggest an alternative approach for React on Rails (e.g.,
server-triggered cache invalidation or client-side refresh) or link to the
framework-specific migration guidance.
docs/migrating/rsc-component-patterns.md (4)

276-307: ⚠️ Potential issue | 🟡 Minor

Pattern 4 still missing imports for Dashboard and export for Stats.

Dashboard uses six symbols (Stats, RevenueChart, RecentOrders, StatsSkeleton, ChartSkeleton, TableSkeleton) that are not imported, and Stats at Line 298 is an unexported function.

📝 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() {
-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
Dashboard server component is using six unresolved symbols (Stats, RevenueChart,
RecentOrders, StatsSkeleton, ChartSkeleton, TableSkeleton) and the Stats
function is not exported; add appropriate import statements at the top of the
file for those six components/placeholder skeletons and make Stats an exported
component (e.g., export async function Stats or export default async function
Stats depending on how you intend to import it) so the Dashboard default export
can reference them without missing-symbol errors.

108-122: ⚠️ Potential issue | 🟡 Minor

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

The "Before" snippet (Line 73) has export default function ProductPage, but Line 110 drops the export, creating an inconsistency.

📝 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"
code snippet drops the default export for the server component; restore the
default export by making the async ProductPage function a default export (i.e.,
export default ProductPage) so the component signature matches the "Before"
example; locate the async function ProductPage in the snippet and change its
declaration to use a default export (or add an export default statement for
ProductPage) so imports relying on a default export continue to work.

336-352: ⚠️ Potential issue | 🟡 Minor

Pattern 5 still missing a React 19+ requirement note.

use(commentsPromise) at Line 343 is only available in React 19. Readers on React 18 will encounter an error with no guidance.

📝 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 - 352, The example
uses use(commentsPromise) inside the Comments client component which is only
supported in React 19+; update the docs to add a note near the Comments
component example (reference: Comments function and use(commentsPromise)) that
callers on React 18 must not use use() and should instead await the promise on
the server or pass resolved data as props, and explicitly state the React 19+
requirement and suggested alternatives for React 18 users.

40-58: ⚠️ Potential issue | 🟡 Minor

Add language identifier to the tree-diagram code fence.

The block at Line 40 still has no language specifier, triggering MD040.

🔧 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 code
fence for the tree-diagram block (the snippet starting with "Before: App ('use
client')           <-- Everything is client-side") is missing a language
identifier and triggers MD040; update the opening triple-backtick to include a
language tag (e.g., change ``` to ```text) so the block is recognized as a
text/code block (leave the diagram contents unchanged).
docs/migrating/rsc-troubleshooting.md (2)

216-247: ⚠️ Potential issue | 🟡 Minor

startTransition is still imported from the wrong module.

startTransition is a React API — it is not exported by next/navigation. The example at Line 219 will produce a runtime import error for any reader who copies it.

🐛 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 import for
startTransition is incorrect: it's a React API, not from next/navigation; update
the import statement so startTransition is imported from 'react' while useRouter
stays imported from 'next/navigation' (so modify the top-level imports that
reference startTransition and useRouter). Ensure the ErrorFallback component's
retry function continues to call startTransition(() => { router.refresh();
resetErrorBoundary(); }) using the corrected startTransition import.

87-93: ⚠️ Potential issue | 🟡 Minor

Both tree-diagram code fences at Lines 87 and 322 still missing language identifiers (MD040).

🔧 Proposed fix (applies to both locations)
-```
+```text
 ClientComponent.jsx ('use client')
-```
+```text
 Unit Tests (Vitest/Jest)
🤖 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 markdown
code fences at the two diagram locations are missing language identifiers
(MD040); update both fenced blocks that start with the lines
"ClientComponent.jsx ('use client')" and "Unit Tests (Vitest/Jest)" to include
an explicit language tag (e.g., use ```text) so the code fences become ```text
... ``` to satisfy the lint rule.
🤖 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-troubleshooting.md`:
- Around line 464-469: The example in DBComponent falsely suggests
fetch(process.env.DATABASE_URL) works; replace the misleading line by either
using a real HTTP environment variable (e.g. process.env.API_URL) if you want to
demonstrate Fetch, or show direct DB access using a DB client/connection string
when demonstrating DATABASE_URL; update the comment and any references to
process.env.DATABASE_URL in the DBComponent example to reflect the chosen
realistic approach so readers aren't confused about HTTP vs DB connection
strings.

---

Duplicate comments:
In `@docs/migrating/rsc-component-patterns.md`:
- Around line 276-307: The Dashboard server component is using six unresolved
symbols (Stats, RevenueChart, RecentOrders, StatsSkeleton, ChartSkeleton,
TableSkeleton) and the Stats function is not exported; add appropriate import
statements at the top of the file for those six components/placeholder skeletons
and make Stats an exported component (e.g., export async function Stats or
export default async function Stats depending on how you intend to import it) so
the Dashboard default export can reference them without missing-symbol errors.
- Around line 108-122: The "After" code snippet drops the default export for the
server component; restore the default export by making the async ProductPage
function a default export (i.e., export default ProductPage) so the component
signature matches the "Before" example; locate the async function ProductPage in
the snippet and change its declaration to use a default export (or add an export
default statement for ProductPage) so imports relying on a default export
continue to work.
- Around line 336-352: The example uses use(commentsPromise) inside the Comments
client component which is only supported in React 19+; update the docs to add a
note near the Comments component example (reference: Comments function and
use(commentsPromise)) that callers on React 18 must not use use() and should
instead await the promise on the server or pass resolved data as props, and
explicitly state the React 19+ requirement and suggested alternatives for React
18 users.
- Around line 40-58: The code fence for the tree-diagram block (the snippet
starting with "Before: App ('use client')           <-- Everything is
client-side") is missing a language identifier and triggers MD040; update the
opening triple-backtick to include a language tag (e.g., change ``` to ```text)
so the block is recognized as a text/code block (leave the diagram contents
unchanged).

In `@docs/migrating/rsc-data-fetching.md`:
- Around line 557-569: Both example snippets use await inside a non-async
function (Profile), causing a syntax error; update both WRONG and CORRECT
examples so Profile is declared async (e.g., async function Profile({ userId })
or an async arrow) and ensure any top-level helper like getUser =
cache(fetchUser) remains at module scope; reference symbols: Profile, getUser,
cache, fetchUser.
- Around line 583-611: The example uses Next.js's revalidatePath (imported from
'next/cache') inside actions.js (function createComment) but the doc omits a
framework disclaimer; update the docs around the createComment/revalidatePath
example to add a clear note that revalidatePath is Next.js-specific and will not
work in React on Rails (the import will fail), reference the symbols
revalidatePath and createComment and the files actions.js and CommentForm.jsx so
readers can find the snippet, and optionally suggest an alternative approach for
React on Rails (e.g., server-triggered cache invalidation or client-side
refresh) or link to the framework-specific migration guidance.

In `@docs/migrating/rsc-troubleshooting.md`:
- Around line 216-247: The import for startTransition is incorrect: it's a React
API, not from next/navigation; update the import statement so startTransition is
imported from 'react' while useRouter stays imported from 'next/navigation' (so
modify the top-level imports that reference startTransition and useRouter).
Ensure the ErrorFallback component's retry function continues to call
startTransition(() => { router.refresh(); resetErrorBoundary(); }) using the
corrected startTransition import.
- Around line 87-93: The markdown code fences at the two diagram locations are
missing language identifiers (MD040); update both fenced blocks that start with
the lines "ClientComponent.jsx ('use client')" and "Unit Tests (Vitest/Jest)" to
include an explicit language tag (e.g., use ```text) so the code fences become
```text ... ``` to satisfy the lint rule.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • docs/migrating/images/top-down-migration.svg is excluded by !**/*.svg
📒 Files selected for processing (4)
  • docs/migrating/rsc-component-patterns.md
  • docs/migrating/rsc-context-and-state.md
  • docs/migrating/rsc-data-fetching.md
  • docs/migrating/rsc-troubleshooting.md
✅ Files skipped from review due to trivial changes (1)
  • docs/migrating/rsc-context-and-state.md

Comment on lines +464 to +469
```jsx
// Server Component -- full access
async function DBComponent() {
const data = await fetch(process.env.DATABASE_URL); // Works
const secret = process.env.API_SECRET; // Works
}
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

fetch(process.env.DATABASE_URL) is a misleading example.

DATABASE_URL is typically a database connection string (e.g. postgresql://user:pass@host/db), not an HTTP URL. The Fetch API will throw on non-HTTP schemes, so the // Works comment is incorrect and will confuse readers.

📝 Proposed fix — use a realistic HTTP endpoint or show direct DB access
 async function DBComponent() {
-  const data = await fetch(process.env.DATABASE_URL);  // Works
-  const secret = process.env.API_SECRET;               // Works
+  // All env vars are accessible — DB connection string, 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 464 - 469, The example in
DBComponent falsely suggests fetch(process.env.DATABASE_URL) works; replace the
misleading line by either using a real HTTP environment variable (e.g.
process.env.API_URL) if you want to demonstrate Fetch, or show direct DB access
using a DB client/connection string when demonstrating DATABASE_URL; update the
comment and any references to process.env.DATABASE_URL in the DBComponent
example to reflect the chosen realistic approach so readers aren't confused
about HTTP vs DB connection strings.

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