Next.js transforma React en un conjunto completo de herramientas para el desarrollo web moderno. Su última versión trae características revolucionarias que cambian nuestra forma de construir aplicaciones web. ¡Descubramos por qué Next.js se ha vuelto imprescindible!
Los puntos fuertes de Next.js 15 🎯
- App Router - Un sistema de enrutamiento moderno basado en el sistema de archivos
- Server Components - Componentes React renderizados en el servidor por defecto
- Server Actions - Mutaciones del servidor directamente desde tus componentes
- Streaming - Carga progresiva de páginas para una UX fluida
- Cache inteligente - Almacenamiento en caché automático de datos
- Zero config - Listo para usar con optimizaciones integradas
Inicio rápido ⚙️
bash
1# Creación de un nuevo proyecto
2npx create-next-app@latest mi-app --typescript --tailwind --app
3
4# Inicio del servidor de desarrollo
5cd mi-app
6npm run dev
Arquitectura moderna 📂
El App Router introduce una nueva estructura de proyecto más intuitiva:
plaintext
1mi-app/
2├── app/
3│ ├── layout.tsx # Layout raíz
4│ ├── page.tsx # Página de inicio (/)
5│ ├── loading.tsx # Carga global
6│ ├── error.tsx # Gestión de errores
7│ ├── not-found.tsx # Página 404
8│ ├── blog/
9│ │ ├── page.tsx # /blog
10│ │ └── [slug]/
11│ │ └── page.tsx # /blog/:slug
12│ └── api/
13│ └── route.ts # Rutas API
14├── components/
15│ ├── server/ # Componentes servidor
16│ ├── client/ # Componentes cliente
17│ └── ui/ # Componentes UI
18└── lib/
19 └── actions.ts # Server Actions
Server Components ⚡️
Los componentes se renderizan en el servidor por defecto para un mejor rendimiento:
typescript
1// app/blog/page.tsx
2import { PostList } from '@/components/server/PostList';
3import { Suspense } from 'react';
4
5export default async function BlogPage() {
6 return (
7 <main className="container mx-auto px-4 py-8">
8 <h1 className="text-4xl font-bold mb-8">Nuestro Blog</h1>
9 <Suspense fallback={<p>Cargando artículos...</p>}>
10 <PostList />
11 </Suspense>
12 </main>
13 );
14}
15
16// components/server/PostList.tsx
17async function getPosts() {
18 const posts = await db.post.findMany();
19 return posts;
20}
21
22export async function PostList() {
23 const posts = await getPosts();
24
25 return (
26 <div className="grid gap-6 md:grid-cols-2">
27 {posts.map(post => (
28 <article key={post.id} className="p-4 rounded-lg border">
29 <h2 className="text-xl font-semibold">{post.title}</h2>
30 <p className="mt-2 text-gray-600">{post.excerpt}</p>
31 </article>
32 ))}
33 </div>
34 );
35}
Client Components 🔄
Para los componentes que requieren interactividad:
typescript
1// components/client/SearchBar.tsx
2'use client';
3
4import { useState } from 'react';
5import { useRouter } from 'next/navigation';
6
7export function SearchBar() {
8 const [query, setQuery] = useState('');
9 const router = useRouter();
10
11 function handleSearch(e: React.FormEvent) {
12 e.preventDefault();
13 router.push(`/search?q=${encodeURIComponent(query)}`);
14 }
15
16 return (
17 <form onSubmit={handleSearch} className="flex gap-2">
18 <input
19 type="search"
20 value={query}
21 onChange={(e) => setQuery(e.target.value)}
22 placeholder="Buscar..."
23 className="px-4 py-2 rounded border"
24 />
25 <button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded">
26 Buscar
27 </button>
28 </form>
29 );
30}
Server Actions ⚡️
Mutaciones del lado del servidor directamente desde tus componentes:
typescript
1// lib/actions.ts
2'use server';
3
4import { revalidatePath } from 'next/cache';
5import { redirect } from 'next/navigation';
6import { z } from 'zod';
7
8const PostSchema = z.object({
9 title: z.string().min(3, "El título debe contener al menos 3 caracteres"),
10 content: z.string().min(10, "El contenido debe contener al menos 10 caracteres")
11});
12
13export async function createPost(formData: FormData) {
14 const data = {
15 title: formData.get('title'),
16 content: formData.get('content')
17 };
18
19 const result = PostSchema.safeParse(data);
20
21 if (!result.success) {
22 return { error: result.error.flatten() };
23 }
24
25 const post = await db.post.create({
26 data: result.data
27 });
28
29 revalidatePath('/blog');
30 redirect('/blog');
31}
32
33// app/blog/new/page.tsx
34import { createPost } from '@/lib/actions';
35
36export default function NewPostPage() {
37 return (
38 <form action={createPost} className="space-y-4 max-w-lg mx-auto">
39 <div>
40 <label htmlFor="title" className="block text-sm font-medium">
41 Título
42 </label>
43 <input
44 type="text"
45 name="title"
46 id="title"
47 required
48 className="mt-1 block w-full rounded-md border-gray-300"
49 />
50 </div>
51 <div>
52 <label htmlFor="content" className="block text-sm font-medium">
53 Contenido
54 </label>
55 <textarea
56 name="content"
57 id="content"
58 required
59 rows={5}
60 className="mt-1 block w-full rounded-md border-gray-300"
61 />
62 </div>
63 <button
64 type="submit"
65 className="w-full py-2 px-4 bg-blue-500 text-white rounded-md"
66 >
67 Publicar
68 </button>
69 </form>
70 );
71}
Rutas API modernas 🛠️
typescript
1// app/api/posts/route.ts
2import { NextResponse } from 'next/server';
3import { z } from 'zod';
4
5const PostSchema = z.object({
6 title: z.string().min(3),
7 content: z.string().min(10),
8});
9
10export async function GET(request: Request) {
11 const { searchParams } = new URL(request.url);
12 const query = searchParams.get('q');
13
14 const posts = await db.post.findMany({
15 where: query
16 ? {
17 OR: [
18 { title: { contains: query } },
19 { content: { contains: query } },
20 ],
21 }
22 : undefined,
23 });
24
25 return NextResponse.json({ posts });
26}
27
28export async function POST(request: Request) {
29 try {
30 const body = await request.json();
31 const result = PostSchema.safeParse(body);
32
33 if (!result.success) {
34 return NextResponse.json(
35 { error: result.error.flatten() },
36 { status: 400 },
37 );
38 }
39
40 const post = await db.post.create({
41 data: result.data,
42 });
43
44 return NextResponse.json(post, { status: 201 });
45 } catch (error) {
46 return NextResponse.json({ error: 'Error del servidor' }, { status: 500 });
47 }
48}
Optimizaciones y buenas prácticas 🚀
1. Streaming con Suspense
typescript
1import { Suspense } from 'react';
2import { PostList } from '@/components/server/PostList';
3import { SidebarNav } from '@/components/server/SidebarNav';
4import { Loading } from '@/components/ui/Loading';
5
6export default function BlogLayout({
7 children
8}: {
9 children: React.ReactNode
10}) {
11 return (
12 <div className="flex gap-8">
13 <Suspense fallback={<Loading />}>
14 <SidebarNav />
15 </Suspense>
16 <main className="flex-1">
17 <Suspense fallback={<Loading />}>
18 {children}
19 </Suspense>
20 </main>
21 <aside className="w-64">
22 <Suspense fallback={<Loading />}>
23 <PostList type="recent" />
24 </Suspense>
25 </aside>
26 </div>
27 );
28}
2. Gestión de errores
typescript
1// app/error.tsx
2'use client';
3
4export default function Error({
5 error,
6 reset
7}: {
8 error: Error & { digest?: string };
9 reset: () => void;
10}) {
11 return (
12 <div className="flex flex-col items-center justify-center min-h-[400px]">
13 <h2 className="text-2xl font-bold mb-4">
14 ¡Ha ocurrido un error!
15 </h2>
16 <button
17 onClick={() => reset()}
18 className="px-4 py-2 bg-blue-500 text-white rounded"
19 >
20 Reintentar
21 </button>
22 </div>
23 );
24}
3. Carga optimizada de imágenes
typescript
1import Image from 'next/image';
2
3export function Avatar({ src, alt }: { src: string; alt: string }) {
4 return (
5 <div className="relative w-10 h-10">
6 <Image
7 src={src}
8 alt={alt}
9 fill
10 sizes="40px"
11 className="rounded-full object-cover"
12 priority
13 />
14 </div>
15 );
16}
Para ir más allá 🎈
En resumen ✨
Next.js 15 representa una evolución importante en el desarrollo web React con:
- Server-first: Componentes servidor por defecto para mejor rendimiento
- Simplicidad: Una arquitectura intuitiva basada en archivos
- Rendimiento: Optimizaciones automáticas y streaming
- DX mejorada: Server Actions y API Routes modernas
La combinación del App Router, Server Components y Server Actions ofrece una base sólida para construir aplicaciones web modernas, eficientes y mantenibles.
¡Ahora que comprendes los fundamentos de Next.js 14, estás listo para crear aplicaciones web modernas y eficientes! No dudes en consultar la documentación oficial para profundizar en cada concepto. ¡Feliz desarrollo! 🚀