Server Components

En NextJS 16, todos los componentes son Server Components por defecto. Se ejecutan en el servidor, no envian JavaScript al cliente, y pueden acceder directamente a bases de datos, APIs y el filesystem.

Por que Server Components

tsx
// app/productos/page.tsx — Server Component por defecto
import { db } from "@/lib/database"

export default async function ProductosPage() {
  // Esto se ejecuta en el servidor
  // No se envia al cliente
  const productos = await db.producto.findMany({
    where: { activo: true },
    orderBy: { createdAt: "desc" },
  })

  return (
    <div>
      <h1>Productos ({productos.length})</h1>
      <ul>
        {productos.map((p) => (
          <li key={p.id}>
            {p.nombre} - ${p.precio}
          </li>
        ))}
      </ul>
    </div>
  )
}

Ventajas:

  • Cero JavaScript al cliente para este componente
  • Acceso directo a la DB sin necesitar una API intermedia
  • Mejor SEO porque el HTML llega completo al navegador
  • Mas rapido porque no hay hidratacion

Cuando usar "use client"

Agrega "use client" solo cuando necesites:

  • Event handlers: onClick, onChange, onSubmit
  • Hooks de React: useState, useEffect, useRef
  • APIs del navegador: localStorage, window, document
tsx
"use client"
import { useState } from "react"

export default function Contador() {
  const [count, setCount] = useState(0)

  return (
    <button onClick={() => setCount(count + 1)}>
      Clicks: {count}
    </button>
  )
}

Composicion: Server + Client

El patron mas comun es tener un Server Component como padre que obtiene datos, y Client Components como hijos para la interactividad:

tsx
// app/dashboard/page.tsx — Server Component
import { getVentas } from "@/lib/data"
import VentasChart from "./VentasChart"
import FiltroFechas from "./FiltroFechas"

export default async function DashboardPage() {
  const ventas = await getVentas()

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

      {/* Client Component: necesita interactividad */}
      <FiltroFechas />

      {/* Client Component: necesita un chart library */}
      <VentasChart data={ventas} />

      {/* Server Component: solo muestra datos */}
      <ResumenVentas total={ventas.length} />
    </div>
  )
}
tsx
// app/dashboard/VentasChart.tsx — Client Component
"use client"
import { BarChart } from "recharts"

export default function VentasChart({ data }: { data: Venta[] }) {
  return <BarChart data={data} />
}
tsx
// app/dashboard/ResumenVentas.tsx — Server Component (no necesita "use client")
export default function ResumenVentas({ total }: { total: number }) {
  return (
    <div className="bg-gray-800 p-4 rounded">
      <p className="text-sm text-gray-400">Total ventas</p>
      <p className="text-2xl font-bold">{total}</p>
    </div>
  )
}

Pasar Server Components como children

Puedes pasar Server Components dentro de Client Components usando children:

tsx
// Client Component que maneja el layout
"use client"
import { useState } from "react"

export default function Tabs({ children }: { children: React.ReactNode }) {
  const [activeTab, setActiveTab] = useState(0)

  return (
    <div>
      <div className="flex gap-2">
        <button onClick={() => setActiveTab(0)}>Tab 1</button>
        <button onClick={() => setActiveTab(1)}>Tab 2</button>
      </div>
      <div>{children}</div>
    </div>
  )
}
tsx
// Server Component padre
import Tabs from "./Tabs"
import DatosServidor from "./DatosServidor"

export default function Page() {
  return (
    <Tabs>
      {/* DatosServidor es Server Component, se renderiza en el servidor */}
      <DatosServidor />
    </Tabs>
  )
}
Regla simple

Empieza todo como Server Component. Solo agrega "use client" cuando el compilador te lo pida (porque usas useState, onClick, etc.).

v16Cambio Con React 19, los Server Components tienen mejor soporte para streaming y Suspense. Si usas async en un componente servidor con Suspense, el contenido se envía al cliente progresivamente.