---
name: build-errors-nextjs
description: >
  Resolución de errores de compilación Next.js: next build, React Server
  Components, hydration mismatch, Turbopack y middleware. Incluye errores de
  ISR, optimización de imágenes, CSS modules y variables de entorno.
  Cargar cuando un build Next.js falle o haya errores de renderizado.
version: "1.1.0"
evolved: true
evolved-from: "1.0.0"
evolved-at: "2026-05-12"
evolved-by: "aprender"
evolved-note: "Backport L-135 SIGM: sección diagnóstico — loop de fetches infinito = HMR panic del bundler (Turbopack), NO useEffect mal escrito; síntomas distintivos + orden de diagnóstico"
herramientasPermitidas: [Read, Bash, Grep]
exclusiones:
  - "No cargar para errores de TypeScript puros en un proyecto Next.js (TS2322, TS2345, errores de tipos en archivos .ts/.tsx) — para esos cargar `build-errors-typescript`; este skill cubre errores específicos de la plataforma Next.js."
  - "No cargar para bugs de lógica de la aplicación en runtime cuando `next build` compila (fetch que retorna datos incorrectos, routing que no funciona como se espera) — si el build pasa, es un error de lógica, no de build."
  - "No cargar para diseñar la arquitectura App Router de un proyecto nuevo (estructura de rutas, layouts, data fetching patterns) — para eso cargar `nextjs-experto` o `nextjs-patrones`."
  - "No cargar para errores de estado de React (useState, useReducer, Zustand) en componentes que ya compilan — si el build pasa pero el estado no se actualiza correctamente, es un bug de lógica de state management."
evolvable: true  # default para skill estandar
---
# Build Errors Next.js — Diagnóstico y Solución

## Cuándo NO cargar

- El error es de TypeScript puro (TS2322, TS2345) sin relación con Next.js — cargar `build-errors-typescript` para esos.
- `next build` compila pero la app falla en lógica de negocio — es un bug de lógica, no de build.
- La pregunta es cómo diseñar la estructura de App Router — para arquitectura cargar `nextjs-experto`.

## Cuándo cargar

- `next build` falla con errores de compilación o runtime
- Hydration mismatch en producción o desarrollo
- Errores de React Server Components (`"use client"` faltante o incorrecto)
- Errores de Turbopack (incompatibilidad de plugins, loaders)
- Errores de middleware, ISR revalidation o image optimization
- Variables de entorno no disponibles en el cliente o en build time

---

## Hydration Mismatch

### `Error: Hydration failed because the initial UI does not match what was rendered on the server`

**Causa**: el HTML generado en el servidor difiere del que React intenta hidratar en el cliente.

```tsx
// MAL — acceso a APIs del browser en render (solo existen en cliente)
export default function Componente() {
    const ancho = window.innerWidth;  // window no existe en servidor
    return <div>Ancho: {ancho}</div>;
}

// MAL — Date() produce valores distintos en servidor y cliente
export default function Timestamp() {
    return <span>{new Date().toLocaleString()}</span>;  // mismatch
}

// BIEN — usar useEffect para lógica solo-cliente
"use client";
import { useState, useEffect } from "react";

export default function Timestamp() {
    const [fecha, setFecha] = useState<string>("");
    useEffect(() => {
        setFecha(new Date().toLocaleString());
    }, []);
    return <span>{fecha || "Cargando..."}</span>;
}

// BIEN — suppressHydrationWarning para diferencias inevitables
<time suppressHydrationWarning>{new Date().toISOString()}</time>
```

### Hydration mismatch por nesting HTML inválido

```tsx
// MAL — <p> no puede contener <div>
function Tarjeta() {
    return (
        <p>
            <div>Contenido</div>  {/* inválido: div dentro de p */}
        </p>
    );
}

// BIEN
function Tarjeta() {
    return (
        <div>
            <p>Contenido</p>
        </div>
    );
}
```

---

## React Server Components — Errores de Directivas

### `You're importing a component that needs "use client"`

**Causa**: un Server Component importa un módulo que usa APIs del browser o hooks de React.

```tsx
// MAL — ServerPage.tsx (Server Component) importa componente con estado
import Contador from "./Contador";  // Contador usa useState

export default function ServerPage() {
    return <Contador />;  // error en build
}

// BIEN — agregar "use client" al componente que necesita estado/browser APIs
// Contador.tsx
"use client";
import { useState } from "react";

export default function Contador() {
    const [n, setN] = useState(0);
    return <button onClick={() => setN(n + 1)}>{n}</button>;
}
// ServerPage.tsx no necesita cambios
```

### `Server Component cannot use client-only hooks`

