Senior 3 min · April 12, 2026

React Server Components - 'use client' Causes 2.1MB Bloat

A 'use client' at the root layout ships 2.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • React Server Components (RSC) render on the server and stream a Flight payload that becomes HTML — server-only components ship 0KB of JavaScript
  • Client Components ('use client') ship JavaScript to the browser; Server Components do not — this is the core performance lever
  • RSC eliminates the waterfall problem: server components fetch data in parallel during render, not sequentially in useEffect hooks
  • Props crossing the Server/Client boundary must be JSON-serializable — Date, Map, Set, and functions throw errors
  • Overusing 'use client' at the top of the tree negates all RSC benefits — the entire subtree ships to the browser
  • Parallel async Server Components + Suspense boundaries deliver the biggest wins
Plain-English First

Imagine a restaurant where the chef (server) prepares 90% of the meal in the kitchen and only sends out the finished plate. Now imagine a restaurant where the chef sends raw ingredients to your table and you cook it yourself. Traditional React sends everything to the browser (your table) and makes you cook. RSC does most of the cooking on the server and only sends the finished plate — with a few interactive toppings (Client Components) that you can customize at the table.

React Server Components fundamentally change where rendering happens. In traditional React, every component ships JavaScript to the browser — even components that just display static data fetched from a database. RSC moves that rendering to the server, sending only the result to the client. The JavaScript bundle for server-only components is zero bytes.

The performance implications are measurable. Teams migrating to RSC report 40-70% reductions in client JavaScript bundle size and 200-500ms improvements in Largest Contentful Paint (LCP). But the gains are not automatic — misplacing 'use client' boundaries, serializing non-JSON types, and creating server-client waterfalls can make RSC slower than traditional React.

This article breaks down exactly how RSC affects performance in production, the benchmarks that matter, the pitfalls that cause regressions, and the optimization strategies that unlock the full benefit.

How RSC Rendering Works: Server vs Client Boundary

RSC introduces a hard boundary between Server Components and Client Components. Server Components render on the server, produce a serialized React element tree (the Flight format). The client reconciler merges this tree with any Client Components, selectively hydrating only the interactive parts.

The boundary is controlled by 'use client'. Any component marked 'use client' and everything it imports becomes a Client Component — shipped to the browser as JavaScript. Components without 'use client' are Server Components by default in Next.js App Router. They never ship JavaScript to the client.

The critical insight: 'use client' is not a directive that makes a component run on the client. It marks the boundary where the server tree meets the client tree. Everything below that boundary is client-rendered. Everything above it is server-rendered. Place the boundary as deep in the tree as possible — only the leaf component that needs interactivity should be marked.

io/thecodeforge/rsc-performance/app/dashboard/page.tsxTSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// This is a Server Component (no 'use client')
// It runs on the server, fetches data, and sends serialized HTML to the client
// Zero JavaScript shipped for this component

import { db } from '@/lib/db';
import { Suspense } from 'react';
import { InteractiveChart } from './InteractiveChart'; // Client Component
import { DataTable } from './DataTable';               // Server Component

// Server Component: fetches data directly — no API route needed
export default async function DashboardPage() {
  // Parallel fetches — no waterfall
  const [metrics, recentOrders] = await Promise.all([
    db.metrics.findMany({ orderBy: { date: 'desc' }, take: 30 }),
    db.orders.findMany({ orderBy: { createdAt: 'desc' }, take: 50 }),
  ]);

  return (
    <div>
      <h1>Dashboard</h1>

      {/* Server Component: renders table HTML on server, zero JS shipped */}
      <DataTable orders={recentOrders} />

      {/* Client Component: needs useState for zoom/pan interaction */}
      {/* Suspense boundary: streams the chart independently */}
      <Suspense fallback={<div>Loading chart...</div>}>
        <InteractiveChart data={metrics} />
      </Suspense>
    </div>
  );
}
Output
Server Component renders on server. DataTable ships zero JS. InteractiveChart ships ~12KB for chart interaction.
RSC Boundary Mental Model
  • Server Components render on the server — they send serialized output, not JavaScript
  • 'use client' marks where the server tree meets the client tree — it's a boundary, not a feature flag
  • Everything imported by a 'use client' component becomes a Client Component — the boundary propagates
  • Place 'use client' as deep as possible — only the interactive leaf should be marked
  • Server Components can fetch data directly (no API route) and pass it as props to Client Components
