Skip to main content

The State of Server Components in 2026

·PkgPulse Team

TL;DR

RSC went from experimental to the default. Most new Next.js apps are RSC-first. React Server Components, introduced in Next.js 13 (app router), hit mainstream adoption in 2025. By 2026, Next.js 15 ships with RSC as the default, Remix v3 adopted RSC, and the broader React ecosystem is catching up. The practical effect: fewer kilobytes sent to browsers, fundamentally different mental models for data fetching, and a graveyard of client-side libraries that need RSC-compatible updates.

Key Takeaways

  • Next.js 15 — RSC is the default; every component is a server component unless you mark 'use client'
  • Bundle savings — RSC moves logic to server; real apps see 30-60% smaller JS bundles
  • Data fetchingasync/await in components replaces useEffect + useState for server data
  • The 'use client' boundary — marks where server code ends and client code begins
  • Package compat — CSS-in-JS, browser APIs, event listeners = 'use client' only

What RSC Actually Changes

The Mental Model Shift

// Before RSC (client-side data fetching)
// Runs in browser: full component tree + data in JS bundle
'use client';

import { useState, useEffect } from 'react';

function PackageList() {
  const [packages, setPackages] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/packages')
      .then(r => r.json())
      .then(data => {
        setPackages(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>Loading...</div>;
  return <ul>{packages.map(p => <li key={p.name}>{p.name}</li>)}</ul>;
}
// Sends to browser: React runtime (~45KB) + this component + data via fetch
// With RSC (server-side, async component)
// Runs on server: only HTML + minimal client JS sent to browser
import { db } from '@/lib/db';

async function PackageList() {
  // Direct DB query — no API route needed, no loading state
  const packages = await db.select().from(packagesTable).limit(20);

  return (
    <ul>
      {packages.map(p => (
        <li key={p.name}>{p.name}</li>
      ))}
    </ul>
  );
}
// Sends to browser: HTML (no JS for this component)

This is the fundamental shift: components that used to ship JavaScript to the browser now run entirely on the server.


Framework Adoption in 2026

Next.js 15 (App Router = RSC Default)

// app/packages/page.tsx — server component by default
import { Suspense } from 'react';

// This is a server component — async, no 'use client'
export default async function PackagesPage({
  searchParams,
}: {
  searchParams: { q?: string };
}) {
  // Direct server-side logic
  const query = searchParams.q ?? '';

  return (
    <main>
      <SearchBar defaultValue={query} />  {/* 'use client' component */}
      <Suspense fallback={<PackagesSkeleton />}>
        <PackageResults query={query} />  {/* Server component */}
      </Suspense>
    </main>
  );
}

// app/packages/_components/PackageResults.tsx
async function PackageResults({ query }: { query: string }) {
  const packages = await searchPackages(query);  // Server-side, direct DB/API
  return <PackageGrid packages={packages} />;
}

// app/packages/_components/SearchBar.tsx
'use client';

import { useRouter } from 'next/navigation';

export function SearchBar({ defaultValue }: { defaultValue: string }) {
  const router = useRouter();
  // Client-side interactivity — this IS sent as JavaScript
  return (
    <input
      defaultValue={defaultValue}
      onChange={e => router.push(`?q=${e.target.value}`)}
    />
  );
}

The 'use client' Boundary

// Server component passing data to client component
// server-component.tsx (no directive = server)
import { ClientChart } from './ClientChart';

async function DownloadStats({ packageName }: { packageName: string }) {
  // Runs on server
  const stats = await getDownloadStats(packageName);

  // Can pass serializable data to client components
  return <ClientChart data={stats} />;
  // ✅ Can pass: strings, numbers, arrays, plain objects, Date
  // ❌ Cannot pass: functions, class instances, non-serializable objects
}

// ClientChart.tsx
'use client';
import { AreaChart } from 'recharts';

export function ClientChart({ data }: { data: DownloadStat[] }) {
  // Runs in browser — can use browser APIs, event handlers
  return <AreaChart data={data} />;
}

The Package Ecosystem Impact

What Broke (or Had to Adapt)

  1. CSS-in-JS (runtime) — styled-components, Emotion: inject styles via JS. RSC incompatible. → Teams moved to Tailwind, Panda CSS, CSS Modules.

  2. Context providerscreateContext requires 'use client'. Provider components must be client components. Common pattern: thin client wrapper around server content.

  3. Browser APIs in componentswindow, localStorage, navigator: only in 'use client'. Many libraries updated with typeof window !== 'undefined' guards.

  4. Event handlersonClick, onChange etc. can only be defined in client components.

What Thrived

  1. TanStack Query — Added @tanstack/query-server for RSC-native patterns
  2. Drizzle / Prisma — Direct DB queries in server components — the main use case
  3. Zod — Schema validation on both server and client components
  4. next-intl — i18n library rebuilt for RSC from the ground up
  5. Auth.js (NextAuth v5) — Session available in server components via auth() helper

Common RSC Patterns in 2026

Pattern 1: Server Component Data + Client Interactivity

// ProductPage — server fetches, client handles cart
async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id);  // Server

  return (
    <div>
      <ProductImages images={product.images} />      {/* Server — static HTML */}
      <h1>{product.name}</h1>                         {/* Server */}
      <AddToCartButton productId={product.id} />      {/* 'use client' */}
    </div>
  );
}

Pattern 2: Streaming with Suspense

// Parallel data fetching with streaming
async function Dashboard() {
  // These fetch in parallel and stream as they resolve
  return (
    <div>
      <Suspense fallback={<StatsSkeleton />}>
        <Stats />          {/* Streams when ready */}
      </Suspense>
      <Suspense fallback={<ChartSkeleton />}>
        <DownloadChart />  {/* Streams independently */}
      </Suspense>
      <Suspense fallback={<TableSkeleton />}>
        <PackageTable />   {/* Streams independently */}
      </Suspense>
    </div>
  );
}

Pattern 3: Server Actions (Forms Without APIs)

// Server Actions — form submission without API routes
async function CreatePackageForm() {
  async function createPackage(formData: FormData) {
    'use server';  // This function runs on the server!
    const name = formData.get('name') as string;
    await db.insert(packages).values({ name });
    redirect('/packages');
  }

  return (
    <form action={createPackage}>
      <input name="name" placeholder="Package name" />
      <button type="submit">Create</button>
    </form>
  );
  // No fetch(), no API route, no useEffect — just a server function
}

Adoption Reality

Honest assessment of where RSC stands in 2026:

SegmentRSC AdoptionNotes
New Next.js projects~75%App router is default since Next.js 14
Existing Next.js (pages router)~20% migrationMany still on pages router
Remix~40%Remix v3 adopted RSC, still maturing
Non-Next.js React~10%Vite + React doesn't have RSC yet
Vue/Angular/SvelteN/ARSC is React-specific; Svelte has own server model

RSC is firmly mainstream for greenfield React apps, but the majority of production React apps are still client-rendered. The migration path is real but not trivial.


What RSC Doesn't Replace

RSC handles server data. It doesn't replace:

  • TanStack Query — still needed for client-side caching, background refetching, optimistic updates
  • Zustand/Jotai — still needed for global client state (modals, shopping cart, UI state)
  • React Hook Form — forms need client interactivity
  • Real-time (WebSockets) — server-push is still client-side

The pattern in 2026: RSC for initial data load → TanStack Query for subsequent client-side fetching → Zustand for ephemeral UI state.


Compare server framework package health on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.