```tsx
// MAL — useRouter, usePathname, useSearchParams en Server Component
// app/pagina/page.tsx (Server Component por defecto)
import { useRouter } from "next/navigation";  // error: hook de cliente

export default function Pagina() {
    const router = useRouter();  // solo funciona en Client Components
}

// BIEN — si se necesita router: extraer a Client Component
// app/pagina/BotonNavegar.tsx
"use client";
import { useRouter } from "next/navigation";

export function BotonNavegar() {
    const router = useRouter();
    return <button onClick={() => router.push("/")}>Inicio</button>;
}
```

---

## Variables de Entorno — Errores Comunes

### Variable undefined en el cliente

**Causa**: variable sin prefijo `NEXT_PUBLIC_` usada en Client Component.

```bash
# MAL — variable privada referenciada en cliente
# .env.local
DATABASE_URL=postgres://...
API_SECRET=clave_secreta

# componente-cliente.tsx
"use client"
const url = process.env.DATABASE_URL  // undefined en cliente — correcto por seguridad
```

```tsx
// BIEN — variables publicas con prefijo NEXT_PUBLIC_
// .env.local
NEXT_PUBLIC_API_URL=https://api.ejemplo.com

// componente-cliente.tsx
"use client"
const apiUrl = process.env.NEXT_PUBLIC_API_URL  // disponible en cliente
```

### Variable undefined en build time (Static Generation)

```tsx
// BIEN — verificar presencia en build y lanzar error descriptivo
// lib/config.ts
export function getConfig() {
    const apiKey = process.env.API_KEY;
    if (!apiKey) {
        throw new Error(
            "API_KEY no está definida. " +
            "Agregar al archivo .env.local o a las variables de entorno del servidor."
        );
    }
    return { apiKey };
}
```

---

## Dynamic Import y Carga Diferida

### `Module not found` con dynamic import en Server Component

```tsx
// MAL — dynamic import de módulo solo-cliente en Server Component
import dynamic from "next/dynamic";
const Mapa = dynamic(() => import("leaflet-maps"), { ssr: false });
// Si Mapa se usa en Server Component el { ssr: false } no tiene efecto completo

// BIEN — dynamic con ssr:false solo en Client Components
"use client";
import dynamic from "next/dynamic";

const MapaDinamico = dynamic(
    () => import("./MapaInteractivo"),
    {
        ssr: false,
        loading: () => <div>Cargando mapa...</div>,
    }
);
```

---

## ISR y Revalidación

### `revalidatePath` / `revalidateTag` no funciona como se espera

```tsx
// MAL — revalidar desde Client Component
"use client";
import { revalidatePath } from "next/cache";  // error: solo en Server Actions/Route Handlers

// BIEN — revalidar en Server Action
// app/actions.ts
"use server";
import { revalidatePath } from "next/cache";

export async function actualizarDatos() {
    await db.actualizar(/* ... */);
    revalidatePath("/dashboard");  // correcto: Server Action
}
```

---

## Turbopack — Problemas de Compatibilidad

### Plugin de webpack no compatible con Turbopack

```js
// next.config.js
// MAL — plugin de webpack que no tiene equivalente en Turbopack
const withBundleAnalyzer = require("@next/bundle-analyzer")({ enabled: true });
module.exports = withBundleAnalyzer({
    experimental: { turbo: true }  // conflicto probable
});

// BIEN — verificar compatibilidad en https://nextjs.org/docs/app/api-reference/turbopack
// Usar loaders Turbopack para transformaciones custom
module.exports = {
    experimental: {
        turbo: {
            rules: {
                "*.svg": {
                    loaders: ["@svgr/webpack"],
                    as: "*.js",
                },
            },
        },
    },
};
```

---

## Optimización de Imágenes

### `Error: Invalid src prop on next/image`

```tsx
// MAL — dominio externo no configurado
import Image from "next/image";
<Image src="https://imagenes-externas.com/foto.jpg" ... />
// Error: hostname no está en la lista permitida

// BIEN — agregar en next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
    images: {
        remotePatterns: [
            {
                protocol: "https",
                hostname: "imagenes-externas.com",
                port: "",
                pathname: "/**",
            },
        ],
    },
};
module.exports = nextConfig;
```

---

## Diagnóstico — Loop de fetches infinito = HMR panic del bundler, NO `useEffect` mal escrito

**Síntoma** (L-135 SIGM 2026-05-12): el navegador entra en loop infinito fetcheando los mismos endpoints al backend (4+ sockets paralelos, requests cada 300-500ms, backend respondiendo 200 OK siempre). El primer instinto es auditar `useEffect` con dependencias inestables — pero las deps están bien (`[fetchPage]` con `useCallback(..., [])` interno) y el loop sigue.

**Causa raíz**: el bundler del dev server (Turbopack en Next.js 16+ por default, ocasionalmente webpack) está en estado de panic — emite updates HMR erróneos que re-montan toda la rama del árbol React, disparando los fetches iniciales una y otra vez. El log del container/proceso muestra:

