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:
- Client Components - Para interactividad
- Composición - Combinar Server y Client
- 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