SSR en Next.js

Qué es Server Side Rendering, cómo Next.js genera HTML en el servidor, qué es el RSC Payload, cómo funciona la hidratación y cuándo elegir SSR sobre SSG o CSR.

SSR en Next.js

Serie: Entendiendo Next.js — Este artículo es parte de la serie "Entendiendo Next.js", donde exploramos cómo funciona Next.js internamente.

¿Qué es Server Side Rendering?

Server Side Rendering (SSR) es una técnica de renderizado, no un tipo de componente. Consiste en que el servidor genera el HTML completo de una página y lo envía listo al navegador, en lugar de que sea el navegador quien lo construya desde cero con JavaScript.

SSR vs CSR vs SSG

Antes de profundizar, vale la pena entender las diferencias entre las tres técnicas principales de renderizado:

Técnica¿Cuándo se genera el HTML?Caso de uso típico
CSR (Client Side Rendering)En el navegador, con JavaScriptApps internas, dashboards
SSR (Server Side Rendering)En el servidor, en cada requestE-commerce, contenido dinámico
SSG (Static Site Generation)En build time, una sola vezBlogs, documentación, landing pages

SSR es la opción correcta cuando el contenido cambia frecuentemente y necesita estar actualizado en cada visita. Si el contenido es siempre el mismo para todos los usuarios, SSG es más eficiente. Si no necesitás SEO y el contenido es completamente personalizado, CSR puede ser suficiente.

¿Qué pasa cuando un usuario entra a una página con SSR?

Usuario
   ↓
Request al servidor
   ↓
Next.js ejecuta los Server Components
   ↓
Genera HTML + RSC Payload
   ↓
El navegador muestra el HTML (el usuario ya ve contenido)
   ↓
React hidrata los Client Components
   ↓
La página es interactiva

Cuando un usuario visita la página por primera vez, ocurre lo siguiente:

  • El usuario hace una request (entra a la URL)
  • El servidor ejecuta el código y genera el HTML completo
  • El navegador recibe ese HTML y lo muestra inmediatamente
  • Se descarga el JavaScript de React junto con el RSC Payload
  • React realiza la hidratación, adjunta los event listeners al HTML ya existente
  • La página es completamente interactiva

En navegaciones posteriores dentro de la app, el comportamiento cambia: Next.js no vuelve a pedir HTML completo al servidor. En su lugar, solicita un nuevo RSC Payload, que React usa para actualizar solo las partes necesarias de la interfaz. El servidor sigue participando, pero de forma mucho más liviana.

¿Qué es el RSC Payload?

Cuando Next.js renderiza en el servidor, no solo envía HTML. Envía dos cosas al navegador:

  • HTML — para mostrar contenido al usuario de forma inmediata
  • RSC Payload — una representación binaria compacta del árbol de Server Components

El RSC Payload contiene el resultado renderizado de los Server Components, referencias a los Client Components que deben ejecutarse en el cliente, y los props que se les pasan. React lo usa para reconciliar el árbol de componentes sin volver a construir el DOM desde cero.

¿Qué es la hidratación?

La hidratación es el proceso donde React toma el HTML ya renderizado por el servidor y le adjunta los event listeners y el estado de JavaScript, sin volver a construir el DOM desde cero.

Es importante entender esto: el usuario ve el contenido antes de que React termine de cargar. Primero llega el HTML visible, después React "despierta" esa interfaz y la vuelve interactiva.

¿Por qué usar SSR?

Hay dos razones principales:

SEO — los motores de búsqueda como Google reciben el HTML completo y listo para indexar. Si el contenido se genera en el cliente, los crawlers pueden no verlo correctamente.

Performance percibida — el usuario ve contenido real antes de que cargue todo el JavaScript. Esto mejora una métrica clave llamada First Contentful Paint (FCP), que mide cuánto tarda en aparecer el primer contenido en pantalla.

Ejemplo práctico: página de producto

Imaginá una tienda online. En la página de un producto:

  • El título, la descripción, las imágenes y el precio se renderizan en el servidor. Llegan listos al navegador.
  • El botón "Agregar al carrito" necesita manejar clicks y estado, por lo que se hidrata y ejecuta en el cliente.

SSR en Next.js App Router

En Next.js con App Router, todos los componentes son Server Components por defecto. Esto significa que se renderizan en el servidor y su JavaScript nunca llega al navegador.

tsx
// app/products/[id]/page.tsx
// Este es un Server Component — se ejecuta en el servidor

async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const product = await fetch(`https://api.example.com/products/${id}`)
    .then(res => res.json())

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>${product.price}</p>
      <AddToCartButton productId={product.id} />
    </div>
  )
}

export default ProductPage

El fetch se ejecuta en el servidor. El HTML llega completo al navegador.

Para el botón interactivo, creamos un Client Component con la directiva 'use client':

tsx
// components/AddToCartButton.tsx
'use client'

import { useState } from 'react'

function AddToCartButton({ productId }: { productId: string }) {
  const [added, setAdded] = useState(false)

  return (
    <button onClick={() => setAdded(true)}>
      {added ? 'Agregado ✓' : 'Agregar al carrito'}
    </button>
  )
}

export default AddToCartButton

Este componente sí se hidrata en el cliente porque necesita manejar eventos e interactividad.

El límite de 'use client'

Cuando marcás un archivo con 'use client', no solo ese componente pasa a ejecutarse en el cliente, sino que todos sus imports y componentes hijos también se consideran parte del bundle del cliente. No hace falta agregar la directiva en cada archivo hijo.

Por eso, una buena práctica es agregar 'use client' lo más abajo posible en el árbol de componentes, solo donde realmente se necesita interactividad. Así minimizás el JavaScript que se envía al navegador.

tsx
// layout.tsx — Server Component
import Search from './search'   // Client Component
import Logo from './logo'       // Server Component

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <nav>
        <Logo />
        <Search />  {/* Solo este componente (y sus hijos) va al cliente */}
      </nav>
      <main>{children}</main>
    </>
  )
}

Proteger código del servidor: server-only

En Next.js, es posible importar accidentalmente código del servidor en un Client Component, exponiendo API keys u otra lógica sensible. Para evitar esto, existe el paquete server-only:

bash
npm install server-only

Importándolo en cualquier archivo, Next.js generará un error en build time si ese archivo se usa dentro de un Client Component:

ts
// lib/data.ts
import 'server-only'

export async function getProducts() {
  const res = await fetch('https://api.example.com/products', {
    headers: { authorization: process.env.API_KEY },
  })
  return res.json()
}

Server Component vs Client Component

Server ComponentClient Component
Se ejecuta enServidorServidor + Cliente
Hidrata en el clienteNo
Puede usar useState, useEffectNo
Puede hacer fetch directoNo recomendado
Directiva necesariaNinguna'use client'

Cuándo usar SSR

SSR es ideal cuando:

  • El contenido cambia con frecuencia y necesita estar actualizado en cada request
  • El SEO es importante, como en blogs, e-commerce o landing pages
  • Querés que el usuario vea contenido real lo antes posible
Emanuel López

Emanuel López

Desarrollador de Software · Montevideo, Uruguay

Mantente al día

Te aviso cuando publique algo nuevo. Podés darte de baja cuando quieras.