Add RSC migration guide series (#2459)#2460
Conversation
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>
WalkthroughAdds 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
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryAdds a comprehensive 5-part RSC migration guide series (2,564 new lines) focusing on React-side restructuring challenges rather than framework configuration. Content Overview:
Quality:
Documentation-only change: No code, config, or test modifications. Safe to merge. Confidence Score: 5/5
Important Files Changed
Last reviewed commit: 438c99f |
PR Review: RSC Migration Guide SeriesThis 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 exampleIn import { useRouter, startTransition } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { startTransition } from 'react';Concern: Serialization table may be inaccurate for React 19In Bug:
|
| ```jsx | ||
| 'use client'; | ||
|
|
||
| import { useRouter, startTransition } from 'next/navigation'; |
There was a problem hiding this comment.
startTransition is not exported from next/navigation — it comes from react. This code will throw at runtime.
| 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` | |
There was a problem hiding this comment.
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)}> |
There was a problem hiding this comment.
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>; |
There was a problem hiding this comment.
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:
| 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 |
There was a problem hiding this comment.
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
nullbefore 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.
There was a problem hiding this comment.
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.
| # 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/) |
There was a problem hiding this comment.
🧩 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/nullRepository: 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 mdRepository: 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 -20Repository: 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 -20Repository: 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/*.mdRepository: 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 -50Repository: 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.
| ``` | ||
| 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 | ||
| ``` |
There was a problem hiding this comment.
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.
| ```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>; | ||
| } | ||
| ``` |
There was a problem hiding this comment.
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.
| 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. |
There was a problem hiding this comment.
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.
| ```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); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
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.
| ```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> | ||
| ); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
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.
| ``` | ||
| 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) | ||
| ``` |
There was a problem hiding this comment.
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.
| ```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> | ||
| ); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
🧩 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:
- 1: https://react.dev/reference/react/startTransition?utm_source=openai
- 2: https://nextjs.org/docs/pages/api-reference/functions/use-router?utm_source=openai
🏁 Script executed:
# Check if the file exists and examine the content at the specified lines
fd rsc-troubleshooting.mdRepository: 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
fiRepository: 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.
| ```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>
Review: RSC Migration Guide SeriesThis 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
Code Accuracy Issues"Solution 4: Mixed Strategy" in Inconsistent React Context API in
Security GapServer Actions need an authentication note in Documentation QualityFramework-specific APIs are unlabeled: "Mounted" pattern needs a stronger caveat: Positive Notes
|
| ```jsx | ||
| 'use client'; | ||
|
|
||
| import { useRouter, startTransition } from 'next/navigation'; |
There was a problem hiding this comment.
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.
| 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} /> |
There was a problem hiding this comment.
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 ause(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>; |
There was a problem hiding this comment.
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:
- Use the new syntax consistently throughout all examples in the article, or
- 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: |
There was a problem hiding this comment.
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.
| **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) { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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:
- CLS (Cumulative Layout Shift) — content pops in after the initial paint, which Google measures and penalizes
- 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` |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
| ```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> | ||
| ); | ||
| } |
There was a problem hiding this comment.
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.
| ```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> | ||
| ); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Pattern 4 code example has two correctness issues: missing imports and a missing export.
Dashboardrenders<Stats />,<RevenueChart />,<RecentOrders />,<StatsSkeleton />,<ChartSkeleton />, and<TableSkeleton />but imports none of them. Readers copying the snippet will get "X is not defined" errors.Statsis defined asasync function Stats()(unexported named function), soDashboard'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.
| ```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. |
There was a problem hiding this comment.
🧩 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”):
usedoes 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'; |
There was a problem hiding this comment.
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").
| 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'; |
There was a problem hiding this comment.
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.
| 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>; | ||
| } |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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:
- Adding a clear callout box like
> **Note:** The following examples use Next.js App Router APIs. For React on Rails, … - 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'; |
There was a problem hiding this comment.
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}`) |
There was a problem hiding this comment.
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. | |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
Review: RSC Migration Guide SeriesThis 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 mismatchThis 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:
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:
Context API syntax inconsistencyrsc-context-and-state.md mixes two Context value syntaxes without explanation:
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 exampleThe '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
|
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>
PR Review: RSC Migration Guide SeriesThis 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 Bugs1. Wrong import for
// 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 3. The Audience / Framework TargetingThe guides are presented as generic React migration guides but contain heavy Next.js-specific API usage without clear labeling:
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 IssuesVitest claim is too strong (rsc-troubleshooting.md)
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
|
| ```jsx | ||
| 'use client'; | ||
|
|
||
| import { useRouter, startTransition } from 'next/navigation'; |
There was a problem hiding this comment.
Bug: startTransition is from react, not next/navigation
| 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) |
There was a problem hiding this comment.
Misleading comment: getPosts doesn't wait for stats
| // 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))} | ||
| /> |
There was a problem hiding this comment.
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 serviceOr 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. |
There was a problem hiding this comment.
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) | |
There was a problem hiding this comment.
"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'; |
There was a problem hiding this comment.
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()fromnext/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.).
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (8)
docs/migrating/rsc-data-fetching.md (2)
557-569:⚠️ Potential issue | 🟠 MajorBoth
Profilefunctions still useawaitwithout beingasync— syntax error.Both the WRONG and CORRECT variants at Lines 559 and 566 declare
function Profile(...)(non-async) but useawaitinside the body, which is a syntax error. Readers copying either snippet will hitSyntaxError: 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
revalidatePathfrom'next/cache'still has no Next.js-specific disclaimer.
revalidatePathis 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 | 🟡 MinorPattern 4 still missing imports for
DashboardandexportforStats.
Dashboarduses six symbols (Stats,RevenueChart,RecentOrders,StatsSkeleton,ChartSkeleton,TableSkeleton) that are not imported, andStatsat 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
ProductPagestill missingexport defaultin 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 | 🟡 MinorPattern 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 | 🟡 MinorAdd 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
startTransitionis still imported from the wrong module.
startTransitionis a React API — it is not exported bynext/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 | 🟡 MinorBoth 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
⛔ Files ignored due to path filters (1)
docs/migrating/images/top-down-migration.svgis excluded by!**/*.svg
📒 Files selected for processing (4)
docs/migrating/rsc-component-patterns.mddocs/migrating/rsc-context-and-state.mddocs/migrating/rsc-data-fetching.mddocs/migrating/rsc-troubleshooting.md
✅ Files skipped from review due to trivial changes (1)
- docs/migrating/rsc-context-and-state.md
| ```jsx | ||
| // Server Component -- full access | ||
| async function DBComponent() { | ||
| const data = await fetch(process.env.DATABASE_URL); // Works | ||
| const secret = process.env.API_SECRET; // Works | ||
| } |
There was a problem hiding this comment.
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.
Summary
Articles created:
Closes #2459
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit