Módulo 00: Introducción a Next.js

Objetivos del módulo

  • Entender qué es Next.js y en qué se diferencia de React, que es una biblioteca y no un framework
  • Saber qué problemas de una aplicación React “pelada” resuelve Next.js y cuáles no
  • Ver una primera página de Next.js funcionando y leer el HTML que llega de verdad al navegador
  • Recorrer un proyecto recién creado con create-next-app y saber para qué sirve cada fichero
  • Distinguir las tres órdenes del ciclo de trabajo (next dev, next build y next start) y lo que hace cada una
  • Seguir una petición completa: armazón prerenderizado, streaming, carga útil RSC e hidratación
  • Medir de verdad una página de Next.js frente a la misma página hecha como SPA con Vite, e interpretar los números sin prejuicios
  • Conocer la historia del proyecto, de dónde viene el App Router y qué papel juega Vercel
  • Comparar Next.js con React + Vite, React Router (antes Remix), Astro y Nuxt, y saber cuándo elegir cada uno
  • Entender la política de versiones y soporte (LTS) de Next.js, el programa mensual de parches de seguridad y por qué hay que instalar siempre el último parche
  • Decidir con criterio entre Next.js 16 y Next.js 15 para un proyecto concreto
  • Saber leer las marcas 🆕 y 🔄 que usa este curso y tener un mapa de lo que vas a aprender

💡 Consejo: Este módulo casi no te pide escribir código, pero no te lo saltes. Next.js ha cambiado tanto entre versiones que gran parte de lo que encontrarás en tutoriales, foros y respuestas de asistentes de IA está desfasado. Saber qué versión describe cada fuente y qué modelo usa (Pages Router, App Router con la caché de Next.js 15, o Cache Components) te ahorrará muchas horas de depuración.

📘 Concepto: Este curso usa Next.js 16.4, con React 19.3, TypeScript 5 y Tailwind CSS 4. Todos los ejemplos y todas las salidas que verás se han obtenido con Next.js 16.4.3. Cuando lo instales tendrás seguramente un parche posterior (16.4.4 salió el 2 de noviembre de 2026, y habrá más): instala siempre el último 16.4.x. Los parches corrigen fallos y vulnerabilidades sin cambiar las APIs, así que todo lo que leas aquí funcionará igual. El apartado 9.3 explica por qué es importante.


1. Qué es Next.js

Next.js es un framework de React para crear aplicaciones web completas: la parte que ve el usuario y una parte de servidor que vive en el mismo proyecto. Lo desarrolla Vercel como proyecto de código abierto con licencia MIT.

Para entender qué significa eso hay que separar dos palabras que se confunden mucho:

  • Una biblioteca es código que tú llamas cuando lo necesitas. React es una biblioteca: te da componentes, estado y hooks para describir una interfaz, y no opina sobre nada más. No sabe qué es una URL, no sabe compilar tu código, no sabe dónde vive tu servidor.
  • Un framework es código que te llama a ti. Tú escribes piezas siguiendo sus convenciones (un fichero page.tsx en tal carpeta, una función exportada con tal nombre) y el framework decide cuándo ejecutarlas, en qué máquina y con qué datos. Es el principio de “inversión de control”.

Cuando trabajaste con React y Vite (por ejemplo en el Curso de React), tuviste que tomar tú todas las decisiones que no son de React: qué router usar, cómo pedir datos, dónde guardar el estado del servidor, cómo dividir el JavaScript en trozos, cómo poner el título de cada página. Next.js toma esas decisiones por ti y las integra entre sí.

Esquema con React como capa interior (componentes, hooks, Server Components) y Next.js alrededor (enrutado, renderizado, datos, backend, compilación, optimización, SEO, despliegue)

Figura 1. React describe la interfaz. Next.js pone todo lo demás: rutas, servidor, compilación, caché y despliegue.

📘 Concepto: Un framework full stack reúne en un único proyecto el código de cliente (lo que se ejecuta en el navegador) y el de servidor (lo que se ejecuta en Node.js). En Next.js la frontera entre ambos no es una carpeta frontend/ y otra backend/, sino una propiedad de cada componente y de cada función, como verás en el módulo 05.

1.1. Lo que Next.js añade a React

Necesidad Con React + Vite Con Next.js
Rutas Instalas y configuras React Router o TanStack Router Las carpetas de app/ son las URL (módulo 03)
Generar HTML en el servidor Montas tú un servidor con SSR, o no lo haces Viene de serie, y además en el build o en streaming (módulo 02)
Pedir datos useEffect o TanStack Query desde el navegador await fetch(...) dentro del componente, en el servidor (módulo 08)
Guardar datos Un backend aparte (Express, Spring…) y fetch Server Actions y Route Handlers en el mismo proyecto (módulos 10 y 12)
Caché La gestionas tú en el cliente 'use cache', perfiles de duración e invalidación por etiquetas (módulo 09)
Imágenes y fuentes Las optimizas a mano next/image y next/font (módulos 06 y 07)
Título y metadatos por página document.title o una biblioteca Metadata API, sitemap, robots (módulo 07)
Compilación Vite Turbopack, integrado y sin configurar
Server Components No disponibles El modelo por defecto

La última fila es la más importante. Los React Server Components (componentes que se ejecutan solo en el servidor) forman parte de React desde la versión 19, pero React solo define cómo funcionan: hace falta un servidor y un bundler que sepan separar el código de servidor del de cliente. Vite, por sí solo, no lo hace. Next.js fue el primer framework en ofrecerlos de forma estable, y hoy es la forma más directa de usarlos.

1.2. Lo que Next.js no hace por ti

Un framework no es magia. Next.js no incluye:

  • Base de datos ni ORM. Usarás SQLite con Drizzle a partir del módulo 16.
  • Sistema de autenticación completo. Lo construirás con cookies y jose, y verás Better Auth en el módulo 17.
  • Biblioteca de componentes visuales. Usarás Tailwind CSS 4, que create-next-app instala por defecto.
  • Validación de formularios. Usarás Zod 4 en el módulo 11.

Lo que sí hace es dejar sitio para cada una de esas piezas y decirte dónde va cada una.

1.3. Lo que dice el propio equipo de React

La documentación oficial de React (react.dev, apartado Creating a React App) ya no recomienda empezar con una SPA hecha a mano. Recomienda usar un framework y cita, por este orden, Next.js (App Router), React Router (v7) y Expo (para aplicaciones nativas). Montar React desde cero con Vite, Parcel o Rsbuild queda como opción para quien tiene necesidades que no cubren los frameworks, quiere construir su propio framework o, simplemente, quiere aprender las bases.

Eso no convierte a Vite en algo malo. Una SPA sigue siendo la mejor elección para muchas aplicaciones internas detrás de un login, y en el apartado 6 verás con números que no siempre es más lenta. Pero explica por qué un curso de React moderno termina, tarde o temprano, en un framework.


2. Qué problemas resuelve

Para valorar Next.js hay que mirar primero qué le pasa a una SPA clásica hecha con React y Vite. Este es el index.html real que genera Vite para una aplicación React:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>lab-react</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Todo el contenido de la página, los menús, los textos, la lista de departamentos, se construye después, en el navegador, cuando ya se ha descargado y ejecutado el JavaScript. De ahí salen cinco problemas.

1. La pantalla en blanco. Hasta que el JavaScript llega y se ejecuta, el usuario no ve nada. En un portátil con fibra son décimas de segundo. En un móvil de gama baja con mala cobertura pueden ser varios segundos.

2. Las cascadas de peticiones (waterfalls). El navegador descarga el HTML, después el JavaScript, después React monta los componentes, y solo entonces los efectos piden los datos a la API. Si un componente hijo necesita los datos del padre para hacer su propia petición, se añade otro viaje. Cada paso espera al anterior.

3. Los buscadores y las vistas previas. Un buscador que lee el HTML sin ejecutar JavaScript ve un <div> vacío. Las redes sociales que generan una vista previa al pegar un enlace leen las etiquetas <meta> del HTML, y en una SPA son las mismas para todas las páginas.

4. Todo el código viaja al navegador. Si para pintar una tabla de productos usas una biblioteca de formato de fechas de 50 kB, esos 50 kB los descarga cada usuario, aunque la tabla nunca cambie después.

5. Los secretos no pueden estar en el cliente. Una clave de API o la cadena de conexión a una base de datos no pueden aparecer en código que se envía al navegador. Así que necesitas un backend aparte solo para hacer de intermediario.

Comparación del recorrido de una petición en una SPA con Vite y en Next.js

Figura 2. En la SPA, el navegador hace todo el trabajo en varios viajes. En Next.js, el servidor hace el trabajo cerca de los datos y envía HTML ya construido.

Next.js ataca los cinco problemas con la misma idea: mover trabajo al servidor, o al momento de compilar, siempre que se pueda. El servidor está cerca de la base de datos, no tiene que descargar nada y puede guardar secretos. El navegador recibe HTML con contenido y solo el JavaScript de las partes interactivas.

⚠️ Importante: Mover trabajo al servidor tiene precio. Necesitas un servidor Node.js en marcha (o un proveedor que lo ponga por ti), cada petición dinámica consume CPU allí, y el modelo mental es más complejo: ahora cada componente vive en un lado de una frontera, y la caché pasa a ser algo que tienes que entender. Por eso este curso dedica módulos enteros a renderizado, a Server y Client Components y a la caché.

2.1. Tu primera página con Next.js

Veamos la idea en código. No te preocupes por instalar nada todavía: en el módulo 01 crearás el proyecto paso a paso. Aquí solo interesa qué hace cada pieza.

La página muestra los departamentos de la API EMPRESA (la misma API de los cursos de React y Angular) y un botón con un contador:

// app/page.tsx
import { Suspense } from 'react'
import { Contador } from './contador'

const API = process.env.API_URL ?? 'http://localhost:3000/api'

type Departamento = {
  deptId: number
  nombre: string
  localidad: string
}

// Server Component asíncrono: se ejecuta solo en el servidor
async function ListaDepartamentos() {
  const respuesta = await fetch(`${API}/departamentos`)
  const datos: { contenido: Departamento[] } = await respuesta.json()

  return (
    <ul className="space-y-1">
      {datos.contenido.map((d) => (
        <li key={d.deptId} className="rounded bg-indigo-50 px-3 py-1">
          <strong>{d.nombre}</strong> ({d.localidad})
        </li>
      ))}
    </ul>
  )
}

export default function Inicio() {
  return (
    <main className="mx-auto max-w-xl space-y-6 p-8">
      <h1 className="text-3xl font-bold text-indigo-900">EMPRESA</h1>
      <p>Departamentos leídos en el servidor:</p>
      <Suspense fallback={<p>Cargando departamentos...</p>}>
        <ListaDepartamentos />
      </Suspense>
      <Contador />
    </main>
  )
}
// app/contador.tsx
'use client'

import { useState } from 'react'