```
FATAL: An unexpected Turbopack error occurred ...
Failed to write app endpoint /(dashboard)/ruta/page ...
Next.js package not found
```

(o errores equivalentes del bundler en uso).

**Síntomas distintivos del loop por HMR panic** vs `useEffect` mal escrito:

| Indicador | HMR panic del bundler | `useEffect` mal escrito |
|---|---|---|
| Sockets simultáneos al mismo endpoint | 4+ paralelos | 1 secuencial |
| Frecuencia entre requests | ~300-500ms (delay retry HMR) | depende del state que cambia |
| Backend response | 200 OK siempre (data correcta) | puede variar |
| Logs del dev server | FATAL/panic recurrente | sin errores |
| Reload manual del navegador | el loop continúa tras reload | el loop suele resetear |

**Orden de diagnóstico obligatorio** (saltarse paso 1 puede costar horas):

1. **Logs del dev server primero**: `docker logs <web> --tail 100 | grep -iE "FATAL|panic|error"` o equivalente local. Buscar panics del bundler.
2. **DevTools Network del navegador**: confirmar si los requests vienen del cliente JS (fetch/XHR) o de navegación de página (document).
3. **DevTools Console**: warnings de React, hidratación, hooks.
4. **SOLO ENTONCES** auditar componentes/hooks/effects.

**Solución típica** cuando es Turbopack en bind mount Windows↔Linux: forzar webpack explícitamente en el dev server.

```yaml
# docker-compose.yml — MAL (Next.js 16+ defaultea a Turbopack)
services:
  web:
    command: ["npx", "next", "dev"]

# docker-compose.yml — BIEN (declarar bundler explícito)
services:
  web:
    command: ["npx", "next", "dev", "--webpack"]
```

Patrón general: cuando un dev server soporta múltiples runtimes (webpack/turbopack, vite/esbuild), declarar explícitamente cuál se usa en `command` o `scripts/dev` — nunca asumir defaults que cambian entre versiones mayores.

---

## Gotchas / Errores comunes no obvios

- **Hydration mismatch solo en producción, no en desarrollo**: `next dev` no detecta el mismatch porque usa renderizado del lado del cliente más permisivo, pero `next build` + `next start` (o producción) aplica comparación estricta del HTML. Causa: el componente usa `new Date()`, `Math.random()`, o `window` en el render inicial, que producen valores diferentes entre servidor y cliente. Solución: mover las llamadas a `new Date()` y valores dinámicos a `useEffect` o `useState` con valor inicial `null`, renderizando el contenido dinámico solo en cliente.
- **`"use client"` en un componente que importa un Server Component produce error "cannot import Server Component in Client Component"**: el grafo de importaciones tiene un Client Component importando un Server Component directamente. Causa: en el App Router, los Server Components no pueden ser importados por Client Components — el componente debe pasarse como `children` o prop. Solución: reestructurar para que el Server Component sea un padre que pase el Client Component como children, o usar `Suspense` con `lazy()` para la porción cliente.
- **Variables de entorno NEXT_PUBLIC_* configuradas en Vercel pero no disponibles en el bundle**: las variables se definieron en el proyecto de Vercel pero no se volvió a hacer deploy después de agregarlas. Causa: `NEXT_PUBLIC_*` se embeben en el bundle durante `next build` — si se agregan después del build, el build existente no las contiene. Solución: hacer un redeploy completo después de agregar o cambiar variables `NEXT_PUBLIC_*`; para variables solo de servidor (sin prefijo) basta con un restart sin rebuild.
- **`next build` falla con "Module not found" para un alias de path configurado en `tsconfig.json`**: tsconfig paths no se propagan automáticamente a la resolución de módulos de Next.js. Causa: Next.js lee `tsconfig.json` para paths solo si hay configuración explícita en `next.config.js` o si el alias está bajo `@/`. Solución: agregar el alias también en `next.config.js` bajo `webpack.resolve.alias` (o en la sección `experimental.turbo.resolveAlias` para Turbopack) — los tsconfig paths solos no son suficientes para el bundler de Next.js.

## Checklist de Diagnóstico

- [ ] `next build 2>&1 | grep "Error"` para filtrar solo errores en el output completo.
- [ ] Para hydration: revisar si el componente usa `window`, `document`, `localStorage` o `Date` en render inicial.
- [ ] Verificar que todo componente con hooks o event handlers tiene `"use client"` al inicio.
- [ ] Variables de entorno en cliente: deben tener prefijo `NEXT_PUBLIC_`.
- [ ] Para ISR: `revalidatePath` y `revalidateTag` solo funcionan en Server Actions y Route Handlers.
- [ ] `next build --debug` para diagnóstico verbose de la compilación.
- [ ] Revisar `next.config.js` al migrar de Pages Router a App Router: algunas opciones cambian.
- [ ] Para Turbopack: verificar en la documentación oficial si el plugin/loader tiene equivalente.