Production Insight
'use client' at a high level propagates to the entire subtree — every child ships JavaScript.
Place the boundary at the deepest interactive leaf, not at the page or layout root.
Rule: if a component doesn't use useState, useEffect, or onClick, it should NOT have 'use client'.
Key Takeaway
'use client' is a boundary marker, not a feature flag — it propagates to every child in the import tree.
Place the boundary at the deepest interactive leaf to maximize Server Component coverage.
Punchline: grep -r 'use client' app/ after migration — if it appears in layout.tsx or page.tsx, your RSC benefits are negated.
Choosing Server vs Client Components
IfComponent only displays data, no interactivity
UseServer Component — zero JavaScript shipped to the client
IfComponent needs useState, useEffect, or event handlers
UseClient Component — mark with 'use client' at the leaf level
IfComponent fetches data and passes it to an interactive child
UseServer Component parent fetches data, passes as props to 'use client' child
IfThird-party library uses browser APIs (chart.js, mapbox)
UseClient Component — wrap in 'use client' and lazy-load with next/dynamic

Data Fetching: Eliminating Waterfalls with Parallel Server Fetches

Traditional React data fetching creates waterfalls: a parent component fetches data in useEffect, then renders a child that fetches its own data in another useEffect. Each fetch waits for the previous render. With 3 nested data dependencies, you get 3 sequential network round-trips.

RSC eliminates this. Server Components can use async/await directly — no useEffect needed. Multiple Server Components on the same page fetch in parallel because each component's fetch is independent. The server resolves all fetches before sending the serialized tree to the client.

Important: Next.js automatically deduplicates identical fetch calls across parallel Server Components (same URL + same options). This is why the parallel pattern works so well.

The optimization pattern: move fetch calls into the components that consume the data, not their parents.

io/thecodeforge/rsc-performance/app/dashboard/DashboardWithSuspense.tsxTSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { Suspense } from 'react';
import { RevenueCard } from './RevenueCard';
import { UserGrowthCard } from './UserGrowthCard';
import { ErrorRateCard } from './ErrorRateCard';

// GOOD: Each child fetches its own data — all 3 fetch in parallel + automatic deduplication
// Total: max(150, 200, 100) = 200ms parallel
export default function Dashboard() {
  return (
    <div className="grid grid-cols-3 gap-4">
      <Suspense fallback={<CardSkeleton />}>
        <RevenueCard />        {/* fetches revenue — 150ms */}
      </Suspense>

      <Suspense fallback={<CardSkeleton />}>
        <UserGrowthCard />     {/* fetches users — 200ms */}
      </Suspense>

      <Suspense fallback={<CardSkeleton />}>
        <ErrorRateCard />      {/* fetches errors — 100ms */}
      </Suspense>
    </div>
  );
}

function CardSkeleton() {
  return <div className="animate-pulse bg-gray-200 h-48 rounded-lg" />;
}
Watch Out: await in Parent Creates a Waterfall
If a Server Component uses await before rendering its children, those children can't start their own fetches until the parent resolves. Move fetches into the children that consume the data. The parent should compose children, not fetch for them — unless the children genuinely depend on the parent's data.
Production Insight
Parallel fetches in independent Server Components eliminate waterfalls — max(fetch times) instead of sum(fetch times). Next.js deduplicates identical fetch calls automatically.
Each Suspense boundary streams its component independently as the fetch resolves.
Rule: move fetches into the components that consume the data, not their parents — unless children depend on parent data.
Key Takeaway
Parallel Server Component fetches eliminate waterfalls — max(fetch times) instead of sum(fetch times).
Suspense boundaries stream each component independently as its fetch resolves.
Punchline: move fetches into the components that consume the data — if the parent awaits before rendering children, you've recreated the waterfall.
Data Fetching Pattern Selection
IfMultiple independent data sources on the same page
UseEach component fetches its own data in parallel — wrap each in Suspense
IfChild component depends on parent's fetched data
UseParent fetches and passes as props — one fetch, shared result
IfData needed for SEO metadata (title, description)
UseFetch in generateMetadata() — Next.js parallelizes metadata and page rendering
IfData changes frequently (real-time dashboard)
UseServer Component for initial render + Client Component polling or WebSocket for updates