// Client Component: se renderiza en el servidor y se hidrata en el navegador
export function Contador() {
  const [visitas, setVisitas] = useState(0)

  return (
    <button
      className="rounded bg-indigo-700 px-4 py-2 text-white"
      onClick={() => setVisitas(visitas + 1)}
    >
      Me has pulsado {visitas} veces
    </button>
  )
}

Para que la página sea tuya, el layout raíz también se ha simplificado (en español y sin las fuentes de la plantilla):

// app/layout.tsx
import type { Metadata } from 'next'
import './globals.css'

export const metadata: Metadata = {
  title: 'EMPRESA con Next.js',
  description: 'Primera página del Curso de Next.js',
}

export default function RootLayout({ children }: LayoutProps<'/'>) {
  return (
    <html lang="es">
      <body>{children}</body>
    </html>
  )
}

Fíjate en tres cosas que en una SPA serían imposibles o muy incómodas:

  1. ListaDepartamentos es una función async que hace await fetch(...) directamente, sin useEffect, sin estado de carga y sin useState. Puede hacerlo porque es un Server Component: se ejecuta en el servidor, espera los datos y devuelve el HTML ya rellenado.
  2. Contador empieza por 'use client'. Esa directiva marca la frontera: a partir de ahí el código sí viaja al navegador, porque necesita useState y onClick.
  3. No hay ningún fichero de rutas. El fichero app/page.tsx es la ruta /.

Página EMPRESA con el título, la lista de siete departamentos y el botón del contador

Figura 3. La página en marcha. La lista se generó en el servidor; el botón es lo único interactivo.

2.2. Qué llega de verdad al navegador

Lo interesante es el HTML. Si pides la página con curl (o con “Ver código fuente” en el navegador), aparecen, entre otras, estas líneas reales:

<h1 class="text-3xl font-bold text-indigo-900">EMPRESA</h1>
<template id="B:0"></template><p>Cargando departamentos...</p>
...
<div hidden id="S:0"><ul class="space-y-1"><li class="rounded bg-indigo-50 px-3 py-1"><strong>DIRECCIÓN</strong> (<!-- -->Madrid<!-- -->)</li>...
...
<button class="rounded bg-indigo-700 px-4 py-2 text-white">Me has pulsado <!-- -->0<!-- --> veces</button>

Léelas despacio, porque resumen medio curso:

  • El <h1> y el texto Cargando departamentos... forman el armazón (shell). Next.js lo generó al compilar, porque no depende de nada que cambie entre peticiones.
  • La lista llega en la misma respuesta, un poco después, dentro de un <div hidden id="S:0">. Un pequeño script en línea la mueve a su sitio, en lugar del texto de carga. Eso es streaming, y lo controla el <Suspense> que rodea a ListaDepartamentos.
  • El botón ya viene en el HTML con su texto inicial (0 veces). Cuando llega el JavaScript, React lo hidrata: le engancha el onClick sin volver a dibujarlo.
  • El código de ListaDepartamentos (el fetch, la URL de la API, el tipo Departamento) no aparece en ningún fichero JavaScript enviado al navegador. Solo viaja su resultado. En el apartado 5 lo comprobarás buscando dentro de los ficheros compilados.

Al compilar, Next.js resume todo esto en una línea:

Route (app)
┌ ◐ /
└ ○ /_not-found

○  (Static)             prerendered as static content
◐  (Partial Prerender)  prerendered as static HTML with dynamic server-streamed content

El símbolo ◐ significa prerenderizado parcial: una parte estática (el armazón) y una parte dinámica que se genera en cada petición y llega por streaming. Lo estudiarás a fondo en el módulo 02.

💡 Consejo: Acostúmbrate desde ya a mirar el HTML que devuelve el servidor y la lista de rutas de next build. Son la forma más rápida de saber si una página es estática, dinámica o parcial, y casi todas las sorpresas de rendimiento se ven ahí antes que en el navegador.


3. Recorrido por un proyecto recién creado

Antes de modificar nada, merece la pena abrir un proyecto tal y como lo deja create-next-app y entender cada pieza. En el módulo 01 verás todas las opciones del asistente; aquí basta con saber que la orden

npx create-next-app@latest empresa-next --yes

crea un proyecto con las opciones recomendadas: App Router, TypeScript, Tailwind CSS 4, ESLint y Turbopack. Durante la instalación muestra qué paquetes añade:

Initializing project with template: app-tw

Installing dependencies:
- next
- react
- react-dom

Installing devDependencies:
- @tailwindcss/turbopack
- @types/node
- @types/react
- @types/react-dom
- eslint
- eslint-config-next
- tailwindcss
- typescript

Fíjate en lo poco que hay en dependencies: solo next, react y react-dom. Todo lo demás (TypeScript, Tailwind, ESLint, tipos) son herramientas de desarrollo que no llegan a producción.

3.1. Qué hay en la carpeta

empresa-next/
├── app/                  Rutas de la aplicación (App Router)
│   ├── favicon.ico       Icono de la pestaña: convención de fichero
│   ├── globals.css       CSS global: importa Tailwind y define variables
│   ├── layout.tsx        Layout raíz: <html> y <body> de todas las páginas
│   └── page.tsx          La página de la ruta /
├── public/               Ficheros que se sirven tal cual (/next.svg, /vercel.svg...)
├── AGENTS.md             Instrucciones para asistentes de IA
├── eslint.config.mjs     Configuración de ESLint (formato "flat")
├── next-env.d.ts         Tipos de Next.js para TypeScript (no se edita)
├── next.config.ts        Configuración de Next.js
├── package.json          Dependencias y scripts
└── tsconfig.json         Configuración de TypeScript

Si vienes de Vite, echarás en falta tres cosas: no hay index.html (lo genera Next.js a partir del layout), no hay main.tsx con createRoot (Next.js hidrata la página por ti) y no hay src/ (el asistente lo ofrece como opción, pero por defecto todo cuelga de la raíz).

Al ejecutar npm run dev y abrir la página, ves la portada de la plantilla:

Página de bienvenida de create-next-app con el logotipo de Next.js, el texto To get started, edit the page.tsx file y los botones Deploy Now y Documentation

Figura 4. La página que genera create-next-app, servida por next dev. El círculo con la “N”, abajo a la izquierda, es el indicador de desarrollo: solo aparece con next dev.

3.2. app/layout.tsx y app/page.tsx

El layout raíz de la plantilla es este (los comentarios son del curso):

import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

// next/font descarga la fuente al compilar y la sirve desde tu propio dominio
const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

// Metadata API: Next.js convierte este objeto en <title> y <meta>
export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

// LayoutProps<"/"> es un tipo global que genera Next.js para cada ruta
export default function RootLayout({ children }: LayoutProps<"/">) {
  return (
    <html
      lang="en"
      className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
    >
      <body className="min-h-full flex flex-col">{children}</body>
    </html>
  );
}

Tres ideas que verás muchas veces:

  • El layout raíz es obligatorio y es el único sitio donde se escriben <html> y <body>. Todas las páginas se pintan dentro de su children.
  • export const metadata sustituye a lo que en Vite escribías a mano en index.html. Cada página puede exportar el suyo (módulo 07).
  • LayoutProps<"/"> no se importa de ningún sitio: es un tipo global que Next.js genera a partir de tus rutas. Por eso tsconfig.json incluye las carpetas .next/types y .next/dev/types.

page.tsx es un componente normal exportado por defecto. En la plantilla muestra la portada de la figura 4 con next/image. Lo sustituirás en cuanto empieces a trabajar.

3.3. next.config.ts

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  cacheComponents: true,
  partialPrefetching: true,
  turbopack: {
    rules: {
      "*.css": {
        loaders: ["@tailwindcss/turbopack"],
        as: "*.css",
      },
    },
  },
};

export default nextConfig;
  • cacheComponents: true activa el modelo de caché de Next.js 16, que estudiarás en el módulo 09. Es lo que hace que una página con datos sin <Suspense> ni caché falle al compilar, como verás en el apartado 10.2.
  • partialPrefetching: true activa la precarga parcial de rutas de la 16.3 (módulo 15).
  • turbopack.rules le dice a Turbopack que pase los ficheros CSS por el cargador de Tailwind. Es la forma en que create-next-app integra Tailwind 4 con Turbopack.

3.4. package.json, tsconfig.json y eslint.config.mjs

Los scripts son cuatro, y los tres primeros son las órdenes del apartado 4:

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "lint": "eslint"
}

lint llama directamente a ESLint: desde Next.js 16 ya no existe la orden next lint. La configuración de ESLint usa el formato flat e importa dos conjuntos de reglas de Next.js:

import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const eslintConfig = defineConfig([
  ...nextVitals,
  ...nextTs,
  globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]),
]);

export default eslintConfig;

En tsconfig.json hay dos detalles propios de Next.js: un plugin ("plugins": [{ "name": "next" }]) que añade avisos específicos en el editor, y el alias "@/*": ["./*"], que te permite escribir import { x } from '@/app/lib/datos' en lugar de rutas relativas largas.

3.5. AGENTS.md y la documentación local

AGENTS.md es un fichero de instrucciones para asistentes de programación con IA. Su contenido real es breve y muy revelador:

