Server Components

Los Server Components son una de las características más importantes de NextJS 15. Se renderizan en el servidor y envían HTML al cliente, resultando en aplicaciones más rápidas y eficientes.

¿Qué son los Server Components?

Los Server Components son componentes de React que se ejecutan solo en el servidor. No se envían al navegador, lo que significa:

  • Menos JavaScript en el cliente
  • Acceso directo a bases de datos y APIs
  • Mejor SEO porque el contenido está en el HTML
  • Carga más rápida de la página inicial
ℹ️
Por defecto

En NextJS 15 con App Router, todos los componentes son Server Components por defecto. No necesitas hacer nada especial para usarlos.

Tu primer Server Component

Cualquier componente en app/ es un Server Component automáticamente:

// app/page.tsx
// Este es un Server Component (por defecto)

async function getData() {
  const res = await fetch('https://api.ejemplo.com/datos')
  return res.json()
}

export default async function HomePage() {
  const datos = await getData()
  
  return (
    <div>
      <h1>Datos del Servidor</h1>
      <p>{datos.mensaje}</p>
    </div>
  )
}
¡Nota el async!

Los Server Components pueden ser async, lo cual es perfecto para obtener datos directamente en el componente.

Ventajas de Server Components

1. Acceso directo a recursos del servidor

Puedes acceder a bases de datos, leer archivos, y usar secrets sin exponer nada al cliente:

// Server Component
import { db } from '@/lib/database'

export default async function UsersPage() {
  // Acceso directo a la base de datos
  const users = await db.query('SELECT * FROM users')
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}
💡

Las credenciales y claves API usadas en Server Components nunca llegan al navegador.

2. Menos JavaScript en el navegador

Solo el HTML se envía al cliente. Esto significa aplicaciones más rápidas:

// Este componente y todas sus dependencias
// NO se envían al navegador
import { format } from 'date-fns' // Librería pesada
import { processData } from '@/lib/heavy-utils'

export default function ServerComponent({ data }) {
  const processed = processData(data) // Se ejecuta en el servidor
  const formatted = format(new Date(), 'PPP') // Solo en el servidor
  
  return <div>{processed} - {formatted}</div>
}

3. Mejor SEO

El contenido está en el HTML desde el inicio:

async function getArticle(slug: string) {
  const article = await fetch(`https://api.blog.com/articles/${slug}`)
  return article.json()
}

export default async function ArticlePage({ params }) {
  const article = await getArticle(params.slug)
  
  // Este contenido está en el HTML inicial
  // Los motores de búsqueda lo ven inmediatamente
  return (
    <article>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
    </article>
  )
}

Obteniendo datos en Server Components

Fetch directo con async/await

export default async function PostsPage() {
  const res = await fetch('https://api.blog.com/posts')
  const posts = await res.json()
  
  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  )
}

Múltiples requests en paralelo

export default async function DashboardPage() {
  // Estas requests se ejecutan en paralelo
  const [users, posts, comments] = await Promise.all([
    fetch('https://api.com/users').then(r => r.json()),
    fetch('https://api.com/posts').then(r => r.json()),
    fetch('https://api.com/comments').then(r => r.json()),
  ])
  
  return (
    <div>
      <Users data={users} />
      <Posts data={posts} />
      <Comments data={comments} />
    </div>
  )
}

Con base de datos

import { db } from '@/lib/database'

export default async function ProductsPage() {
  const products = await db.product.findMany({
    where: { active: true },
    include: { category: true },
  })
  
  return (
    <div className="grid grid-cols-3 gap-4">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  )
}
⚠️
Importante

Las queries a base de datos deben usar variables de entorno (env vars) para las credenciales. Estas nunca se exponen al cliente.

Composición de Server Components

Puedes anidar Server Components sin problemas:

// Todos estos son Server Components

// app/page.tsx
export default function HomePage() {
  return (
    <div>
      <Header />
      <MainContent />
      <Sidebar />
      <Footer />
    </div>
  )
}

// components/MainContent.tsx
async function getPosts() {
  const res = await fetch('https://api.blog.com/posts')
  return res.json()
}

export default async function MainContent() {
  const posts = await getPosts()
  
  return (
    <main>
      {posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </main>
  )
}

// components/PostCard.tsx
export default function PostCard({ post }) {
  return (
    <article>
      <h2>{post.title}</h2>
      <p>{post.excerpt}</p>
    </article>
  )
}

Limitaciones de Server Components

Los Server Components NO pueden:

❌ Usar hooks de React (useState, useEffect, etc.) ❌ Usar event handlers (onClick, onChange, etc.) ❌ Usar browser APIs (window, localStorage, etc.) ❌ Usar Context API directamente

Ejemplo de lo que NO puedes hacer:

// ❌ ESTO NO FUNCIONA en Server Components
export default function BadExample() {
  const [count, setCount] = useState(0) // ❌ No hooks
  
  const handleClick = () => {  // ❌ No event handlers
    setCount(count + 1)
  }
  
  return <button onClick={handleClick}>Count: {count}</button>
}
ℹ️

Para interactividad, necesitas Client Components. Aprende más en la siguiente sección.

Cuándo usar Server Components

Usa Server Components cuando:

  • ✅ Necesitas obtener datos del servidor
  • ✅ Accedes a bases de datos o APIs backend
  • ✅ Quieres reducir el JavaScript del cliente
  • ✅ Necesitas usar secrets/keys de forma segura
  • ✅ El contenido es estático o no requiere interactividad

Patterns comunes

Pattern 1: Fetch + Render

async function getUser(id: string) {
  const user = await db.user.findUnique({ where: { id } })
  return user
}

export default async function UserProfile({ params }) {
  const user = await getUser(params.id)
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}

Pattern 2: Composición jerárquica

// Layout fetches data and passes down
export default async function DashboardLayout({ children }) {
  const user = await getCurrentUser()
  
  return (
    <div>
      <Sidebar user={user} />
      <main>{children}</main>
    </div>
  )
}

Pattern 3: Streaming con Suspense

import { Suspense } from 'react'

export default function Page() {
  return (
    <div>
      <h1>Mi Dashboard</h1>
      <Suspense fallback={<LoadingSkeleton />}>
        <SlowComponent />
      </Suspense>
    </div>
  )
}

async function SlowComponent() {
  const data = await fetch('https://slow-api.com/data')
  return <div>{/* Renderizar data */}</div>
}

Metadata en Server Components

Puedes exportar metadata estática o dinámica:

Estática

export const metadata = {
  title: 'Mi Página',
  description: 'Descripción de mi página',
}

export default function Page() {
  return <h1>Contenido</h1>
}

Dinámica

export async function generateMetadata({ params }) {
  const post = await getPost(params.id)
  
  return {
    title: post.title,
    description: post.excerpt,
  }
}

export default async function PostPage({ params }) {
  const post = await getPost(params.id)
  return <article>{/* ... */}</article>
}

Próximos pasos

Para agregar interactividad a tu aplicación, aprende sobre:

  1. Client Components - Para interactividad
  2. Composición - Combinar Server y Client
  3. Data Fetching - Patterns avanzados de datos

Resumen
  • Server Components son el tipo por defecto en NextJS 15
  • Se ejecutan solo en el servidor = menos JavaScript en el cliente
  • Perfectos para obtener datos, acceder a bases de datos y SEO
  • No pueden usar hooks, event handlers o browser APIs
  • Para interactividad, usa Client Components