Serialization Boundaries: The Silent Performance Killer

The boundary between Server and Client Components requires serialization. Props passed from a Server Component to a Client Component must be JSON-serializable. This means no Date objects, no Map, no Set, no class instances, no functions, no symbols. If you pass a non-serializable prop, Next.js throws a hard error at build/dev time.

The serialization cost is also a performance factor. Large objects passed across the boundary are serialized on the server and deserialized on the client. A Server Component passing a 10,000-row dataset as a prop to a Client Component creates a multi-megabyte RSC payload. The fix: pass only the data the Client Component needs, or render the table as a Server Component and only pass interaction state to the client.

io/thecodeforge/rsc-performance/app/dashboard/DataTable.tsxTSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Server Component: renders the full table on the server
// Only the sort controls are Client Components
import { SortControls } from './SortControls'; // 'use client'

interface Order {
  id: string;
  customer: string;
  amount: number;
  status: string;
  createdAt: string; // ISO string, NOT Date — must be JSON-serializable
}

export default async function DataTable({
  orders,
  sortBy,
}: {
  orders: Order[];
  sortBy: string;
}) {
  const sorted = [...orders].sort((a, b) => {
    if (sortBy === 'amount') return b.amount - a.amount;
    if (sortBy === 'date') return b.createdAt.localeCompare(a.createdAt);
    return 0;
  });

  return (
    <div>
      <SortControls currentSort={sortBy} totalCount={orders.length} />
      <table>
        {/* table body unchanged */}
      </table>
    </div>
  );
}
Pro Tip: Serialize Dates to ISO Strings at the Fetch Layer
Convert Date objects to ISO strings (toISOString()) in your database query layer, not at the component boundary. This ensures every prop crossing the Server/Client boundary is already JSON-safe.
Production Insight
Large datasets passed as props to Client Components create multi-megabyte RSC payloads.
Render data-heavy UI (tables, lists) as Server Components — pass only interaction state to the client.
Rule: if the Client Component doesn't need to transform the data, don't send the data — render it on the server.
Key Takeaway
Server-to-Client prop boundaries require JSON serialization — Date, Map, Set, and functions throw hard errors.
Render data-heavy UI on the server; pass only interaction state to Client Components.
Serialization Boundary Decisions
IfComponent displays data but needs client-side sorting/filtering
UseServer Component renders table; Client Component provides sort/filter controls
IfComponent needs to transform data on the client (grouping, aggregation)
UsePass raw data to Client Component — but paginate or limit to reduce payload size
IfProps contain Date, Map, Set, or class instances
UseConvert to JSON-safe types before passing — ISO string for Date, plain object for Map
IfThird-party component expects Date objects as props
UseWrap in a Client Component that converts ISO strings back to Date objects after hydration

Bundle Size Impact: Benchmarks from Production Migrations

The primary performance win of RSC is bundle size reduction. Every Server Component that doesn't import a Client Component ships zero JavaScript. For content-heavy pages (blogs, dashboards, documentation), this means 60-80% of the page ships no JavaScript at all.

Real production benchmarks from teams migrating to RSC (internal migration, n=12 dashboards on Moto G Power devices) show consistent patterns. A typical dashboard page with 15 components sees its client bundle drop from 450KB to 120KB when 10 components become Server Components. Time to Interactive (TTI) improves by 300-800ms on mid-range devices because the browser has less JavaScript to parse and execute.

io/thecodeforge/rsc-performance/app/products/page.tsxTSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Suspense } from 'react';
import { db } from '@/lib/db';
import { ProductCard } from './ProductCard';       // Server Component — zero JS
import { FilterBar } from './FilterBar';            // 'use client' — ships JS
import { AddToCartButton } from './AddToCartButton'; // 'use client' — ships JS

