DevToolBoxGRATIS
Blog

React Server Components: Guía Completa 2026

15 minpor DevToolBox

React Server Components (RSC) representan el mayor cambio arquitectonico desde los hooks.

Que son los RSC?

Componentes que se renderizan en el servidor.

Tres tipos de componentes en el modelo RSC.

Server vs Client

AspectoServer ComponentClient Component
Por defectoSiNo
DirectivaNinguna"use client"
JS clienteNingunoSi
useState/useEffectNo disponibleDisponible
EventosNo disponibleDisponible
Acceso DBSiNo
Sistema de archivosSiNo
Variables servidorSiNo
Componente asyncSiNo
Tamano bundleCeroAumenta

Arbol de decision

Should this component be a Server or Client Component?

  Does it need useState, useEffect, or useRef?
  ├── YES → Client Component ("use client")
  └── NO ↓

  Does it need event handlers (onClick, onChange, onSubmit)?
  ├── YES → Client Component ("use client")
  └── NO ↓

  Does it use browser-only APIs (window, localStorage, navigator)?
  ├── YES → Client Component ("use client")
  └── NO ↓

  Does it use third-party libraries that require hooks or browser APIs?
  ├── YES → Client Component ("use client")
  └── NO ↓

  Does it fetch data or access databases/file system?
  ├── YES → Server Component (default) ← BEST CHOICE
  └── NO → Server Component (default) ← still the default

Patrones RSC

Patron 1: Carga de datos servidor

Carga directa con async/await.

// app/users/page.tsx — Server Component (default, no directive needed)
import { db } from '@/lib/database';

// This component is async — only possible in Server Components
export default async function UsersPage() {
  // Direct database query — this code NEVER reaches the client
  const users = await db.user.findMany({
    orderBy: { createdAt: 'desc' },
    take: 50,
  });

  return (
    <div>
      <h1>Users ({users.length})</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.name} — {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

// Benefits:
// - Zero JavaScript sent to client for this component
// - No loading states needed (data is available before render)
// - Database credentials never exposed to the browser
// - No useEffect + fetch + useState dance

Patron 2: Composicion

Server puede renderizar Client.

// components/LikeButton.tsx — Client Component
'use client';
import { useState } from 'react';

export function LikeButton({ postId }: { postId: string }) {
  const [liked, setLiked] = useState(false);
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => {
      setLiked(!liked);
      setCount(c => liked ? c - 1 : c + 1);
    }}>
      {liked ? 'Unlike' : 'Like'} ({count})
    </button>
  );
}

// app/posts/[id]/page.tsx — Server Component
import { db } from '@/lib/database';
import { LikeButton } from '@/components/LikeButton';

export default async function PostPage({ params }: { params: { id: string } }) {
  // Server: fetch data
  const post = await db.post.findUnique({ where: { id: params.id } });

  return (
    <article>
      {/* Server-rendered content (zero JS) */}
      <h1>{post?.title}</h1>
      <div>{post?.content}</div>

      {/* Client Component island (only this ships JS) */}
      <LikeButton postId={params.id} />
    </article>
  );
}

// Pattern: Server Component as children of Client Component
// components/Sidebar.tsx
'use client';
export function Sidebar({ children }: { children: React.ReactNode }) {
  const [open, setOpen] = useState(true);
  return (
    <aside style={{ display: open ? 'block' : 'none' }}>
      <button onClick={() => setOpen(!open)}>Toggle</button>
      {children}  {/* Server Component content passed as children */}
    </aside>
  );
}

Patron 3: Server Actions

Llamar funciones del servidor directamente.

// app/actions.ts — Server Action
'use server';

import { db } from '@/lib/database';
import { revalidatePath } from 'next/cache';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;

  await db.post.create({
    data: { title, content, authorId: 'user-1' },
  });

  revalidatePath('/posts');
}

export async function deletePost(postId: string) {
  await db.post.delete({ where: { id: postId } });
  revalidatePath('/posts');
}

// components/CreatePostForm.tsx — Client Component using Server Action
'use client';
import { createPost } from '@/app/actions';
import { useActionState } from 'react';

export function CreatePostForm() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="Post title" required />
      <textarea name="content" placeholder="Write your post..." required />
      <button type="submit">Create Post</button>
    </form>
  );
}

// Works without API routes!
// The form submits directly to the server action
// Progressive enhancement: works without JavaScript

Patron 4: Streaming

La carga lenta no bloquea la pagina.

// app/dashboard/page.tsx — Streaming with Suspense
import { Suspense } from 'react';

// Slow component — fetches from external API
async function AnalyticsChart() {
  const data = await fetch('https://api.analytics.com/data', {
    next: { revalidate: 60 },
  });
  const analytics = await data.json();
  return <div>Chart: {analytics.visitors} visitors</div>;
}

// Fast component — fetches from local DB
async function RecentPosts() {
  const posts = await db.post.findMany({ take: 5 });
  return (
    <ul>
      {posts.map(p => <li key={p.id}>{p.title}</li>)}
    </ul>
  );
}

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

      {/* This renders immediately */}
      <Suspense fallback={<div>Loading posts...</div>}>
        <RecentPosts />
      </Suspense>

      {/* This streams in when ready (doesn't block the page) */}
      <Suspense fallback={<div>Loading analytics...</div>}>
        <AnalyticsChart />
      </Suspense>
    </div>
  );
}