## This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all
differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/`
(resolved from this file's directory; ...) before writing any code. Heed deprecation notices.

El paquete next incluye toda su documentación en node_modules/next/dist/docs/, en Markdown, con la versión exacta que tienes instalada. Next.js reconoce así un problema real: los asistentes de IA se entrenaron con código de versiones antiguas y tienden a proponer APIs que ya no existen. El fichero les pide leer la documentación instalada antes de escribir código. Para ti también es útil: si dudas de si algo existe en tu versión, esa carpeta es la respuesta más fiable.

💡 Consejo: next dev vuelve a escribir ese bloque en AGENTS.md si lo borras (lo verás en su salida: “Generated AGENTS.md for AI agents”). Si no lo quieres, la propia salida indica la opción de configuración para desactivarlo. Lo normal es dejarlo y subirlo al repositorio.


4. El ciclo de trabajo: next dev, next build y next start

En Vite tenías vite (desarrollo), vite build (compilar) y vite preview (probar lo compilado). Next.js tiene tres órdenes equivalentes, pero la tercera no es una simple vista previa: es el servidor de producción.

Tres columnas que comparan next dev, next build y next start: cuándo se usan, qué hacen y dónde escriben

Figura 5. Las tres órdenes. dev es para programar, build prepara todo lo posible por adelantado y start sirve el resultado.

4.1. next dev: programar

Esta es la salida real de next dev en el proyecto recién creado, con dos peticiones a /:

▲ Next.js 16.4.3 (Turbopack)
- Local:         http://127.0.0.1:5500
- Network:       http://127.0.0.1:5500
✓ Ready in 26.6s

✓ Running next.config.ts took 511ms
- Cache Components enabled
- Partial Prefetching enabled

✓ Generated AGENTS.md for AI agents. Set `agentRules: false` in next.config to disable.
○ Compiling / ...
 GET / 200 in 6.4s (next.js: 5.0s, application-code: 1404ms)
 GET / 200 in 534ms (next.js: 92ms, application-code: 442ms)

(En el curso ves el puerto 5500 porque los ejemplos se probaron así; tú usarás el 3001, como se explica en el apartado 12.2.)

Lo que cuenta esta salida:

  • Turbopack compila bajo demanda. “Ready” no significa que todo esté compilado: la ruta / se compiló al pedirla por primera vez (Compiling / ...). En un proyecto con 50 rutas, solo se compilan las que visitas.
  • La primera petición es lenta y las siguientes, no. 6,4 s la primera (casi todo trabajo de Next.js: compilar), 534 ms la segunda. Desde Next.js 16.1, Turbopack guarda en disco lo compilado, así que el siguiente arranque será más rápido.
  • Cada línea separa el tiempo de Next.js del de tu código (next.js frente a application-code). Si una página va lenta en desarrollo, esta línea te dice a quién culpar.
  • Escribe en .next/dev, no en .next. Desde Next.js 16, desarrollo y compilación usan carpetas distintas, de modo que puedes tener next dev en marcha mientras ejecutas next build.

Los tiempos absolutos dependen mucho del equipo (estas cifras se tomaron en un equipo con otros procesos compitiendo por la CPU); fíjate en las proporciones.

⚠️ Importante: En modo desarrollo Next.js renderiza todo en cada petición, añade código de depuración y no aplica muchas optimizaciones. Nunca midas el rendimiento con next dev ni lo uses para servir una aplicación real.

4.2. next build: compilar para producción

✓ Running next.config.ts took 546ms
- Cache Components enabled
- Partial Prefetching enabled
  Creating an optimized production build ...
✓ Compiled successfully in 20.1s
  Running TypeScript ...
  Finished TypeScript in 23.4s ...
  Collecting page data using 5 workers ...
✓ Generating static pages using 5 workers (4/4) in 23.6s
  Finalizing page optimization ...

Route (app)
┌ ◐ /
└ ○ /_not-found

next build hace mucho más que compilar:

  1. Compila y optimiza todas las rutas: minifica, divide el JavaScript en trozos (chunks) y separa el código de servidor del de cliente.
  2. Comprueba los tipos con TypeScript. Un error de tipos detiene el build, aunque next dev te hubiera dejado seguir.
  3. Prerenderiza: ejecuta tus componentes para generar por adelantado todo lo que se pueda (páginas estáticas completas y armazones de las parciales). Para ello ejecuta tu código, así que si una página estática pide datos a la API, la API tiene que estar en marcha durante el build.
  4. Imprime la tabla de rutas con el tipo de cada una (○, ◐, ƒ).

📘 Concepto: En Next.js 15, next build mostraba además el tamaño en kB de cada ruta (“First Load JS”). Next.js 16 eliminó esas cifras porque, según su guía de actualización, eran inexactas en aplicaciones con Server Components: ni siquiera Turbopack y Webpack coincidían al contar la carga útil de los Client Components. Para analizar tamaños se usa el analizador de paquetes (módulo 20).

El resultado queda en .next. Dentro de .next/server/app encontrarás, para la página del apartado 2.1, un fichero index.html de 1 764 bytes: es el armazón prerenderizado. Contiene “Cargando departamentos…” y ningún departamento, porque esa parte es dinámica. A su lado, index.meta guarda las cabeceras y un campo postponed con el estado necesario para reanudar el render en cada petición justo donde se quedó.

4.3. next start: servir en producción

▲ Next.js 16.4.3
- Local:         http://127.0.0.1:5500
- Network:       http://127.0.0.1:5500
✓ Ready in 6.4s

next start no compila nada: lee .next y sirve. Si no has ejecutado antes next build, falla. Ojo, porque llega a imprimir “Ready” antes de darse cuenta:

✓ Ready in 4.3s
Error: Could not find a production build in the '.next' directory. Try building your app with 'next build' before starting the production server.

Si cambias el código, tienes que volver a compilar. En el módulo 23 verás cómo se empaqueta esto en Docker o se despliega en un proveedor.

  next dev next build next start
Cuándo Mientras programas Antes de publicar En el servidor de producción
Compila Bajo demanda, ruta a ruta Todo, optimizado Nada
Comprueba tipos No (solo el editor) Sí, y falla si hay errores No
Prerenderiza No, todo se genera en cada petición Sí Usa lo prerenderizado
Recarga al guardar Sí No aplica No
Carpeta .next/dev .next Lee .next
Equivalente en Vite vite vite build vite preview (pero next start sí es para producción)

5. Qué pasa en una petición, paso a paso

Con la página del apartado 2.1 compilada y servida con next start, vamos a seguir una petición de principio a fin con herramientas reales: curl, el contenido de .next y el navegador.

Diagrama de secuencia entre navegador, servidor Next.js y API EMPRESA: petición, armazón, fetch a la API, streaming de la lista, descarga de JS, hidratación y navegaciones posteriores

Figura 6. Los ocho pasos de una petición. En naranja, lo que viaja en la respuesta HTML.

5.1. Las cabeceras de la respuesta

Estas son las cabeceras reales de GET /:

HTTP/1.1 200 OK
Vary: rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch, Accept-Encoding
x-nextjs-stale-time: 300
x-nextjs-prerender: 1
x-nextjs-postponed: 1
X-Powered-By: Next.js
Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
  • x-nextjs-prerender: 1: la respuesta parte de algo prerenderizado en el build (el armazón).
  • x-nextjs-postponed: 1: hay una parte aplazada (la lista) que se ha renderizado en esta petición.
  • Transfer-Encoding: chunked: la respuesta se envía por trozos, sin saber de antemano su longitud. Es la base técnica del streaming.
  • Cache-Control: private, no-cache, no-store: como la página tiene una parte dinámica, ni el navegador ni una CDN deben guardar la respuesta completa.
  • Vary: rsc, next-router-...: la misma URL puede devolver HTML o la carga útil RSC según las cabeceras de la petición. Lo verás en el paso 8.

Medido con curl, el primer byte de la respuesta llegó en unos 0,1 s, y la respuesta completa, entre 0,6 y 1,3 s según la ejecución. La API, por su parte, respondía en menos de 0,04 s. La diferencia es el trabajo del servidor al renderizar la parte dinámica (en un equipo cargado). Lo importante es que el usuario empieza a ver la página con el primer byte, sin esperar a la respuesta completa.

5.2. El HTML y la carga útil RSC

Al final del HTML hay varios <script> con este aspecto (recortados):

self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:\"/_next/static/chunks/1tq6ppe__m8m-.js\"\n3:I[39756,...
self.__next_f.push([1,"8:[\"$\",\"ul\",null,{\"className\":\"space-y-1\",\"children\":[[\"$\",\"li\",\"10\",{...\"children\":\"DIRECCIÓN\"}...

Eso es la carga útil RSC (RSC Payload): una representación compacta del árbol de Server Components ya renderizado. Según la documentación de Next.js, contiene tres cosas:

  1. El resultado de los Server Components (el <ul> con sus <li>, como datos, no como código).
  2. Huecos donde van los Client Components, con una referencia a su fichero JavaScript.
  3. Las props que un Server Component pasa a un Client Component.

La referencia al contador aparece así en la página real:

a:I[98700,["$2","$9"],"Contador"]

Es una instrucción para React: “en este hueco va el componente exportado como Contador del módulo 98700, que está en estos ficheros”. El navegador no recibe el código de Inicio ni el de ListaDepartamentos, solo su resultado y la dirección del código de Contador.

📘 Concepto: El HTML y la carga útil RSC llevan la misma información en dos formatos. El HTML es para que el navegador pinte algo al instante. La carga útil es para que React reconstruya el árbol de componentes, hidrate los Client Components y, más adelante, actualice la página al navegar sin recargarla. Por eso los nombres de los departamentos aparecen dos veces en el documento.

5.3. Comprobar qué código viaja

Puedes demostrar que el código de servidor no viaja buscando dentro de los ficheros que se envían al navegador, que están en .next/static:

grep -rl "Me has pulsado" .next/static
grep -rl "departamentos" .next/static
grep -rl "API_URL" .next/static

Resultado real:

.next/static/chunks/1sti3nkibdny_.js

Solo la primera búsqueda encuentra algo: el texto del botón está en un chunk de cliente, porque Contador es un Client Component. Ni la palabra departamentos (que solo aparece en la URL del fetch y en el texto del servidor) ni el nombre de la variable de entorno aparecen en ningún fichero enviado al navegador.

En total, .next/static contiene 10 ficheros JavaScript (unos 576 kB sin comprimir, en disco). No todos se descargan en cada página: el navegador pide solo los que necesita la ruta, y next start los sirve comprimidos con gzip y con Cache-Control: public, max-age=31536000, immutable, es decir, el navegador puede guardarlos un año. Eso es posible porque cada nombre de fichero incluye un hash de su contenido: si el código cambia, cambia el nombre.

5.4. La hidratación y las navegaciones siguientes

Cuando llegan los ficheros JavaScript, React:

  1. Lee la carga útil RSC de self.__next_f.
  2. Recorre el árbol y, al llegar al hueco de Contador, ejecuta su código sin volver a crear el DOM: reutiliza el <button> que ya estaba y le engancha el onClick. Esto es la hidratación, que ya conoces del Curso de React.

A partir de ahí, si la aplicación tuviera más páginas enlazadas con <Link>, las navegaciones ya no pedirían HTML: Next.js pide solo la carga útil RSC de la nueva ruta (de ahí la cabecera Vary: rsc), la guarda en una caché del navegador y React actualiza únicamente la parte de la página que cambia. El estado de los Client Components que siguen en pantalla se conserva. Lo verás en el módulo 03.

⚠️ Importante: Si intentas pedir la carga útil “a mano” con curl -H "RSC: 1" http://.../, el servidor responde con una redirección 307 a /?_rsc. Next.js exige un parámetro _rsc calculado a partir de las cabeceras del router para que las respuestas RSC no se mezclen en las cachés intermedias con las respuestas HTML. Es un detalle interno: tú nunca construirás estas peticiones, las hace <Link>.


6. Medirlo: Next.js frente a una SPA con Vite

Hasta aquí, todo son argumentos. Toca medir. Para ello se ha construido la misma página como SPA con React 19.3 y Vite, con los mismos datos y el mismo botón:

// src/App.tsx (SPA con Vite)
import { useEffect, useState } from 'react'

const API = import.meta.env.VITE_API_URL ?? 'http://localhost:3000/api'

type Departamento = {
  deptId: number
  nombre: string
  localidad: string
}

function ListaDepartamentos() {
  const [departamentos, setDepartamentos] = useState<Departamento[] | null>(null)

  // En una SPA los datos se piden desde el navegador, después del primer render
  useEffect(() => {
    fetch(`${API}/departamentos`)
      .then((r) => r.json())
      .then((datos: { contenido: Departamento[] }) => setDepartamentos(datos.contenido))
  }, [])

  if (!departamentos) return <p>Cargando departamentos...</p>

  return (
    <ul className="lista">
      {departamentos.map((d) => (
        <li key={d.deptId}>
          <strong>{d.nombre}</strong> ({d.localidad})
        </li>
      ))}
    </ul>
  )
}

function Contador() {
  const [visitas, setVisitas] = useState(0)
  return (
    <button onClick={() => setVisitas(visitas + 1)}>
      Me has pulsado {visitas} veces
    </button>
  )
}

export function App() {
  return (
    <main>
      <h1>EMPRESA</h1>
      <p>Departamentos leídos desde el navegador:</p>
      <ListaDepartamentos />
      <Contador />
    </main>
  )
}

vite build la compiló en unos 5 segundos:

dist/index.html                   0.40 kB │ gzip:  0.27 kB
dist/assets/index-DkwuBXSx.css    0.35 kB │ gzip:  0.23 kB
dist/assets/index-DlvYlq0H.js   220.20 kB │ gzip: 68.90 kB

Además, se añadió a Next.js una tercera variante, /cacheada, en la que la lista se cachea en lugar de generarse en cada petición. Usa 'use cache', que estudiarás en el módulo 09; por ahora basta con saber que le dice a Next.js “este resultado puede guardarse durante horas”:

// app/cacheada/page.tsx
import { cacheLife } from 'next/cache'

const API = process.env.API_URL ?? 'http://localhost:3000/api'

type Departamento = {
  deptId: number
  nombre: string
  localidad: string
}

// Los departamentos cambian poco: se guardan en caché durante horas
async function ListaDepartamentos() {
  'use cache'
  cacheLife('hours')

  const respuesta = await fetch(`${API}/departamentos`)
  const datos: { contenido: Departamento[] } = await respuesta.json()

  return (
    <ul className="space-y-1">
      {datos.contenido.map((d) => (
        <li key={d.deptId} className="rounded bg-indigo-50 px-3 py-1">
          <strong>{d.nombre}</strong> ({d.localidad})
        </li>
      ))}
    </ul>
  )
}

export default function Cacheada() {
  return (
    <main className="mx-auto max-w-xl space-y-6 p-8">
      <h1 className="text-3xl font-bold text-indigo-900">EMPRESA</h1>
      <ListaDepartamentos />
    </main>
  )
}

next build la clasifica como completamente estática, con su duración de caché:

Route (app)      Revalidate  Expire
┌ ◐ /
├ ○ /_not-found
└ ○ /cacheada            1h      1d

Y al servirla, las cabeceras cambian: x-nextjs-cache: HIT y Cache-Control: s-maxage=3600, stale-while-revalidate=82800. Ahora sí, una CDN puede guardar la página una hora.

6.1. Cómo se ha medido

Las tres páginas se sirvieron en producción (next start y vite preview) y se midieron con Chromium controlado por Playwright, cinco veces cada una, con la caché del navegador desactivada. El script registra los bytes recibidos por tipo de recurso, el número de peticiones, el FCP (First Contentful Paint, el momento en que el navegador pinta el primer contenido) y el momento en que el texto INVESTIGACIÓN, el último departamento, aparece en pantalla. Se tomó la mediana de las cinco ejecuciones.

Se hicieron dos rondas: sin limitar nada y simulando un móvil modesto (latencia de 150 ms, unos 200 kB/s de bajada y la CPU cuatro veces más lenta, con las herramientas de Chrome).

6.2. Resultados

Medida SPA con Vite Next.js / (streaming) Next.js /cacheada
Documento HTML (comprimido) 659 B 5 887 B 2 941 B
JavaScript descargado (comprimido) 68 577 B 138 236 B 137 519 B
Datos pedidos desde el navegador 989 B 0 B 0 B
Peticiones 4 8 7
FCP, sin limitar 248 ms 236 ms 320 ms
Lista visible, sin limitar 504 ms 1 080 ms 356 ms
FCP, móvil simulado 1 960 ms 1 040 ms 872 ms
Lista visible, móvil simulado 2 659 ms 2 862 ms 1 109 ms
Con JavaScript desactivado Página en blanco Armazón y “Cargando…” Página completa

(/cacheada no tiene el botón, por eso su HTML es más pequeño.)

Página EMPRESA servida por Next.js con JavaScript desactivado: se ven el título, el texto Cargando departamentos y el botón, pero no la lista

Figura 7. La página / con JavaScript desactivado. El armazón llega, pero la lista no se coloca en su sitio, porque lo hace un script en línea.

Página /cacheada con JavaScript desactivado: el título y los siete departamentos se ven completos

Figura 8. La página /cacheada con JavaScript desactivado. La lista forma parte del HTML prerenderizado, así que se ve aunque no se ejecute nada.

6.3. Cómo leer estos números

Los números no dicen “Next.js es más rápido”, y es importante entender por qué:

  • Next.js envía más JavaScript que la SPA: unos 138 kB frente a 69 kB. Esa es la base fija del framework: el router del App Router, el código que interpreta la carga útil RSC y la gestión de la caché del cliente. En una página de juguete pesa más que toda la aplicación. En una aplicación real, en cambio, los Server Components no añaden JavaScript por mucho código que tengan, mientras que en una SPA cada componente nuevo suma.
  • El primer pintado gana claramente en el móvil: 1,0 s (o 0,9 s cacheada) frente a 2,0 s. El navegador puede pintar el HTML del armazón antes de descargar y ejecutar el JavaScript. En la SPA, con la CPU lenta, no hay nada que pintar hasta que React se ejecuta.
  • La lista dinámica con streaming no llega antes que en la SPA. En el móvil simulado, 2,9 s frente a 2,7 s; sin limitar, incluso tarda más (1,1 s frente a 0,5 s). El servidor tiene que renderizar esa parte en cada petición, y en el equipo de pruebas ese trabajo costaba entre medio segundo y un segundo. La ventaja del streaming no es que el dato llegue antes, sino que el resto de la página no espera por él.
  • La versión cacheada gana en todo lo que importa al usuario: 1,1 s hasta ver la lista en el móvil, menos de la mitad que la SPA, y una página completa incluso sin JavaScript. Es la razón por la que el catálogo de la aplicación EMPRESA será estático y cacheado.
  • Sin JavaScript, la SPA no muestra nada. Esto importa para los buscadores que no ejecutan JavaScript, para las vistas previas de enlaces y para cualquier fallo de red que impida descargar los scripts.

💡 Consejo: La conclusión práctica es la que guía todo el curso: lo que pueda ser estático o cacheado, que lo sea (catálogo, fichas de producto, páginas informativas); lo que tenga que ser dinámico, que vaya dentro de <Suspense> para no bloquear lo demás; y que los Client Components sean pocos y pequeños. Next.js no hace rápida una aplicación por sí solo: te da las herramientas para que lo sea.

⚠️ Importante: Estos números se tomaron en un único equipo, con otros procesos en marcha, y la página es diminuta. Sirven para entender tendencias, no como cifras absolutas. Si quieres conclusiones para tu proyecto, mide tu proyecto (ejercicio 12).


7. Un poco de historia

Saber de dónde viene Next.js explica por qué hay tantas formas de hacer lo mismo y por qué un tutorial de hace tres años puede no servirte.

Línea de tiempo de Next.js desde 2016 hasta 2026 con los hitos principales

Figura 9. Los hitos principales. En naranja, los dos cambios de modelo que más afectan a cómo se escribe una aplicación.

7.1. Los orígenes: SSR para React (2016-2019)

La primera versión se publicó como código abierto en GitHub el 25 de octubre de 2016. Su autor principal fue Guillermo Rauch, fundador de la empresa ZEIT. La idea inicial era muy concreta: hacer fácil algo que en 2016 era muy difícil, renderizar React en el servidor. Cada fichero dentro de una carpeta pages/ se convertía en una ruta, y Next se encargaba de compilar, servir el HTML y enviar el JavaScript.

Ese modelo se conoce hoy como Pages Router. Durante años añadió piezas: rutas de API en la versión 9 (2019), y en 2020 la generación estática y la regeneración estática incremental (ISR, en la versión 9.5), que permitía regenerar páginas estáticas sin recompilar todo el sitio.

7.2. De ZEIT a Vercel (2020)

El 21 de abril de 2020 ZEIT cambió su nombre a Vercel, con una nueva ronda de financiación. Vercel ofrece una plataforma de alojamiento y es quien mantiene Next.js: su director ejecutivo es Guillermo Rauch y el responsable del proyecto, Tim Neutkens.

7.3. El compilador en Rust y el App Router (2021-2023)

  • Next.js 12 (octubre de 2021) introdujo un compilador escrito en Rust y el middleware.
  • Next.js 13 (octubre de 2022) presentó, en beta, el App Router: una carpeta app/ con layouts anidados y React Server Components. Fue el mayor cambio de la historia del proyecto. El App Router pasó a ser estable en la versión 13.4 (mayo de 2023).
  • Next.js 14 (octubre de 2023) declaró estables las Server Actions y presentó una vista previa del prerenderizado parcial (PPR).

Durante esta etapa, el App Router tenía un sistema de caché implícito: muchas cosas se guardaban en caché sin que lo pidieras, y eso fue la mayor fuente de quejas y confusiones de la comunidad.

7.4. Next.js 15 y 16 (2024-2026)

  • Next.js 15 (21 de octubre de 2024) adoptó React 19, hizo asíncronas las APIs de petición (cookies(), headers(), params…) y dejó de cachear fetch por defecto.
  • Next.js 16 (21 de octubre de 2025) hizo Turbopack el bundler por defecto, presentó Cache Components (caché explícita con 'use cache'), renombró middleware a proxy y eliminó todo lo que llevaba tiempo marcado como obsoleto.
  • Las versiones menores 16.1 a 16.4 han ido completando el modelo: caché en disco para el desarrollo, la Adapter API estable para desplegar en cualquier proveedor, navegaciones instantáneas y, en 16.4 (octubre de 2026), la recomendación oficial de Cache Components como el mejor modelo para cualquier aplicación. Será el modelo por defecto en Next.js 17.

📘 Concepto: Hoy conviven tres generaciones de código Next.js en Internet: Pages Router (pages/, getServerSideProps), App Router con la caché anterior (fetch con next.revalidate, export const revalidate, unstable_cache) y App Router con Cache Components ('use cache', cacheLife, cacheTag). Este curso enseña la tercera, explica la segunda para quien siga en Next.js 15 y te enseña a leer la primera en el módulo 24.

7.5. Next.js y Vercel: ¿estoy atado a una empresa?

Es una pregunta razonable, y la respuesta corta es no. Next.js tiene licencia MIT, y una aplicación Next.js se puede ejecutar en cualquier sitio donde haya Node.js:

  • Con next build y next start en tu propio servidor, como en los apartados 4 a 6.
  • En un contenedor Docker con output: 'standalone'.
  • Como sitio estático puro con output: 'export', si no necesitas servidor.
  • En otros proveedores mediante la Adapter API, estable desde la 16.2.

Lo que sí es cierto es que algunas funciones (la caché distribuida, la optimización de imágenes a gran escala, el ISR en varias regiones) son más fáciles de aprovechar en Vercel, porque su plataforma está hecha a medida. En otros proveedores puede que tengas que configurar más cosas. Lo verás con detalle en el módulo 23.


8. Next.js frente a las alternativas

Next.js no es la única opción, ni siempre la mejor. Antes de elegir, conviene saber qué ofrece cada alternativa y qué precio tiene.

8.1. React + Vite (SPA)

Es lo que hiciste en el Curso de React y lo que has medido en el apartado 6. Vite compila y sirve tu código; React construye la interfaz en el navegador; tú añades un router y una biblioteca de datos.

  • Ventajas: modelo mental simple (todo pasa en el navegador), alojamiento estático y barato, ningún servidor que mantener, arranque y recarga en caliente muy rápidos, compilación rápida (5 s frente a más de 40 s de next build en el ejemplo, incluida la comprobación de tipos), máxima libertad para elegir piezas.
  • Inconvenientes: los cinco problemas del apartado 2. No hay Server Components ni Server Actions.
  • Cuándo elegirlo: aplicaciones internas detrás de un login (un panel de administración, una herramienta de gestión), donde los buscadores no importan y los usuarios tienen buenos equipos. También cuando el backend ya existe en otro lenguaje (Java, .NET, Python) y solo necesitas la interfaz.

8.2. React Router v7 (antes Remix)

Remix nació como un framework centrado en los estándares web: formularios HTML, Request y Response, mejora progresiva. En mayo de 2024 sus autores anunciaron que lo que iba a ser Remix v3 se publicaría como React Router v7, de modo que hoy React Router puede funcionar como simple router de una SPA o como framework full stack con Vite. La marca Remix sigue existiendo para proyectos nuevos de sus autores.

  • Ventajas: usa Vite, se basa mucho en estándares web, tiene un modelo de carga de datos (loaders y actions) fácil de razonar, y te permite empezar como SPA e ir añadiendo servidor poco a poco.
  • Inconvenientes: su soporte de Server Components es más reciente que el de Next.js y el ecosistema de ejemplos es menor.
  • Cuándo elegirlo: si ya tienes una SPA con React Router y quieres añadir renderizado en servidor sin cambiar de herramienta, o si prefieres un modelo más cercano a HTTP.

8.3. Astro

Astro es un framework orientado a sitios de contenido: blogs, documentación, webs corporativas, tiendas con mucho catálogo y poca interacción. Por defecto genera HTML sin nada de JavaScript, y solo hidrata las “islas” interactivas que marques. Además, esas islas pueden escribirse con React, Vue, Svelte u otros.

  • Ventajas: páginas muy ligeras, ideal para contenido, permite mezclar frameworks. Evita la base fija de JavaScript que has visto en el apartado 6.3.
  • Inconvenientes: no está pensado para aplicaciones muy interactivas con mucho estado compartido entre partes de la página.
  • Cuándo elegirlo: cuando lo importante es el contenido y la interacción es puntual.

8.4. Nuxt

Nuxt es a Vue lo que Next.js es a React: un framework full stack con rutas por carpetas, renderizado en servidor, generación estática y rutas de API. Si vienes del mundo Vue, Nuxt es la referencia.

  • Cuándo elegirlo: cuando el equipo trabaja con Vue. Elegir entre Next.js y Nuxt es, sobre todo, elegir entre React y Vue.

8.5. Y también: TanStack Start, Angular

La documentación de React menciona también TanStack Start, un framework full stack construido sobre TanStack Router, todavía joven. Y en el mundo Angular, el propio framework incluye renderizado en servidor e hidratación (lo verás en el Curso de Angular). Son opciones válidas, pero con menos material y menos ofertas de empleo que Next.js.

8.6. Tabla comparativa

  React + Vite Next.js React Router v7 Astro Nuxt
Biblioteca de interfaz React React React Cualquiera (islas) Vue
Dónde se genera el HTML Navegador Servidor, build o ambos Servidor o navegador Sobre todo build Servidor, build o ambos
Server Components No Sí, modelo por defecto Más recientes No (usa islas) No (Vue tiene su propio modelo)
Backend en el mismo proyecto No Sí Sí Sí, limitado Sí
Necesita servidor No Normalmente sí Según el modo Normalmente no Normalmente sí
Curva de aprendizaje Baja Alta Media Baja Media
Ideal para Paneles internos, SPAs Aplicaciones completas con SEO y datos Apps centradas en formularios y HTTP Sitios de contenido Apps completas con Vue

💡 Consejo: No elijas framework por moda. Hazte tres preguntas: ¿importan los buscadores o la primera carga en móviles? ¿Necesito código de servidor propio? ¿Qué sabe ya mi equipo? Si las dos primeras son “no”, una SPA con Vite probablemente sea lo más sencillo. Si son “sí” y el equipo sabe React, Next.js es la opción más madura.


9. Versiones, LTS y soporte

Next.js sigue el versionado semántico: 16.4.3 significa versión mayor 16, menor 4, parche 3.

  • Las mayores (15, 16, 17) salen aproximadamente una vez al año, en octubre, y pueden romper código.
  • Las menores (16.1, 16.2…) salen cada pocos meses y añaden funciones sin romper lo que ya funciona.
  • Los parches (16.4.3, 16.4.4…) corrigen fallos y vulnerabilidades. Salen a menudo, y conviene instalarlos siempre.

Además existe un canal canary, con versiones de prueba que se publican casi a diario. Sirven para probar funciones futuras, pero el propio equipo de Next.js desaconseja usarlas en producción.

9.1. La política de soporte

La política oficial de soporte (nextjs.org/support-policy) define dos fases para cada versión mayor:

  • Active LTS: la versión mayor más reciente. Recibe funciones nuevas, correcciones, mejoras de rendimiento y parches de seguridad. Dura hasta que sale la siguiente mayor.
  • Maintenance LTS: cuando sale una mayor nueva, la anterior pasa a esta fase. Solo recibe correcciones críticas y parches de seguridad, y dura dos años desde su lanzamiento original.
Versión mayor Lanzamiento Estado en noviembre de 2026
16.x 21-10-2025 Active LTS
15.x 21-10-2024 Maintenance LTS hasta el 31-01-2027 (ampliado)
14.x y anteriores 26-10-2023 y antes Sin soporte

La fase de mantenimiento de Next.js 15 debía terminar el 21-10-2026, pero el equipo la amplió hasta el 31-01-2027 para dar más tiempo a quien migra a la 16. La última versión de la rama 15 es la 15.5.28, que npm publica con la etiqueta backport.

Barras de soporte de Next.js 15 y 16: la 15 en Active LTS hasta octubre de 2025 y en Maintenance LTS hasta enero de 2027; la 16 en Active LTS desde octubre de 2025

Figura 10. Cuando sale una versión mayor, la anterior pasa a mantenimiento. La línea discontinua marca la fecha de este curso.

📘 Concepto: En muchos proyectos (Node.js, Angular, Java) “LTS” es una versión especial, distinta de la última. En Next.js la última mayor también es LTS: Next.js 16 es a la vez la versión más reciente y la que tiene soporte activo. Lo que cambia es que Next.js 15, aún con soporte, ya no recibe funciones nuevas.

9.2. Los parches de seguridad

En diciembre de 2025 se descubrieron vulnerabilidades graves en la forma en que React y Next.js procesan las peticiones de Server Components (la primera fue la CVE-2025-66478, conocida como React2Shell). Hubo que publicar parches urgentes en varias ramas. Las estudiarás en el módulo 22.

A raíz de aquello, en julio de 2026 Next.js formalizó un programa de publicaciones de seguridad: aproximadamente una vez al mes se anuncia en el blog oficial, con antelación, qué día saldrán los parches y la gravedad máxima de las vulnerabilidades que corrigen. Los fallos urgentes o ya explotados se siguen corrigiendo de inmediato, sin esperar al calendario. Los parches salen tanto para la rama activa (16.x) como para la de mantenimiento (15.5.x).

9.3. La versión de este curso: Next.js 16.4

Este curso se llama “de Next.js 16.4”, no “de Next.js 16.4.3”, y la diferencia importa. Los ejemplos se verificaron con 16.4.3, que era la última versión el 23 de octubre de 2026. El 2 de noviembre de 2026 se publicó 16.4.4, y next info lo avisa así en un proyecto con 16.4.3:

Relevant Packages:
  next: 16.4.3 // There is a newer version (16.4.4) available, upgrade recommended!

Cuando leas esto habrá probablemente más parches. Tu regla debe ser: la menor del curso (16.4) con el último parche disponible. Hay tres motivos:

  1. Seguridad. Con el calendario mensual, cada pocas semanas sale un parche que corrige vulnerabilidades. Una aplicación Next.js tiene un servidor expuesto a Internet: un fallo en cómo procesa las peticiones de Server Components o de Server Actions afecta a todas las aplicaciones que no se actualicen, como pasó en diciembre de 2025.
  2. Riesgo mínimo. Un parche no cambia APIs ni comportamientos por defecto. Por eso puedes seguir este curso con 16.4.4 o posteriores sin que cambie nada de lo que lees.
  3. Correcciones de fallos. Si algo no funciona como debería, lo primero que te pedirán en una incidencia de GitHub es probar con la última versión.
# Instala la última 16.4.x (y las versiones de React que pide)
npm install next@16.4 react@latest react-dom@latest
# Comprueba la versión instalada
npx next --version

⚠️ Importante: En una aplicación con servidor, no actualizar es un riesgo real. Suscríbete al blog de Next.js (nextjs.org/blog) y aplica los parches de tu rama en cuanto salgan. Si usas un gestor de dependencias automático (Dependabot, Renovate), configúralo para que proponga los parches de next en cuanto se publiquen.


10. ¿Qué versión elegir? Next.js 16 o Next.js 15

Este curso usa Next.js 16.4. Pero muchos proyectos reales siguen en Next.js 15, y el salto de la 15 a la 16 es grande. Por eso el curso indica, apartado por apartado, qué no tendrás si sigues en Next.js 15 y qué hacer en su lugar.

10.1. Qué cambia de la 15 a la 16

Estos son los cambios más visibles. Cada uno se estudia en su módulo.

Tema Next.js 15 Next.js 16
Caché Modelo anterior: fetch con cache y next.revalidate, export const revalidate, unstable_cache Cache Components: 'use cache', cacheLife, cacheTag, updateTag (el modelo anterior sigue disponible si no lo activas)
Bundler Webpack por defecto; Turbopack con --turbopack Turbopack por defecto; Webpack con --webpack
Interceptar peticiones middleware.ts proxy.ts (runtime de Node.js)
APIs de petición Asíncronas, con acceso síncrono aún tolerado con aviso Solo asíncronas
next lint Existe Eliminado: se usa eslint directamente
revalidateTag Un argumento Exige un segundo argumento (perfil)
Navegación Prefetch clásico Prefetch incremental, navegaciones instantáneas (16.3)
Salida de next build Con tamaños por ruta Sin tamaños; con tiempos por fase
Carpeta de next dev .next .next/dev
Requisitos Node.js 18.18+ Node.js 20.9+, TypeScript 5.1+, navegadores modernos (Chrome/Edge/Firefox 111+, Safari 16.4+)
Fin de soporte 31-01-2027 Activo hasta la 17; después, dos años desde octubre de 2025

10.2. Un mismo código, dos comportamientos 🆕 16.0

🆕 Desde Next.js 16.0. La opción cacheComponents no existe en Next.js 15. Si usas Next.js 15: una página que lee datos sin marcar se prerenderiza en el build con los datos de ese momento; para que se genere en cada petición añade export const dynamic = 'force-dynamic' a la página (en Next.js 16 con cacheComponents esa línea rompe el build).

La diferencia de filosofía se ve con un ejemplo muy pequeño. Esta página lee el número de departamentos sin <Suspense> y sin caché:

// app/sin-suspense/page.tsx
const API = process.env.API_URL ?? 'http://localhost:3000/api'

export default async function SinSuspense() {
  const respuesta = await fetch(`${API}/departamentos`)
  const datos: { total: number } = await respuesta.json()
  return <p>Hay {datos.total} departamentos</p>
}

En un proyecto recién creado con Next.js 16.4, que activa Cache Components, next build falla con este mensaje real:

Error: Route "/sin-suspense": Next.js encountered uncached or runtime data during prerendering.
`fetch(...)`, `cookies()`, `headers()`, `params`, `searchParams`, or `connection()` accessed outside of `<Suspense>` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
  - [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
  - [cache] For uncached data (`fetch`, database calls): cache the access with `"use cache"` (does not apply to `connection()`)
  - [block] Set `export const instant = false` to allow a blocking route

En Next.js 15.5.28, con la plantilla de create-next-app, la misma página compila sin errores:

Route (app)                         Size  First Load JS
┌ ○ /                            5.47 kB         119 kB
├ ○ /_not-found                      0 B         114 kB
└ ○ /sin-suspense                    0 B         114 kB
○  (Static)  prerendered as static content

¿Cuál de los dos tiene razón? Mira el símbolo: ○ (Static). Next.js 15 hizo el fetch una sola vez, durante el build, guardó el HTML resultante y lo servirá así para siempre. Si mañana se crea un departamento nuevo, la página seguirá diciendo “Hay 7 departamentos” hasta la próxima compilación. No hay error, pero tampoco aviso: es un dato congelado sin que lo hayas pedido.

Next.js 16 con Cache Components se niega a adivinar. Te obliga a decidir entre las dos primeras soluciones que propone el mensaje, que son justo las que has visto en el apartado 6: o el dato se cachea a propósito ('use cache', como en /cacheada), o se genera en cada petición y le das un sitio de espera (<Suspense>, como en /) para que el resto de la página pueda servirse al instante. Es más estricto, pero los errores aparecen al compilar, no en producción.

💡 Consejo: Este ejemplo resume por qué la 16 se recomienda para proyectos nuevos: las decisiones de caché pasan de ser implícitas a ser explícitas. Lo estudiarás en profundidad en el módulo 09.

10.3. Criterio para decidir

Situación Recomendación
Proyecto nuevo Next.js 16, con el último parche de la última menor. Tienes soporte activo, Cache Components y Turbopack.
Estás aprendiendo Next.js 16. Es lo que encontrarás en la documentación oficial actual.
Proyecto en producción con Next.js 15 y poco margen Sigue en 15.5.x, aplica todos los parches de seguridad y planifica la migración antes del 31-01-2027.
Proyecto en producción con Next.js 14 o anterior No tiene soporte: migra cuanto antes. Pasa primero a 15 y después a 16, o directamente a 16 con los codemods (módulo 24).
Dependes de una biblioteca que aún no funciona con la 16 15.5.x mientras tanto, vigilando la fecha de fin de soporte.
Servidor con Node.js 18 que no puedes actualizar Next.js 16 no funciona. Actualiza Node.js; si no puedes, 15.5.x temporalmente.

⚠️ Importante: Quedarse en Next.js 15 tiene fecha de caducidad. A partir del 31-01-2027 no habrá más parches de seguridad para esa rama, y con el ritmo actual de descubrimiento de vulnerabilidades, una aplicación con servidor sin parches es un riesgo serio.


11. Cómo leer las marcas de este curso

Como el curso enseña Next.js 16.4 pero quiere servirte también si trabajas con Next.js 15, cada apartado afectado por una diferencia de versión lleva una marca al final de su título:

  • 🆕 16.x: la función no existe en Next.js 15.
  • 🔄 16.x: la función existe en Next.js 15, pero cambia (de nombre, de comportamiento por defecto o de sintaxis).

Justo debajo del título aparece siempre una caja con lo que pierdes y qué hacer. Ya has visto una real en el apartado 10.2. Así se verá, por ejemplo, en el módulo 13:

🔄 Cambia en Next.js 16.0. En Next.js 15 el fichero se llama middleware.ts y exporta una función middleware, que por defecto se ejecuta en el runtime Edge. Si usas Next.js 15: usa middleware.ts con el mismo contenido y la función llamada middleware.

Y así una función que no existe en la 15:

🆕 Desde Next.js 16.4. No existe en Next.js 15. Si usas Next.js 15: no hay equivalente directo; …

Reglas para leerlas:

  1. Si un apartado no tiene marca, funciona igual en Next.js 15. Las marcas solo se ponen cuando hay diferencia comprobada.
  2. Las alternativas para Next.js 15 se han compilado en un proyecto real creado con create-next-app@15.5.28.
  3. Si todo un módulo depende de una novedad, la caja aparece al principio, después de los objetivos.
  4. Las versiones menores importan: algo marcado 🆕 16.3 no existe en la 16.2. Si tu proyecto está en una 16 antigua, actualiza a la última 16.x: es gratis y no rompe nada.
  5. Los parches no llevan marca nunca: entre 16.4.3 y 16.4.4 no cambian las APIs.

💡 Consejo: Si sigues el curso con Next.js 15, lee primero el apartado normal (para entender la idea) y después la caja (para saber cómo hacerlo en tu versión). Así, cuando migres a la 16, ya sabrás qué cambia.


12. Mapa del curso

Mapa del curso en seis bloques: fundamentos, rutas y componentes, datos, aplicación completa, producción y proyecto final

Figura 11. Los 26 módulos agrupados en seis bloques. Cada bloque añade una capa a la aplicación EMPRESA.

Bloque Módulos Qué aprenderás
Fundamentos 00, 01, 02 Qué es Next.js, cómo crear un proyecto y cómo se genera cada página
Rutas y componentes 03 a 07 App Router, rutas dinámicas, Server y Client Components, estilos, imágenes y metadatos
Datos 08 a 12 Leer datos en el servidor, caché, Server Actions, formularios y APIs propias
Aplicación completa 13 a 19 Proxy, errores, rutas avanzadas, base de datos, autenticación, estado en cliente e idiomas
Producción 20 a 24 Rendimiento, pruebas, seguridad, despliegue y migración
Proyecto final 25 La aplicación EMPRESA completa, descargable

12.1. El hilo conductor: la aplicación EMPRESA

A lo largo del curso construirás una aplicación full stack con dos partes:

  • Un catálogo público de la tienda de la empresa (productos, ficha de cada producto), estático y en caché, rápido y bien posicionado en buscadores. Es el caso de /cacheada del apartado 6.
  • Una zona privada de gestión (empleados, departamentos, pedidos) con inicio de sesión y roles, con datos siempre actuales.

Cada módulo se puede leer por separado, pero los ejemplos usan siempre los mismos datos, así que irás reconociendo los departamentos, empleados y pedidos.

12.2. Los datos

  • Módulos 00 a 15: la API EMPRESA, la misma de los cursos de React y Angular. Descárgala de recursos/empresa-api.zip, descomprímela y arráncala con node server.mjs. Queda en http://localhost:3000/api. No tiene dependencias: solo necesitas Node.js 22 o superior.
  • A partir del módulo 16: una base de datos SQLite propia, gestionada con Drizzle, que se carga con los mismos datos.

⚠️ Importante: La API EMPRESA y Next.js usan por defecto el mismo puerto, el 3000. Si arrancas la API primero, next dev detectará que el puerto está ocupado y probará otro, pero next start no lo hace, y es mejor no depender de eso: arranca Next.js siempre en el 3001 con npm run dev -- -p 3001. Lo verás en el módulo 01.

12.3. Qué necesitas saber antes

  • HTML y CSS: estructura de una página, selectores, flexbox. Repásalo en el Curso de HTML y CSS si lo necesitas.
  • JavaScript moderno: módulos, async/await, desestructuración, fetch. Está todo en el Curso de JavaScript.
  • React: componentes, props, estado, useState, useEffect, Suspense. Es muy recomendable haber hecho el Curso de React, al menos hasta el módulo de Server Components. Este curso remite a él para todo lo que es de React y no de Next.js.
  • TypeScript básico: tipos, interfaces, genéricos sencillos. Los ejemplos están en TypeScript, como en el Curso de React.

12.4. Requisitos técnicos

Herramienta Versión mínima Comprobación
Node.js 20.9 (recomendada la 24 LTS) node --version
npm La que trae Node.js npm --version
Navegador Chrome, Edge o Firefox 111+, Safari 16.4+  
Editor VS Code o similar, con soporte de TypeScript  

13. Ejercicios

Ejercicio 1: ¿Biblioteca o framework?

Clasifica cada responsabilidad según la asuma React, Next.js o ninguno de los dos (la tienes que resolver tú con otra herramienta):

  1. Volver a dibujar un componente cuando cambia su estado.
  2. Convertir el fichero app/productos/page.tsx en la URL /productos.
  3. Guardar los pedidos en una base de datos.
  4. Dividir el JavaScript en trozos para que cada página descargue solo lo suyo.
  5. Definir qué es un Server Component y cómo se serializa su resultado.
  6. Generar el HTML de una página durante next build.
  7. Comprobar que el email de un formulario tiene un formato válido.
  8. Poner un <title> distinto en cada página.
Ver solución
Responsabilidad Quién Por qué
1. Volver a dibujar al cambiar el estado React Es exactamente lo que hace React: estado y render.
2. Fichero → URL Next.js React no sabe nada de URL. El enrutado por carpetas es una convención de Next.js.
3. Guardar en base de datos Ninguno Next.js te da dónde ejecutar el código (Server Actions), pero la base de datos y el ORM los eliges tú (en el curso, SQLite con Drizzle).
4. Dividir el JavaScript Next.js Lo hace su bundler, Turbopack, durante next build.
5. Definir Server Components React La especificación es de React 19. Next.js la implementa con su servidor y su bundler.
6. HTML en next build Next.js El prerenderizado es una decisión del framework. React solo aporta la función que convierte componentes en HTML.
7. Validar un email Ninguno Puedes hacerlo a mano, pero en el curso usarás Zod.
8. <title> por página Next.js Con la Metadata API (export const metadata), como en el layout del apartado 3.2. React 19 también permite poner <title> dentro de un componente, pero Next.js lo integra con el streaming y la caché.

La respuesta 8 es intencionadamente ambigua: en el límite entre React y Next.js hay zonas grises, y es normal. La regla útil es: si la pieza tiene que ver con URL, servidor, compilación o despliegue, es del framework.

Ejercicio 2: Detectives del HTML

Abre en el navegador una SPA que conozcas (por ejemplo, la que hiciste en el Curso de React con npm run dev) y una web hecha con Next.js (la propia nextjs.org sirve). En cada una:

  1. Pulsa Ctrl+U (ver código fuente). ¿Aparece el texto visible de la página dentro del HTML?
  2. Abre las herramientas de desarrollo, pestaña Network, recarga y fíjate en la primera petición (la del documento). ¿Qué tamaño tiene?
  3. Desactiva JavaScript (en Chrome: DevTools → Ctrl+Shift+P → “Disable JavaScript”) y recarga. ¿Qué ves?
Ver solución
  1. En la SPA, el HTML solo contiene <div id="root"></div> y una etiqueta <script>: el texto visible no está. En la web de Next.js aparece el contenido completo: títulos, párrafos, enlaces.
  2. El documento de la SPA ocupa muy poco (en la medición del apartado 6, 659 bytes) porque está vacío; casi todo el peso está en los ficheros .js. El documento de Next.js ocupa bastante más (5 887 bytes en la página de ejemplo), porque lleva el HTML ya construido y, al final, la carga útil RSC en los <script> con self.__next_f.push(...).
  3. Sin JavaScript, la SPA se queda en blanco. La web de Next.js se ve casi igual, con contenido y enlaces que funcionan como enlaces normales; solo dejan de funcionar los elementos interactivos (menús desplegables, botones que cambian estado).

Un matiz que has visto en las figuras 7 y 8: en una página con streaming, las partes que llegan después dentro de un <div hidden> las coloca en su sitio un script en línea. Con JavaScript desactivado verás el texto de carga en lugar de esa parte. Las páginas que deben verse completas sin JavaScript (o para ciertos buscadores) conviene que tengan los datos importantes en el armazón, cacheados, como /cacheada.

Ejercicio 3: ¿Qué código viaja al navegador?

Fíjate en la página del apartado 2.1. Indica, para cada elemento, si su código llega al navegador:

  1. La función ListaDepartamentos.
  2. La URL de la API (process.env.API_URL).
  3. La función Contador.
  4. El tipo Departamento.
  5. El componente Inicio.
  6. El texto “DIRECCIÓN” que aparece en la lista.
Ver solución
Elemento ¿Viaja su código? Explicación
ListaDepartamentos No Es un Server Component. Solo viaja su resultado (el <ul> con los <li>).
API_URL No Se lee en el servidor. Una variable de entorno sin el prefijo NEXT_PUBLIC_ nunca se incluye en el JavaScript del cliente (módulo 13). El grep del apartado 5.3 lo confirma.
Contador Sí Está en un fichero con 'use client'. Su código se envía para poder hidratarlo y ejecutar onClick. Es el chunk que contiene “Me has pulsado”.
Departamento No Los tipos de TypeScript desaparecen al compilar, tanto en servidor como en cliente.
Inicio No También es un Server Component (no hay 'use client' en page.tsx).
“DIRECCIÓN” Sí, como dato Viaja dos veces: dentro del HTML y dentro de la carga útil RSC. Lo que no viaja es el código que lo obtuvo.

Esta es una diferencia fundamental con una SPA, donde todos los componentes, la URL de la API y la lógica de carga viajan al navegador. Haz la prueba: en el .js que genera vite build para la SPA del apartado 6 aparecen tanto la URL de la API como la palabra departamentos.

Ejercicio 4: Lee las cabeceras

Un compañero te pasa las cabeceras de dos páginas de la misma aplicación Next.js 16.4:

Página A
x-nextjs-cache: HIT
x-nextjs-prerender: 1
Cache-Control: s-maxage=3600, stale-while-revalidate=82800
Página B
x-nextjs-prerender: 1
x-nextjs-postponed: 1
Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate
Transfer-Encoding: chunked
  1. ¿Qué símbolo (○, ◐ o ƒ) tendrá cada una en la salida de next build?
  2. ¿Cuál de las dos puede guardar una CDN, y durante cuánto tiempo?
  3. ¿En cuál verías la página completa con JavaScript desactivado?
Ver solución
  1. A es ○ (estática): viene entera de lo prerenderizado (x-nextjs-prerender), se sirve desde la caché (HIT) y no hay nada aplazado. B es ◐ (prerenderizado parcial): tiene un armazón prerenderizado y una parte aplazada (x-nextjs-postponed: 1) que se genera en cada petición y llega por streaming (chunked). Son exactamente las cabeceras reales de /cacheada y de / en el apartado 6.
  2. A. s-maxage=3600 permite a una caché compartida (una CDN) guardarla 3 600 segundos, una hora. stale-while-revalidate=82800 le permite seguir sirviendo la copia antigua hasta 23 horas más mientras pide una nueva en segundo plano: en total, un día, que coincide con las columnas “Revalidate 1h” y “Expire 1d” de next build. B no se puede guardar: private, no-store lo prohíbe, porque una parte cambia en cada petición.
  3. En A. En B verías el armazón con el texto de carga, porque la parte aplazada se coloca con un script (figura 7).

Ejercicio 5: ¿Qué orden uso?

Indica qué orden (next dev, next build o next start) necesitas en cada situación, y si hay alguna trampa:

  1. Quieres ver al instante cómo queda un cambio de color mientras programas.
  2. Quieres saber si una página nueva será estática o dinámica.
  3. Quieres comprobar cuánto tarda la página en cargar en un móvil.
  4. Has subido el código al servidor de producción y quieres arrancarlo.
  5. Tu compañero dice que en su equipo “funciona”, pero en la integración continua falla con un error de tipos.
  6. La página principal prerenderiza datos de la API EMPRESA y next build falla con un error de conexión.
Ver solución
  1. next dev: recarga en caliente al guardar.
  2. next build: es la única que clasifica las rutas (○, ◐, ƒ). En desarrollo todo se genera en cada petición, así que no te lo dice.
  3. next build y después next start, nunca next dev. El modo desarrollo compila bajo demanda, añade código de depuración y no prerenderiza: sus tiempos no se parecen a los reales (la primera petición del apartado 4.1 tardó 6,4 s solo por compilar).
  4. next build y next start. La trampa: next start no compila. Si solo copias el código fuente y ejecutas next start, falla porque no hay .next.
  5. Su equipo usa next dev, que no comprueba los tipos del proyecto entero; la integración continua ejecuta next build, que sí lo hace. Solución: ejecutar npm run build antes de subir cambios.
  6. next build ejecuta tu código para prerenderizar, así que la API tiene que estar en marcha durante el build. Arráncala antes de compilar (o, si la página no debe ser estática, haz que esos datos se lean en cada petición dentro de <Suspense>).

Ejercicio 6: Elige la herramienta

Para cada proyecto, elige entre React + Vite, Next.js, Astro, Nuxt o React Router v7, y justifica en una frase:

  1. El blog técnico de un departamento de informática, con 300 artículos en Markdown y un buscador.
  2. Un panel interno de fichajes para 40 empleados, detrás de un login, con un backend en Spring Boot ya hecho.
  3. Una tienda online de 5 000 productos que debe salir bien en Google y tener carrito, login y gestión de pedidos.
  4. La web de una empresa cuyo equipo lleva cinco años trabajando con Vue.
  5. Una SPA con React Router que ya funciona, a la que quieres añadir renderizado en servidor sin cambiar de router ni de bundler.
Ver solución
  1. Astro. Es contenido casi puro, con interacción puntual (el buscador). Astro genera HTML sin JavaScript y permite una “isla” interactiva para buscar. Next.js también serviría (las páginas serían ○), pero arrastraría la base fija de JavaScript del apartado 6.3 en cada página.
  2. React + Vite. No importan los buscadores, los usuarios están en la red de la empresa y el backend ya existe. Un servidor Next.js solo añadiría una pieza más que desplegar.
  3. Next.js. Necesita SEO (catálogo prerenderizado y cacheado), datos actualizados (carrito, pedidos), autenticación y código de servidor. Es justo el caso que resuelve, y es el proyecto de este curso.
  4. Nuxt. El conocimiento del equipo pesa más que las diferencias técnicas entre Nuxt y Next.js, que resuelven problemas muy parecidos.
  5. React Router v7. Permite pasar del modo SPA al modo framework con el mismo router y con Vite, de forma gradual.

No hay respuestas absolutas: lo importante es la justificación. Una respuesta como “Next.js para todo” no es incorrecta técnicamente, pero ignora el coste de mantener un servidor cuando no hace falta.

Ejercicio 7: Calcula el soporte

Con la política de soporte del apartado 9.1, responde:

  1. Si Next.js 17 sale el 20 de octubre de 2026, ¿en qué fecha pasaría la 16 a Maintenance LTS? ¿Y hasta cuándo recibiría parches de seguridad?
  2. Si Next.js 17 se retrasa a marzo de 2027, ¿cambia la fecha de fin de soporte de la 16?
  3. Un cliente te dice el 15 de febrero de 2027 que su aplicación está en Next.js 15.5.28. ¿Qué le dices?
  4. Otro cliente está en Next.js 16.4.3 y el blog de Next.js anuncia para la semana que viene parches de seguridad de gravedad alta. ¿Qué versión debe instalar y qué riesgo corre al hacerlo?
Ver solución
  1. Pasaría a Maintenance LTS el mismo día que sale la 17. El mantenimiento dura dos años desde el lanzamiento original de la 16 (21-10-2025), así que recibiría parches hasta el 21-10-2027, salvo ampliación como la que tuvo la 15.
  2. No. La fecha final se cuenta desde el lanzamiento de la 16, no desde el de la 17. Lo que cambia es cuánto dura cada fase: la 16 estaría más tiempo en Active LTS (recibiendo funciones nuevas) y menos en Maintenance LTS.
  3. Que su versión ya no tiene soporte desde el 31-01-2027: no recibirá parches de seguridad aunque se descubran vulnerabilidades. Debe migrar a la 16 (o a la mayor vigente) cuanto antes. El módulo 24 explica cómo hacerlo con los codemods oficiales.
  4. Debe instalar el parche que salga para su rama (un 16.4.x posterior, o el último de la menor más reciente si ya hubiera una 16.5), con npm install next@16.4 en cuanto se publique, y volver a compilar y desplegar. El riesgo es mínimo: los parches no cambian APIs. El riesgo real es no hacerlo, porque una vez publicado el parche los detalles de la vulnerabilidad se hacen públicos y cualquiera puede intentar explotarla en servidores sin actualizar.

Ejercicio 8: ¿Qué versión tengo?

En un proyecto Next.js (puedes crear uno con npx create-next-app@latest mi-prueba --yes, aunque lo veremos con calma en el módulo 01), ejecuta:

npx next --version
npx next info
npm view next dist-tags

Interpreta la salida: ¿qué versión tienes?, ¿hay una más nueva?, ¿qué significa la etiqueta backport?

Ver solución

Esta es la salida real en el equipo donde se preparó el curso, el 2 de noviembre de 2026 (recortada):

$ npx next --version
Next.js v16.4.3

$ npx next info
Binaries:
  Node: 24.19.0
  npm: 11.6.2
Relevant Packages:
  next: 16.4.3 // There is a newer version (16.4.4) available, upgrade recommended!
  react: 19.3.0
  react-dom: 19.3.0
  typescript: 5.9.3

$ npm view next dist-tags
{
  "backport": "15.5.28",
  "latest": "16.4.4",
  "canary": "16.5.0-canary.36",
  ...
}
  • next --version muestra la versión instalada en el proyecto.
  • next info muestra además Node.js, React y TypeScript, y avisa si hay una versión de parche más nueva. Es lo primero que te pedirán si abres una incidencia en GitHub.
  • dist-tags muestra las etiquetas de npm: latest es la que instala npm install next (la última estable de la 16), backport es la última de la rama 15 (la que instala npm install next@backport) y canary es la de pruebas. Fíjate en que canary ya apunta a la 16.5: la siguiente versión menor está en camino.

Tus números serán seguramente más altos. Si tu versión mayor y menor coinciden con las del curso (16.4.x), todo lo que leas aquí funcionará igual. Si next info te avisa de un parche, instálalo como en el apartado 9.3.

Ejercicio 9: Interpreta las marcas

Tienes un proyecto en Next.js 15.5.28. Mientras sigues el curso, encuentras estos encabezados. Para cada uno, di si puedes usar el código tal cual, si tienes que adaptarlo o si no hay forma de usarlo:

  1. ### Rutas dinámicas con [id] (sin marca).
  2. ### params y searchParams asíncronos 🔄 16.0.
  3. ### ensureStatic 🆕 16.4.
  4. ### El fichero proxy.ts 🔄 16.0.
  5. Tu proyecto está en Next.js 16.2 y encuentras ### Partial Prefetching 🆕 16.3.
Ver solución
  1. Tal cual. Sin marca significa que funciona igual en Next.js 15.
  2. Adaptar, aunque poco. En la 15 ya son asíncronos, pero se tolera el acceso síncrono con un aviso. La caja te dirá exactamente qué cambia; en la práctica, si escribes await params como en la 16, funciona en las dos.
  3. No hay forma directa. Es una novedad de la 16.4. La caja te dirá qué hacer en su lugar (normalmente, revisar a mano la salida de next build para comprobar que la ruta sigue siendo estática).
  4. Adaptar. En la 15 el fichero se llama middleware.ts y la función, middleware. La lógica es la misma.
  5. No la tienes, pero la solución es fácil: actualiza a la última 16.x con npm install next@latest. Las versiones menores no rompen código, así que no hay motivo para quedarse en una 16 antigua.

Ejercicio 10: Predice el resultado

Sin ejecutarlo, predice qué ocurre al hacer next build en Next.js 16.4 (con la configuración de create-next-app) y en Next.js 15.5 con cada una de estas variantes de la página sin-suspense del apartado 10.2:

  1. La página tal cual.
  2. La página con el fetch dentro de un componente envuelto en <Suspense fallback={<p>Cargando...</p>}>.
  3. La página tal cual, pero añadiendo export const dynamic = 'force-dynamic'.
Ver solución
Variante Next.js 16.4 (Cache Components) Next.js 15.5
1. Tal cual El build falla: “uncached or runtime data during prerendering”. Compila. La ruta queda ○ (Static) con el dato congelado en el momento del build.
2. Con <Suspense> Compila. La ruta queda ◐ (Partial Prerender): el armazón con “Cargando…” es estático y el número llega por streaming en cada petición. Compila, igual que en el caso 1: estática, con el dato del build. Suspense no cambia la decisión estática o dinámica en la 15.
3. Con dynamic = 'force-dynamic' El build falla: “Route segment config “dynamic” is not compatible with nextConfig.cacheComponents. Please remove it.” Compila. La ruta pasa a ƒ (Dynamic): se genera en cada petición.

Todos estos resultados se han obtenido compilando de verdad las seis combinaciones. La variante 2 en la 16 es el mismo patrón que la página / del apartado 2.1. La 3 en la 15 es la alternativa de la caja 🆕 del apartado 10.2. La lección: el mismo código puede comportarse de forma muy distinta según la versión y la configuración, y por eso conviene mirar siempre la lista de rutas que imprime next build.

Ejercicio 11: Las tres generaciones de código

Clasifica cada fragmento como Pages Router, App Router con la caché anterior (Next.js 13-15) o App Router con Cache Components (Next.js 16):

// A
export async function getServerSideProps() {
  const res = await fetch('http://localhost:3000/api/empleados')
  return { props: { empleados: await res.json() } }
}
// B
export const revalidate = 3600

export default async function Productos() {
  const res = await fetch('http://localhost:3000/api/productos')
  // ...
}
// C
import { cacheLife } from 'next/cache'

export default async function Productos() {
  'use cache'
  cacheLife('hours')
  const res = await fetch('http://localhost:3000/api/productos')
  // ...
}
// D
const res = await fetch('http://localhost:3000/api/productos', {
  next: { revalidate: 3600, tags: ['productos'] },
})
Ver solución
  • A: Pages Router. getServerSideProps solo existe en la carpeta pages/. Si lo ves, el código es del modelo original. Lo leerás en el módulo 24.
  • B: App Router con la caché anterior. export const revalidate es una opción de segmento del modelo de Next.js 13-15. Con cacheComponents activado no se usa (igual que dynamic, rompe el build).
  • C: App Router con Cache Components. 'use cache' y cacheLife son el modelo actual, el mismo de la página /cacheada del apartado 6 (módulo 09).
  • D: App Router con la caché anterior. Las opciones next.revalidate y next.tags de fetch pertenecen al modelo de Next.js 13-15.

B y D hacen, a grandes rasgos, lo mismo que C: guardar la lista de productos durante una hora. Si sigues en Next.js 15, B y D son tus herramientas. Si buscas ayuda en Internet, esta clasificación te dirá enseguida si el ejemplo te sirve.

Ejercicio 12 (propuesto): Repite la medición en tu equipo

Reproduce el experimento del apartado 6 con tus propias herramientas:

  1. Crea la página del apartado 2.1 en un proyecto Next.js y la del apartado 6 en un proyecto Vite (npm create vite@latest, plantilla react-ts). Arranca la API EMPRESA.
  2. Compila ambas (npm run build) y sírvelas en producción: npm start -- -p 3001 para Next.js y npm run preview para Vite.
  3. En Chrome, abre DevTools → Network, marca “Disable cache” y elige la limitación “Slow 4G”. En la pestaña Performance, limita también la CPU (4x).
  4. Recarga cada página varias veces y anota: tamaño del documento, JavaScript transferido, momento del primer pintado y momento en que aparece la lista.
  5. Añade la página /cacheada y repite.

Compara tus resultados con la tabla del apartado 6.2. ¿Se mantienen las tendencias? ¿Qué cambia si añades a la SPA una biblioteca de 50 kB que solo usa la lista (por ejemplo, para formatear presupuestos con Intl envuelto en una biblioteca)? ¿Y si la añades a ListaDepartamentos en Next.js?


Resumen

Concepto API o herramienta Idea clave
Next.js next Framework full stack de React: rutas, servidor, compilación, caché y despliegue
Biblioteca frente a framework   Tú llamas a la biblioteca; el framework te llama a ti siguiendo sus convenciones
Server Component async function sin 'use client' Se ejecuta solo en el servidor; su código no viaja al navegador
Client Component 'use client' Se renderiza en el servidor y se hidrata en el navegador para ser interactivo
Proyecto nuevo create-next-app --yes App Router, TypeScript, Tailwind 4, ESLint flat, AGENTS.md, Cache Components activado
Layout raíz app/layout.tsx Único sitio con <html> y <body>; exporta metadata
Desarrollo next dev Compila bajo demanda, recarga al guardar, escribe en .next/dev; no sirve para medir
Compilación next build Compila, comprueba tipos, prerenderiza y clasifica las rutas (○, ◐, ƒ)
Producción next start Sirve .next sin compilar; renderiza solo lo dinámico
Armazón y streaming <Suspense> Lo estático sale al instante; lo dinámico llega después en la misma respuesta
Prerenderizado parcial ◐, x-nextjs-postponed Una página con parte estática y parte generada en cada petición
Carga útil RSC self.__next_f El árbol de Server Components serializado, con referencias a los Client Components
Hidratación   React engancha los eventos al HTML que ya existe, sin volver a crearlo
Caché de página 'use cache', x-nextjs-cache: HIT La página entera es estática y una CDN puede guardarla
SPA frente a Next.js Medición del apartado 6 Next.js envía más JS base, pero pinta antes; lo cacheado gana en todo
Pages Router pages/, getServerSideProps El modelo original (2016); hay que saber leerlo
App Router app/, page.tsx, layout.tsx Modelo actual desde la 13.4, con Server Components
Cache Components cacheComponents, 'use cache' Caché explícita; recomendada desde 16.4 y por defecto en 17
Alternativas Vite, React Router v7, Astro, Nuxt SPA interna, modelo HTTP, contenido, Vue
Active LTS Next.js 16 Funciones nuevas, correcciones y seguridad hasta la siguiente mayor
Maintenance LTS Next.js 15 Solo seguridad, hasta el 31-01-2027
Versión del curso Next.js 16.4 (verificado con 16.4.3) Instala siempre el último parche 16.4.x
Parches de seguridad nextjs.org/blog Anunciados cada mes; aplícalos siempre
Versión instalada next --version, next info Comprueba también si hay un parche más reciente
Marcas del curso 🆕 16.x, 🔄 16.x Qué no tiene Next.js 15 y qué hacer en su lugar
Puertos API en 3000, Next en 3001 Evita que las dos aplicaciones choquen