export default async function ProductsPage({
  searchParams,
}: {
  searchParams: { category?: string; sort?: string };
}) {
  const products = await db.product.findMany({ /* ... */ });

  return (
    <div>
      <FilterBar />
      <div className="grid grid-cols-4 gap-4">
        {products.map((product) => (
          <div key={product.id}>
            <ProductCard product={product} />
            <AddToCartButton productId={product.id} price={product.price} />
          </div>
        ))}
      </div>
    </div>
  );
}
Output
50 product cards: 0KB client JS. FilterBar: 8KB. AddToCartButton: 3KB per instance (deduplicated). Total client JS: ~11KB vs ~350KB without RSC.
The Bundle Size Formula
Client JS = sum of all 'use client' components + their imports. Server JS = 0 bytes on the client. To minimize client JS, maximize the percentage of your component tree that has no 'use client' marker.
Production Insight
Content-heavy pages (blogs, listings, docs) see 60-80% bundle reduction with RSC.
Highly interactive pages (editors, real-time dashboards) see smaller gains — most components need 'use client'.
Rule: measure client JS before and after RSC migration — if bundle didn't shrink, 'use client' is leaking.
Key Takeaway
RSC's primary performance win is bundle size — Server Components ship zero JavaScript to the client.
Content-heavy pages see 60-80% reduction; interactive pages see smaller gains.
Punchline: measure client JS before and after migration — if the bundle didn't shrink, 'use client' is leaking at too high a level in the tree.
Bundle Optimization Decisions
IfPage is mostly content display with 1-2 interactive elements
UseRSC delivers maximum benefit — 60-80% bundle reduction
IfPage is highly interactive (real-time, drag-and-drop, rich editing)
UseRSC benefit is smaller — focus on code-splitting and lazy-loading Client Components
IfThird-party component ships large JS (chart.js, monaco-editor)
UseWrap in 'use client' + next/dynamic with ssr:false — lazy-load only when visible
IfBundle size didn't change after migration
Usegrep -r 'use client' app/ — find where the boundary is leaking and push it deeper

Streaming and Suspense: Progressive Rendering for Perceived Performance

RSC integrates with React Suspense to enable streaming — the server sends HTML progressively as each Suspense boundary resolves. The user sees the page shell immediately, then content fills in as data becomes available.

In 2026, most real-world RSC gains come from Partial Prerendering (PPR), which combines a static shell with dynamic holes that stream in. This is the default behavior in Next.js 15+ when you use Suspense boundaries.

io/thecodeforge/rsc-performance/app/analytics/page.tsxTSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import { Suspense } from 'react';

// Each section is an independent Server Component with its own fetch
// Each Suspense boundary streams independently

export default function AnalyticsPage() {
  return (
    <div>
      <h1>Analytics</h1>

      {/* Shell renders instantly — skeletons show immediately */}
      <div className="grid grid-cols-2 gap-6">
        <Suspense fallback={<MetricSkeleton label="Revenue" />}>
          <RevenueMetrics />    {/* streams at ~120ms */}
        </Suspense>

        <Suspense fallback={<MetricSkeleton label="Users" />}>
          <UserMetrics />       {/* streams at ~200ms */}
        </Suspense>

        <Suspense fallback={<ChartSkeleton />}>
          <ConversionChart />   {/* streams at ~350ms */}
        </Suspense>

        <Suspense fallback={<TableSkeleton rows={5} />}>
          <TopProductsTable />  {/* streams at ~180ms */}
        </Suspense>
      </div>
    </div>
  );
}

// Skeleton components — render instantly, no layout shift
function MetricSkeleton({ label }: { label: string }) {
  return (
    <div className="border rounded-lg p-6 animate-pulse">
      <div className="text-sm text-gray-400 mb-2">{label}</div>
      <div className="h-8 bg-gray-200 rounded w-24" />
      <div className="h-4 bg-gray-200 rounded w-16 mt-2" />
    </div>
  );
}

function ChartSkeleton() {
  return <div className="h-64 bg-gray-200 rounded-lg animate-pulse" />;
}