// Result: User sees "Dashboard" + posts immediately
// Analytics chart streams in when the API responds
// No client-side loading spinners needed

Estrategias de carga

Secuencial

Cuando las peticiones dependen.

// Sequential: second request depends on first
async function UserProfile({ userId }: { userId: string }) {
  const user = await getUser(userId);           // 1st request
  const posts = await getUserPosts(user.name);  // 2nd (depends on user.name)

  return <div>{user.name}: {posts.length} posts</div>;
}

Paralelo

Promise.all para peticiones independientes.

// Parallel: independent requests — use Promise.all
async function Dashboard() {
  // Start all requests simultaneously
  const [users, posts, analytics] = await Promise.all([
    getUsers(),
    getPosts(),
    getAnalytics(),
  ]);

  return (
    <div>
      <UserList users={users} />
      <PostList posts={posts} />
      <AnalyticsPanel data={analytics} />
    </div>
  );
}

Cache y revalidacion

Next.js extiende fetch con control de cache.

// Next.js fetch caching strategies

// 1. Static (cached indefinitely until revalidated)
const data = await fetch('https://api.example.com/data');
// Equivalent to: { cache: 'force-cache' }

// 2. Revalidate every 60 seconds (ISR)
const data = await fetch('https://api.example.com/data', {
  next: { revalidate: 60 },
});

// 3. Dynamic (no caching — fresh on every request)
const data = await fetch('https://api.example.com/data', {
  cache: 'no-store',
});

// 4. Tag-based revalidation
const data = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] },
});

// In a Server Action:
import { revalidateTag } from 'next/cache';
revalidateTag('posts');  // Purge all fetches tagged 'posts'

Ventajas de rendimiento

RSC ofrece varias ventajas de rendimiento.

RSC Performance Benefits:

1. Zero Client JavaScript for Server Components
   - Only Client Components ship JS to the browser
   - Large libraries used on server (markdown, syntax highlighting) add zero bundle

2. Streaming SSR
   - HTML streams to browser as components resolve
   - First Contentful Paint (FCP) is faster
   - Time to Interactive (TTI) is faster (less JS to hydrate)

3. Automatic Code Splitting
   - Each Client Component is automatically code-split
   - No manual dynamic imports needed

4. Reduced Data Transfer
   - Server Components fetch data on the server (same network)
   - No client-side fetch waterfalls
   - No duplicate data in both HTML and JS payload

5. Smaller Hydration Payload
   - Only Client Components need hydration
   - Server Component output is static — no hydration needed

Typical Results:
   Before RSC: 450KB JS bundle, 2.5s TTI
   After RSC:  180KB JS bundle, 1.1s TTI (60% JS reduction)

Errores comunes

// Mistake 1: Adding "use client" unnecessarily
// BAD: This doesn't need client-side JavaScript
'use client';  // REMOVE THIS
export function UserCard({ name, email }: { name: string; email: string }) {
  return <div>{name} ({email})</div>;  // No hooks, no events = Server Component
}

// Mistake 2: Importing Server Component in Client Component
// BAD: This won't work
'use client';
import { ServerComponent } from './ServerComponent';  // ERROR
// FIX: Pass as children instead
export function ClientWrapper({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>;
}

// Mistake 3: Using "use client" too high in the tree
// BAD: Making a layout a Client Component
'use client';  // This makes EVERYTHING below it client-side
export function Layout({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState('dark');
  return <div className={theme}>{children}</div>;
}
// FIX: Extract the interactive part into a small Client Component
// components/ThemeToggle.tsx
'use client';
export function ThemeToggle() {
  const [theme, setTheme] = useState('dark');
  return <button onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>{theme}</button>;
}
// layout.tsx — stays as Server Component
export function Layout({ children }: { children: React.ReactNode }) {
  return <div><ThemeToggle />{children}</div>;
}

Preguntas frecuentes

RSC sin Next.js?

Tecnicamente si. Practicamente Next.js es el framework principal.

RSC reemplaza SSR?

No, lo complementa.

Cuando "use client"?

Cuando necesitas hooks o APIs del navegador.

Context en Server Components?

No disponible.

Impacto SEO?

Positivo. HTML generado en el servidor.

Herramientas y guias relacionadas

𝕏 Twitterin LinkedIn
¿Fue útil?

Mantente actualizado

Recibe consejos de desarrollo y nuevas herramientas.

Sin spam. Cancela cuando quieras.

Prueba estas herramientas relacionadas

{ }JSON FormatterJSJavaScript Minifier

Artículos relacionados

Next.js App Router: Guia de migracion completa 2026

Domina el Next.js App Router con esta guia completa. Server Components, obtencion de datos, layouts, streaming, Server Actions y migracion paso a paso desde Pages Router.

Guia completa de React Hooks: useState, useEffect y Hooks personalizados

Domina React Hooks con ejemplos practicos. useState, useEffect, useContext, useReducer, useMemo, useCallback, hooks personalizados y hooks concurrentes de React 18+.

Optimización React: 15 consejos prácticos

Optimiza apps React con 15 técnicas probadas.