function TableSkeleton({ rows }: { rows: number }) {
  return (
    <div className="space-y-2">
      {Array.from({ length: rows }).map((_, i) => (
        <div key={i} className="h-10 bg-gray-200 rounded animate-pulse" />
      ))}
    </div>
  );
}
Streaming Mental Model
  • Each Suspense boundary is independent — it streams as soon as its data resolves
  • Skeleton fallbacks show immediately — zero layout shift, instant FCP
  • The LCP element appears at max(fetch_times), not sum(fetch_times)
  • Without Suspense, the entire page blocks on the slowest fetch — streaming eliminates this
  • Wrap every independently-fetched section in its own Suspense boundary for maximum progressive rendering
Production Insight
Streaming with Suspense reduces FCP to sub-100ms — the shell renders instantly while data loads.
Each Suspense boundary is independent — the slowest fetch doesn't block the fastest.
Rule: wrap every independently-fetched section in Suspense with a skeleton fallback — one boundary per data source.
Key Takeaway
Streaming sends HTML progressively — the shell renders at 0ms, each section fills in as its data resolves.
Each Suspense boundary is independent — the slowest fetch doesn't block the fastest.
Punchline: wrap every independently-fetched section in its own Suspense boundary with a skeleton fallback — one boundary per data source.
Suspense Boundary Placement
IfSection fetches data independently from other sections
UseWrap in its own Suspense boundary — streams as soon as its fetch resolves
IfSection depends on data from a parent section
UseNo separate Suspense — it will resolve with or after its parent
IfSection has no async data (static content)
UseNo Suspense needed — renders immediately with the shell
IfSection is a Client Component that fetches on mount
UseConsider moving the fetch to a Server Component parent — RSC fetch is faster than client fetch
● Production incidentPOST-MORTEMseverity: high

'use client' at layout root ships 2.1MB of JavaScript — RSC benefits completely negated

Symptom
Client JavaScript bundle was 2.1MB — unchanged from before the RSC migration. LCP was 3.2s. No tree-shaking improvement. No code splitting benefit. The app behaved exactly like a traditional React SPA.
Assumption
The team assumed RSC was enabled by default in Next.js 14 and would automatically reduce bundle size. They didn't realize 'use client' at the root layout propagates to the entire tree.
Root cause
A developer added 'use client' to the root layout.tsx to use a ThemeProvider (which needs useState). Because 'use client' propagates downward, every page, every component, and every import became a Client Component. The RSC runtime was active but never used — every component was opted out.
Fix
Moved ThemeProvider into a separate ClientWrapper component marked 'use client'. Removed 'use client' from layout.tsx. Wrapped only the ThemeProvider in the layout, keeping all pages as Server Components by default. Bundle dropped from 2.1MB to 680KB. LCP improved from 3.2s to 1.4s.
Key lesson
  • 'use client' at any node marks that node AND all its children as Client Components — it propagates downward
  • Only the component that needs interactivity (useState, useEffect, onClick) should be marked 'use client'
  • Server Components are the default in Next.js App Router — adding 'use client' is an opt-out, not an opt-in
  • Measure bundle size before and after RSC migration — if it didn't change, 'use client' is leaking everywhere
Production debug guideCommon RSC performance failures and how to diagnose them6 entries
Symptom · 01
Client JavaScript bundle is unexpectedly large after RSC migration
Fix
Run next build and check the output. Look for large Client Component chunks. Use 'use client' boundary analysis: grep -r 'use client' app/ to find where client boundaries start.
Symptom · 02
Page loads slowly despite RSC — LCP is above 2.5s
Fix
Check if the slow component is a Client Component. In React DevTools Components tab, Server Components have a 'server' badge and won't show hooks.
Symptom · 03
RSC fetch waterfall — data loads sequentially instead of in parallel
Fix
Check if await is used in a parent Server Component before rendering child components that also fetch. Move fetch calls into the components that use the data, not their parents.
Symptom · 04
Serialization error: 'Only plain objects can be passed to Client Components'
Fix
A Server Component is passing a Date, Map, Set, function, or class instance as a prop to a Client Component. Convert to JSON-safe types (ISO string for Date, plain object for Map).
Symptom · 05
Hydration mismatch error in production
Fix
A Client Component renders different HTML on server vs client (e.g., using Date.now() or Math.random() during render). Make the render deterministic or move the dynamic logic into useEffect.
Symptom · 06
Server Component re-renders on every navigation
Fix
Check if the component is inside a 'use client' boundary. Server Components should not re-render on client navigation — they're cached by the server. If re-rendering, it's likely a Client Component.
Traditional React vs React Server Components
MetricTraditional React (CSR)React Server Components
Client JS shipped100% of componentsOnly 'use client' components and their imports
Data fetchinguseEffect on client — creates waterfallsasync/await on server — parallel by default
Time to First ByteFast (static shell)Fast (static shell) — same
First Contentful PaintSlow (JS must parse first)Fast (HTML streams immediately)
Largest Contentful PaintBlocked by JS bundleStreams as soon as its Suspense boundary resolves
Time to InteractiveAfter full JS parse + hydrationAfter only Client Components hydrate — much faster
Bundle size (typical dashboard)450KB120KB (73% reduction)
SEORequires SSR or pre-renderingServer-rendered HTML by default — full SEO support

Key takeaways

1
RSC ships zero JavaScript for Server Components
the primary performance win is bundle size reduction.
2
'use client' is a boundary marker that propagates downward
place it at the deepest interactive leaf, not the page root.
3
Parallel Server Component fetches eliminate waterfalls
total load time is max(fetch_times), not sum(fetch_times).
4
Serialization boundaries require JSON-safe props
Date, Map, Set, and functions throw hard errors at the Server/Client edge.
5
Streaming with Suspense gives sub-100ms FCP
wrap every independently-fetched section in its own boundary.

Common mistakes to avoid

4 patterns
×

Placing 'use client' at the layout or page root

Symptom
Client JavaScript bundle is identical to pre-migration size. Lighthouse scores show no improvement. Every component in the app is a Client Component because 'use client' propagates downward.
Fix
Remove 'use client' from layout.tsx and page.tsx. Only mark the specific leaf components that need useState, useEffect, or event handlers. Keep the tree Server Components by default.
×

Passing non-serializable props across the Server/Client boundary

Symptom
Next.js throws a hard error: 'Only plain objects can be passed to Client Components from Server Components'.
Fix
Define a toPlain() layer at your DB/ORM edge: createdAt: row.createdAt.toISOString(). Never pass rich types across the boundary.
×

Creating fetch waterfalls by awaiting in parent Server Components

Symptom
Page loads sequentially — section 1 appears, then section 2, then section 3. Total load time is sum of all fetch times instead of the maximum.
Fix
Move fetch calls into the child components that consume the data. Each child fetches independently and streams via its own Suspense boundary.
×

Not using Suspense boundaries for streaming

Symptom
Page blocks on the slowest fetch — no content appears until all data is ready. FCP is high because the server waits for every fetch before sending any HTML.
Fix
Wrap each independently-fetched section in a Suspense boundary with a skeleton fallback. The shell renders instantly, and each section streams in as its fetch resolves.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the difference between Server Components and Server-Side Renderi...
Q02SENIOR
What happens when you place 'use client' at the root of your component t...
Q03SENIOR
How does RSC eliminate the data fetching waterfall problem? Walk through...
Q04SENIOR
What are the serialization constraints at the Server/Client component bo...
Q05SENIOR
How does streaming with Suspense improve perceived performance, and when...
Q01 of 05SENIOR

Explain the difference between Server Components and Server-Side Rendering (SSR) in React. Are they the same thing?

ANSWER
No. SSR and RSC both render on the server and stream HTML. The difference is what ships as JavaScript. SSR hydrates every component (so all JS ships). RSC renders components that never hydrate — their JS is 0 bytes. Think: SSR = where rendering happens, RSC = if JavaScript ships at all.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
Can a Server Component use hooks like useState or useEffect?
02
Can a Client Component import a Server Component?
03
How do I measure the performance impact of RSC in my app?
04
Do RSC work with third-party component libraries like MUI or Chakra?
🔥

That's React.js. Mark it forged?

3 min read · try the examples if you haven't

Previous
Prisma ORM Best Practices with Next.js 16 in 2026
33 / 47 · React.js
Next
Server Actions vs tRPC in 2026: When to Use Which?