InterviewHack.ai
Empezar gratis
Blog/Preguntas de entrevista de Next.js con respuestas (35+)

Preguntas de entrevista de Next.js con respuestas (35+)

16 de septiembre de 2026

nextjsreact

Artículo SEO completo con 45 preguntas de entrevista de Next.js — fundamentos, App Router, RSC, data fetching, Server Actions, performance, auth, deployment y preguntas de escenario senior, con código real en cada respuesta.

Preguntas de entrevista de Next.js con respuestas (35+)

Next.js se volvió el estándar de facto para aplicaciones React en producción. Si estás aplicando a un rol de frontend o full-stack, vas a encontrarte con al menos una de estas preguntas en la técnica. Esta guía cubre las 40+ preguntas que más se repiten — desde conceptos base hasta arquitectura avanzada — con respuestas directas y código real para que puedas responderlas con confianza.


Fundamentos de Next.js

1. ¿Qué es Next.js y en qué se diferencia de React puro?

Respuesta: Next.js es un framework de React que agrega rendering del lado del servidor (SSR), generación de sitios estáticos (SSG), routing basado en archivos, optimización de imágenes, y manejo de API routes — todo sin configuración. React por sí solo es solo una librería de UI: te da el renderizado de componentes pero nada de routing, SSR ni build pipeline. Next.js resuelve la capa de "aplicación" encima de React.

Lo que diferencia a los candidatos fuertes: mencionan el tradeoff — Next.js agrega opinión (estructura de carpetas, convenciones de naming, hydration automática) que acelera el desarrollo pero limita la flexibilidad si el equipo tiene necesidades muy específicas de build.


2. Explicá las diferencias entre Pages Router y App Router

Respuesta directa:

| Característica | Pages Router | App Router |

|----------------|-------------|------------|

| Directorio base | pages/ | app/ |

| Server Components | No | Sí (por defecto) |

| Layouts anidados | Manual | Nativo (layout.tsx) |

| Data fetching | getServerSideProps, getStaticProps | async/await directo en componentes |

| Streaming/Suspense | No | Sí |

| Cache granular | No | Sí (por fetch) |

App Router (Next.js 13+) adopta el modelo de React Server Components: los componentes son server-first por defecto y solo marcás con "use client" cuando necesitás interactividad. Esto reduce drásticamente el JavaScript enviado al cliente.

Cuándo usar Pages Router todavía: proyectos legacy, equipos que no pueden absorber la curva de aprendizaje del modelo server/client, o librerías de terceros que aún no son compatibles con RSC.


3. ¿Qué es el Server-Side Rendering (SSR) y cuándo lo usarías?

Respuesta: En SSR, cada request genera el HTML en el servidor — el usuario recibe HTML completo (favorable para SEO y primer pintado) en lugar de HTML vacío + JS que hidrata. En Next.js con Pages Router se implementa con getServerSideProps; en App Router, cualquier Server Component que haga fetch sin cache: 'force-cache' efectivamente hace SSR.

Usarlo cuando:

  • El contenido cambia con cada request (datos del usuario, inventario en tiempo real, precios dinámicos)
  • SEO es crítico y los datos no pueden pre-generarse
  • Necesitás acceder a cookies o headers del request para personalizar

No usarlo cuando: el contenido es estático o se puede revalidar periódicamente — SSR tiene costo de latencia en cada request.

tsx
// Pages Router — SSR clásico
export async function getServerSideProps(context) {
  const { params, req } = context;
  const data = await fetchUserDashboard(params.id, req.cookies.token);
  return { props: { data } };
}

4. ¿Qué es la Static Site Generation (SSG) y cuándo conviene sobre SSR?

Respuesta: En SSG, las páginas se generan en tiempo de build como HTML estático. El resultado se sirve desde CDN — latencia mínima, sin carga en el servidor por request. En Pages Router: getStaticProps. En App Router: fetch con cache: 'force-cache' (default) o generateStaticParams.

SSG conviene cuando:

  • El contenido no cambia por usuario (posts de blog, docs, landing pages, páginas de producto)
  • El volumen de páginas es manejable en build time
  • Querés el mejor tiempo de carga posible

El problema con SSG puro: si el contenido cambia frecuentemente, el sitio queda desactualizado hasta el próximo build. La solución es ISR.

tsx
// Pages Router — SSG con revalidación
export async function getStaticProps() {
  const posts = await fetchPosts();
  return {
    props: { posts },
    revalidate: 60, // revalida cada 60s (ISR)
  };
}

5. ¿Qué es Incremental Static Regeneration (ISR)?

Respuesta: ISR combina lo mejor de SSG y SSR. Las páginas se generan estáticamente pero pueden regenerarse en background después de un tiempo de expiración, sin rebuild completo. El primer request después del TTL sirve la versión en caché (stale) y dispara una regeneración; el siguiente request ya ve la versión nueva.

En App Router, ISR se configura por fetch:

tsx
// App Router — ISR granular por fetch
async function ProductPage({ params }) {
  const product = await fetch(`/api/products/${params.id}`, {
    next: { revalidate: 3600 }, // revalida cada hora
  }).then(r => r.json());

  return <Product data={product} />;
}

O con revalidation on-demand (mejor para CMS):

ts
// app/api/revalidate/route.ts
import { revalidatePath } from "next/cache";

export async function POST(req: Request) {
  const { path } = await req.json();
  revalidatePath(path);
  return Response.json({ revalidated: true });
}

6. ¿Cómo funciona el routing en el App Router?

Respuesta: El App Router usa el sistema de archivos como fuente de verdad. Cada carpeta dentro de app/ define un segmento de ruta. Los archivos especiales determinan el comportamiento:

| Archivo | Propósito |

|---------|-----------|

| page.tsx | UI pública de la ruta |

| layout.tsx | UI compartida que envuelve hijos (persiste entre navegaciones) |

| loading.tsx | UI de loading con Suspense automático |

| error.tsx | Error boundary para el segmento |

| not-found.tsx | Render cuando notFound() se llama |

| route.ts | API endpoint (equivalente a API routes) |

Rutas dinámicas: app/blog/[slug]/page.tsx → /blog/mi-post.

Grupos de rutas (sin impacto en URL): app/(marketing)/about/page.tsx → /about.

Rutas paralelas: app/@modal/(.)producto/[id]/page.tsx para modales que preservan la URL.


7. ¿Qué son los React Server Components (RSC) y qué los hace distintos?

Respuesta: Los RSC se renderizan exclusivamente en el servidor — su código nunca llega al bundle del cliente. Pueden:

  • Acceder directamente a bases de datos, sistemas de archivos, servicios internos
  • Usar async/await sin useEffect ni estado de carga
  • Reducir el JavaScript enviado al cliente a cero para esos componentes

Lo que NO pueden hacer: usar hooks de React (useState, useEffect, etc.), acceder a APIs del browser (window, document), agregar event listeners.

tsx
// Server Component — corre solo en servidor
// NO necesitás "use client"
async function UserProfile({ userId }: { userId: string }) {
  // Esto corre en el servidor, nunca en el browser
  const user = await db.users.findUnique({ where: { id: userId } });

  return (
    <div>
      <h1>{user.name}</h1>
      {/* InteractiveButton sí necesita "use client" */}
      <InteractiveButton userId={userId} />
    </div>
  );
}

Regla práctica: empezá todo como Server Component. Agregá "use client" solo cuando necesitás interactividad, estado local o APIs del browser.


8. ¿Cuándo usás `"use client"` y cuándo no lo necesitás?

Respuesta: "use client" crea un "boundary" entre server y client. Todo lo que está dentro del componente marcado (y sus hijos directos) corre en el cliente.

Necesitás "use client" cuando:

  • Usás useState, useReducer, useEffect, useRef
  • Manejas eventos del browser (onClick, onChange, onSubmit)
  • Usás window, document, localStorage
  • Usás librerías que requieren DOM (animaciones, drag-and-drop, mapas)

No necesitás "use client" cuando:

  • El componente solo renderiza UI sin interactividad
  • Hace fetch de datos (usá async/await directamente)
  • Accede a la DB o servicios del servidor

Error común: marcar toda la app como "use client" por comodidad. Esto anula los beneficios de RSC — el bundle crece y se pierde el acceso directo al servidor.


Data Fetching y Caché

9. ¿Cómo funciona el sistema de caché de Next.js 14/15?

Respuesta: Next.js tiene cuatro capas de caché:

  1. 1Request Memoization — fetch con la misma URL+opciones en el mismo render tree se deduplica automáticamente (in-memory, solo durante la request)
  2. 2Data Cache — resultados de fetch persisten entre requests y deployments (se puede optar out con cache: 'no-store')
  3. 3Full Route Cache — HTML + RSC payload estático cacheado en el servidor
  4. 4Router Cache — prefetch de segmentos en el cliente durante la sesión del browser
tsx
// Datos frescos en cada request (SSR behavior)
const data = await fetch('/api/data', { cache: 'no-store' });

// Datos estáticos cacheados indefinidamente
const config = await fetch('/api/config', { cache: 'force-cache' });

// ISR — revalida cada 10 minutos
const posts = await fetch('/api/posts', { next: { revalidate: 600 } });

Next.js 15 cambió el default: fetch ya NO cachea por defecto (breaking change vs 14). Ahora necesitás optar IN explícitamente con force-cache si querés cacheo.


10. ¿Qué es `unstable_cache` y cuándo lo usarías?

Respuesta: unstable_cache (ahora cache en Next.js 15 RC) permite cachear el resultado de cualquier función asíncrona — no solo fetch. Útil cuando querés cachear queries de ORM (Prisma, Drizzle), llamadas a SDKs de terceros, o cualquier operación costosa.

tsx
import { unstable_cache } from "next/cache";

const getUser = unstable_cache(
  async (userId: string) => {
    return await prisma.user.findUnique({ where: { id: userId } });
  },
  ["user-by-id"], // cache key prefix
  {
    revalidate: 300, // 5 minutos
    tags: ["user"], // permite revalidar por tag
  }
);

// En tu componente
const user = await getUser(params.id);

Invalidar por tag (on-demand revalidation):

ts
import { revalidateTag } from "next/cache";
// Después de actualizar un usuario:
revalidateTag("user");

11. Diferencia entre `revalidatePath` y `revalidateTag`

Respuesta:

| | revalidatePath | revalidateTag |

|--|--|--|

| Granularidad | Por URL/path | Por tag (puede afectar muchas rutas) |

| Uso típico | "Actualicé el post con slug X" | "Actualizé cualquier post" |

| Efecto | Purga esa ruta específica | Purga todos los fetches con ese tag |

ts
// Después de editar un post específico
revalidatePath(`/blog/${slug}`);

// Después de cualquier cambio en posts
revalidateTag("blog-posts");
// Todos los fetches con { next: { tags: ['blog-posts'] } } se invalidan

Server Actions y Mutations

12. ¿Qué son los Server Actions?

Respuesta: Los Server Actions son funciones async que corren en el servidor pero se pueden llamar desde Client Components (o formularios HTML). Eliminan la necesidad de crear API routes para mutaciones simples — la lógica de servidor se define inline o en archivos separados con "use server".

tsx
// app/actions.ts
"use server";

import { revalidatePath } from "next/cache";

export async function createPost(formData: FormData) {
  const title = formData.get("title") as string;

  // Directamente a la DB — sin API route
  await db.posts.create({ data: { title } });

  revalidatePath("/blog");
}
tsx
// Client Component que lo usa
"use client";
import { createPost } from "./actions";

export function PostForm() {
  return (
    <form action={createPost}>
      <input name="title" />
      <button type="submit">Crear post</button>
    </form>
  );
}

Puntos clave para la entrevista: los Server Actions se envían al servidor vía HTTP POST (no WebSockets); "use server" puede estar al top del archivo o como directiva inline dentro de una función; siempre validar inputs del servidor con Zod o similar, nunca confiar en lo que llega del cliente.


13. ¿Cómo manejás el estado de una Server Action (pending, error, success)?

Respuesta: Con el hook useActionState (antes useFormState en versiones < 19):

tsx
"use client";
import { useActionState } from "react";
import { createPost } from "./actions";

type State = { error?: string; success?: boolean };

export function PostForm() {
  const [state, action, isPending] = useActionState<State, FormData>(
    createPost,
    {}
  );

  return (
    <form action={action}>
      <input name="title" disabled={isPending} />
      {state.error && <p className="text-red-500">{state.error}</p>}
      {state.success && <p>¡Post creado!</p>}
      <button type="submit" disabled={isPending}>
        {isPending ? "Creando..." : "Crear"}
      </button>
    </form>
  );
}
ts
// La action ahora recibe state previo + formData
"use server";
export async function createPost(
  prevState: State,
  formData: FormData
): Promise<State> {
  const title = formData.get("title") as string;
  if (!title) return { error: "El título es requerido" };

  try {
    await db.posts.create({ data: { title } });
    revalidatePath("/blog");
    return { success: true };
  } catch {
    return { error: "Error al crear el post" };
  }
}

Optimización y Performance

14. ¿Cómo funciona `next/image` y por qué es importante?

Respuesta: El componente de Next.js hace optimización automática:

  • Convierte imágenes a WebP/AVIF según soporte del browser
  • Sirve el tamaño exacto según el viewport (responsive)
  • Lazy loading nativo por defecto
  • Previene Cumulative Layout Shift (CLS) requiriendo width y height
  • Optimización bajo demanda (no en build time) — ahorra tiempo de build
tsx
import Image from "next/image";

// Imagen local (Next.js infiere el tamaño)
import hero from "@/public/hero.png";
<Image src={hero} alt="Hero" priority />

// Imagen remota (requiere config en next.config)
<Image
  src="https://cdn.example.com/product.jpg"
  alt="Producto"
  width={800}
  height={600}
  sizes="(max-width: 768px) 100vw, 800px"
/>

priority prop: para imágenes above the fold — bypasa el lazy loading y mejora LCP (Largest Contentful Paint).

Error común en entrevistas: no configurar images.remotePatterns en next.config para imágenes externas — Next.js bloquea dominios no autorizados por seguridad.


15. ¿Qué es `next/font` y cómo optimiza la performance?

Respuesta: next/font descarga las fuentes en build time, las aloja en tu dominio (cero requests a Google Fonts en runtime), y genera automáticamente el CSS de font-display: swap con size-adjust para prevenir layout shift.

tsx
// app/layout.tsx
import { Inter, Geist_Mono } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  variable: "--font-inter",
  display: "swap",
});

export default function RootLayout({ children }) {
  return (
    <html className={inter.variable}>
      <body>{children}</body>
    </html>
  );
}

Fuente local:

tsx
import localFont from "next/font/local";
const myFont = localFont({ src: "./fonts/MyFont.woff2" });

Por qué importa para Core Web Vitals: elimina el CLS de fuentes externas y el FOIT/FOUT — impacto directo en LCP y CLS scores.


16. ¿Cómo implementás code splitting y lazy loading en Next.js?

Respuesta: Next.js hace code splitting automático por ruta. Para lazy loading manual:

tsx
import dynamic from "next/dynamic";

// Componente cargado solo cuando se renderiza
const HeavyChart = dynamic(() => import("./HeavyChart"), {
  loading: () => <p>Cargando gráfico...</p>,
  ssr: false, // útil para librerías que requieren browser (window, document)
});

// Con named export
const Modal = dynamic(() =>
  import("./Modal").then(mod => mod.Modal)
);

Casos de uso para ssr: false:

  • Librerías de mapas (Leaflet, Mapbox)
  • Editores de texto ricos (TipTap, Quill, Monaco)
  • Componentes que usan window o document directamente

En App Router: usás React.lazy + Suspense directamente — next/dynamic sigue funcionando pero no es la única opción.


17. ¿Cómo optimizás el bundle size en Next.js?

Respuesta:

  1. 1Analizar con @next/bundle-analyzer:
bash
npm install @next/bundle-analyzer
ANALYZE=true npm run build
  1. 2Server Components primero — código que no necesita ser interactivo no va al bundle del cliente.
  1. 3Imports con barrel files — evitá import { todo } from 'libreria-gigante'; preferí imports específicos:
ts
// Malo — importa toda la librería
import { format } from 'date-fns';

// Bien — import directo del módulo
import format from 'date-fns/format';
  1. 4next/dynamic con ssr: false para componentes pesados solo-cliente.
  1. 5Revisar dependencias — lodash vs lodash-es, moment vs date-fns, etc.
  1. 6experimental.optimizePackageImports en next.config:
js
experimental: {
  optimizePackageImports: ['@mui/material', 'lucide-react'],
}

Middleware y Autenticación

18. ¿Para qué sirve el middleware en Next.js?

Respuesta: El middleware corre en el Edge Runtime antes de que la request llegue a la página o API route. Se usa para:

  • Autenticación/autorización — redirigir a /login si no hay sesión
  • Internacionalización — detectar idioma y redirigir
  • A/B testing — asignar variante basado en cookie o geo
  • Rewrite de URLs — proxies, legacy URL handling
  • Rate limiting — bloquear bots o abusos
ts
// middleware.ts (en la raíz del proyecto)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const token = request.cookies.get("auth-token");

  if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*", "/api/protected/:path*"],
};

Limitaciones del Edge Runtime: no podés usar Node.js APIs (fs, crypto nativo, módulos nativos). Usás las Web APIs estándar.


19. ¿Cómo implementás autenticación en Next.js? ¿Qué opciones existen?

Respuesta:

Auth.js (NextAuth v5) — la opción más común para Next.js:

ts
// auth.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";

export const { auth, handlers, signIn, signOut } = NextAuth({
  providers: [GitHub],
  callbacks: {
    authorized({ auth, request }) {
      return !!auth?.user; // protege rutas
    },
  },
});

Otras opciones populares:

  • Clerk — autenticación como servicio, excelente DX, precios según MAU
  • Supabase Auth — si ya usás Supabase, integración nativa con RLS
  • Custom JWT — control total pero más código

Flujo de protección de rutas (App Router):

ts
// middleware.ts
export { auth as middleware } from "@/auth";
export const config = { matcher: ["/dashboard/:path*"] };

Lo que diferencia candidatos senior: hablan del manejo de tokens (HttpOnly cookies vs localStorage), refresh token rotation, CSRF protection, y las implicaciones de seguridad de cada elección.


API Routes y Edge Functions

20. ¿Qué son las Route Handlers y cómo difieren de las API Routes del Pages Router?

Respuesta: Las Route Handlers (App Router) usan la Web Request/Response API estándar en lugar de req/res del Node.js estilo Express.

ts
// app/api/posts/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const page = searchParams.get("page") ?? "1";

  const posts = await db.posts.findMany({
    take: 10,
    skip: (parseInt(page) - 1) * 10,
  });

  return NextResponse.json({ posts });
}

export async function POST(request: NextRequest) {
  const body = await request.json();
  // validar con Zod...
  const post = await db.posts.create({ data: body });
  return NextResponse.json(post, { status: 201 });
}

Diferencia clave: Route Handlers pueden correr en Edge Runtime o Node.js runtime. Las API Routes del Pages Router son solo Node.js.


21. ¿Cuándo usarías Edge Runtime vs Node.js Runtime?

Respuesta:

| | Edge Runtime | Node.js Runtime |

|--|--|--|

| Cold start | ~0ms | ~100-300ms |

| Disponibilidad | 300+ regiones globales | Según provider |

| APIs disponibles | Web APIs, limitado | Todo Node.js |

| Acceso a DB | Solo databases Edge-compatible (Neon, PlanetScale) | Cualquier DB |

| fs, módulos nativos | No | Sí |

| Ideal para | Middleware, auth checks, redirects, A/B | API routes con DB, procesamiento pesado |

ts
// Forzar Edge en una route específica
export const runtime = "edge";

// Forzar Node.js
export const runtime = "nodejs";

Consejo para la entrevista: la elección correcta depende de si necesitás acceso a bases de datos tradicionales (usa Node.js) o solo lógica liviana con baja latencia (usa Edge).


Patrones Avanzados

22. ¿Qué es el patrón de composición de layouts en App Router?

Respuesta: En App Router, los layouts se anidan automáticamente. Cada layout.tsx envuelve a sus hijos y a los layouts de segmentos hijos:

app/
  layout.tsx          → RootLayout (html, body, nav global)
  dashboard/
    layout.tsx        → DashboardLayout (sidebar, header de dashboard)
    page.tsx          → Dashboard home
    analytics/
      page.tsx        → Analytics page (hereda ambos layouts)
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }) {
  return (
    <div className="flex">
      <Sidebar />
      <main className="flex-1">{children}</main>
    </div>
  );
}

Beneficio clave: los layouts no se re-renderean al navegar entre rutas del mismo segmento — solo el page.tsx cambia. Esto preserva scroll position y estado local del layout.


23. ¿Cómo implementás parallel routes y intercepting routes?

Respuesta: Las parallel routes permiten renderizar múltiples páginas en el mismo layout simultáneamente (con @folder):

app/
  layout.tsx
  @team/page.tsx      → renderiza en slot "team"
  @analytics/page.tsx → renderiza en slot "analytics"
tsx
// app/layout.tsx
export default function Layout({ children, team, analytics }) {
  return (
    <>
      {children}
      {team}
      {analytics}
    </>
  );
}

Intercepting routes permiten mostrar una ruta en contexto de otra (modales, drawers):

app/
  foto/[id]/page.tsx  → página completa de la foto
  @modal/(.)foto/[id]/page.tsx → modal cuando se navega desde el feed

Caso de uso clásico: galería de fotos estilo Instagram donde hacer click en una foto abre un modal con la URL actualizada, pero recargando la URL directa muestra la página completa.


24. ¿Cómo manejás errores en App Router?

Respuesta: App Router usa el sistema de Error Boundaries de React, pero simplificado con archivos convencionales:

tsx
// app/dashboard/error.tsx
"use client"; // Error boundaries DEBEN ser client components

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>Algo salió mal</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Reintentar</button>
    </div>
  );
}

global-error.tsx (en app/) captura errores que rompen el root layout.

not-found.tsx se activa cuando llamás notFound() desde una page/layout:

tsx
import { notFound } from "next/navigation";

async function ProductPage({ params }) {
  const product = await getProduct(params.id);
  if (!product) notFound(); // muestra el not-found.tsx más cercano
  return <Product data={product} />;
}

25. ¿Cómo implementás internacionalización (i18n) en Next.js?

Respuesta: Con App Router, el patrón recomendado es routing basado en segmentos de idioma:

app/
  [lang]/
    layout.tsx
    page.tsx
    blog/page.tsx
tsx
// middleware.ts — detecta idioma y redirige
import { match } from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";

const locales = ["es", "en"];
const defaultLocale = "es";

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  const hasLocale = locales.some(l => pathname.startsWith(`/${l}`));
  if (hasLocale) return;

  const locale = getLocale(request);
  request.nextUrl.pathname = `/${locale}${pathname}`;
  return NextResponse.redirect(request.nextUrl);
}

Librerías populares: next-intl (la más adoptada con App Router), i18next, o el approach nativo con JSON files simples.


26. ¿Qué es Streaming y cómo funciona con Suspense en Next.js?

Respuesta: En lugar de esperar a que todos los datos carguen para enviar HTML, Streaming envía el HTML del shell primero y "streamea" el contenido de cada Suspense boundary a medida que sus datos están disponibles.

tsx
// app/dashboard/page.tsx — Server Component
import { Suspense } from "react";

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      {/* Este se muestra inmediatamente */}
      <QuickStats />

      {/* Este streamea cuando sus datos cargan */}
      <Suspense fallback={<ChartSkeleton />}>
        <RevenueChart />  {/* async Server Component */}
      </Suspense>

      <Suspense fallback={<TableSkeleton />}>
        <RecentOrders />  {/* async Server Component */}
      </Suspense>
    </div>
  );
}

Por qué es importante: en SSR clásico, si RevenueChart tarda 2 segundos, el usuario espera 2 segundos para ver TODA la página. Con Streaming, ve el layout y QuickStats inmediatamente — mejor Time to First Byte y UX percibida.


27. ¿Cómo funciona el prefetching en Next.js?

Respuesta: Next.js hace prefetch automático de rutas enlazadas con :

  • En desarrollo: prefetch desactivado (por performance del dev server)
  • En producción: cuando un entra al viewport, Next.js prefetchea el RSC payload de esa ruta en background
tsx
// Prefetch desactivado explícitamente
<Link href="/pesado" prefetch={false}>Página pesada</Link>

// Prefetch programático
import { useRouter } from "next/navigation";
const router = useRouter();

// En un hover handler:
router.prefetch("/dashboard");

Cómo funciona internamente: Next.js hace un fetch al endpoint de RSC (?_rsc=...) y cachea el payload en el Router Cache del cliente. La navegación posterior es instantánea porque el HTML ya está ahí.


28. ¿Qué es `generateMetadata` y cómo lo usás para SEO dinámico?

Respuesta: generateMetadata es una función async que genera metadata dinámica para cada ruta en App Router:

tsx
// app/blog/[slug]/page.tsx
import type { Metadata } from "next";

export async function generateMetadata({
  params,
}: {
  params: { slug: string };
}): Promise<Metadata> {
  const post = await fetchPost(params.slug);

  if (!post) return { title: "Post no encontrado" };

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: post.coverImage, width: 1200, height: 630 }],
      type: "article",
      publishedTime: post.publishedAt,
    },
    twitter: {
      card: "summary_large_image",
      title: post.title,
    },
    alternates: {
      canonical: `https://misitio.com/blog/${params.slug}`,
    },
  };
}

Punto importante: generateMetadata y generateStaticParams se ejecutan en el servidor — podés hacer DB queries directamente.


29. ¿Cómo generás páginas estáticas dinámicamente con `generateStaticParams`?

Respuesta: generateStaticParams reemplaza a getStaticPaths del Pages Router. Le dice a Next.js qué rutas pre-generar en build time:

tsx
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await fetchAllPosts();
  return posts.map(post => ({ slug: post.slug }));
}

// Esta función es llamada con cada slug retornado arriba
export default async function BlogPost({ params }) {
  const post = await fetchPost(params.slug);
  return <Article post={post} />;
}

Comportamiento para slugs no pre-generados:

  • dynamicParams = true (default): se generan on-demand y se cachean
  • dynamicParams = false: devuelve 404 si el slug no está en la lista
tsx
export const dynamicParams = false; // 404 para slugs no listados

30. ¿Cómo testeas componentes en Next.js?

Respuesta: Testing en Next.js tiene capas:

Unit/Component tests con Vitest + Testing Library:

tsx
// __tests__/Button.test.tsx
import { render, screen } from "@testing-library/react";
import { Button } from "@/components/Button";

test("muestra el texto y dispara el handler", async () => {
  const handler = vi.fn();
  render(<Button onClick={handler}>Guardar</Button>);
  await userEvent.click(screen.getByRole("button", { name: "Guardar" }));
  expect(handler).toHaveBeenCalledOnce();
});

Server Components — son más difíciles de unit-testear porque son async. La recomendación del equipo de Next.js es testearlos via E2E o integración.

E2E con Playwright:

ts
// e2e/blog.spec.ts
import { test, expect } from "@playwright/test";

test("muestra el post", async ({ page }) => {
  await page.goto("/blog/mi-post");
  await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
});

Para testar API routes: usar next-test-api-route-handler o los Request/Response nativos directamente.


Deployment y Configuración

31. ¿Qué opciones de deployment tiene Next.js?

Respuesta:

| Opción | Cuándo usarla |

|--------|--------------|

| Vercel | Default, integración total, Edge Network, Analytics |

| Self-hosted Node.js | next start después de next build — control total |

| Static export (output: 'export') | Solo SSG, sin server — sirve en S3, Netlify, Cloudflare Pages |

| Docker | Orquestación con K8s, ambientes on-premise |

| Netlify / Cloudflare Pages | Alternativas a Vercel con Edge support |

Para static export:

js
// next.config.js
module.exports = { output: "export" };

Restricciones del static export: no se puede usar SSR, middleware, API routes, ni Image Optimization del servidor.

Docker multi-stage (standalone output):

dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM node:20-alpine AS runner
COPY --from=builder /app/.next/standalone ./
EXPOSE 3000
CMD ["node", "server.js"]

Con output: 'standalone' en next.config.js para generar un bundle minimizado.


32. ¿Cómo configurás variables de entorno en Next.js?

Respuesta: Next.js tiene un sistema de env vars con prefijos que determinan exposición:

| Variable | Disponible en |

|----------|--------------|

| NEXT_PUBLIC_* | Cliente y servidor (embebida en el bundle) |

| Sin prefijo | Solo servidor (nunca va al browser) |

bash
# .env.local (no commitear)
DATABASE_URL=postgresql://...          # solo servidor
NEXT_PUBLIC_API_URL=https://api.ejemplo.com  # cliente + servidor
STRIPE_SECRET_KEY=sk_live_...         # solo servidor — NUNCA public
tsx
// En Server Component o API route:
const db = new Client(process.env.DATABASE_URL); // ok

// En Client Component:
const apiUrl = process.env.NEXT_PUBLIC_API_URL; // ok
const secret = process.env.STRIPE_SECRET_KEY; // UNDEFINED en cliente — correcto

Error crítico de seguridad: usar NEXT_PUBLIC_ para secretos (API keys, database URLs). Esos valores quedan expuestos en el bundle JS visible en el browser.


33. ¿Qué es el archivo `next.config.js` y qué configuraciones son más comunes?

js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Imágenes de dominios externos
  images: {
    remotePatterns: [
      { protocol: "https", hostname: "cdn.example.com" },
    ],
  },

  // Rewrites — proxy a otra API sin exponer el dominio
  async rewrites() {
    return [
      { source: "/api/legacy/:path*", destination: "https://old-api.com/:path*" },
    ];
  },

  // Redirects (HTTP 301/302)
  async redirects() {
    return [
      { source: "/old-about", destination: "/about", permanent: true },
    ];
  },

  // Headers de seguridad
  async headers() {
    return [{
      source: "/(.*)",
      headers: [
        { key: "X-Frame-Options", value: "DENY" },
        { key: "X-Content-Type-Options", value: "nosniff" },
      ],
    }];
  },

  // Output para Docker
  output: "standalone",

  // Features experimentales
  experimental: {
    ppr: true, // Partial Prerendering (Next.js 15)
  },
};

34. ¿Qué es Partial Prerendering (PPR)?

Respuesta: PPR es una feature experimental de Next.js 15 que combina SSG y SSR en la misma ruta. El "shell" estático (layout, header, contenido que no depende de datos dinámicos) se sirve instantáneamente desde la CDN. Los "holes" dinámicos (datos del usuario, carrito, inventario) se streamean después.

tsx
// app/product/[id]/page.tsx
import { Suspense } from "react";

export default function ProductPage({ params }) {
  return (
    <div>
      {/* Estático — sirve desde CDN inmediatamente */}
      <ProductDescription id={params.id} />

      {/* Dinámico — streamea después */}
      <Suspense fallback={<PriceSkeleton />}>
        <DynamicPrice id={params.id} />
      </Suspense>
      <Suspense fallback={<StockSkeleton />}>
        <StockStatus id={params.id} />
      </Suspense>
    </div>
  );
}

Lo innovador: sin PPR tenías que elegir entre SSG (todo estático) o SSR (todo dinámico). PPR hace que la misma ruta sea parcialmente estática y parcialmente dinámica.


35. ¿Cuál es la diferencia entre `useRouter` del Pages Router y del App Router?

Respuesta: Son imports diferentes con APIs distintas:

tsx
// Pages Router
import { useRouter } from "next/router";
const router = useRouter();
router.push("/destino");
router.query.id; // acceso a params

// App Router
import { useRouter } from "next/navigation";
const router = useRouter();
router.push("/destino");
// Los params NO están en useRouter — usás useParams():
import { useParams } from "next/navigation";
const params = useParams(); // { id: "123" }

Otros hooks del App Router:

  • usePathname() — pathname actual (reemplaza router.pathname)
  • useSearchParams() — query string (reemplaza router.query)
  • useParams() — segmentos dinámicos

Error común: mezclar imports entre routers. Si importás de "next/router" en un proyecto con App Router, vas a ver errores en runtime.


36. ¿Cómo implementás optimistic updates con Server Actions?

Respuesta: Con el hook useOptimistic de React 19:

tsx
"use client";
import { useOptimistic } from "react";
import { toggleLike } from "./actions";

export function LikeButton({ post }: { post: Post }) {
  const [optimisticLikes, setOptimisticLikes] = useOptimistic(
    post.likes,
    (current, increment: number) => current + increment
  );

  return (
    <form
      action={async () => {
        setOptimisticLikes(post.liked ? -1 : 1); // UI actualiza inmediatamente
        await toggleLike(post.id); // server action en background
      }}
    >
      <button type="submit">
        {optimisticLikes} likes
      </button>
    </form>
  );
}

Si la server action falla, useOptimistic revierte al valor original automáticamente. Esto es lo que diferencia optimistic updates bien hechos — el manejo del rollback es automático.


37. ¿Cómo protegés rutas en App Router sin middleware?

Respuesta: Además del middleware, podés proteger rutas directamente en los layouts o pages:

tsx
// app/dashboard/layout.tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";

export default async function DashboardLayout({ children }) {
  const session = await auth();

  if (!session?.user) {
    redirect("/login");
  }

  return (
    <div>
      <DashboardNav user={session.user} />
      {children}
    </div>
  );
}

Cuándo usar layout vs middleware:

  • Middleware: protección perimetral, más rápido (Edge Runtime), ideal cuando la check es simple (¿existe el token?)
  • Layout: validaciones más complejas con la DB (¿tiene el rol correcto? ¿suscripción activa?), acceso al contexto del usuario real

Importante: estas dos capas se complementan. El middleware bloquea a quienes claramente no tienen sesión; el layout valida permisos granulares.


38. ¿Qué diferencia hay entre `redirect` y `permanentRedirect` en App Router?

Respuesta:

tsx
import { redirect, permanentRedirect } from "next/navigation";

// 307 Temporary Redirect — el destino puede cambiar
redirect("/nueva-pagina");

// 308 Permanent Redirect — Google y browsers cachean esto
permanentRedirect("/nueva-url-definitiva");

redirect (307): usar para redirects dinámicos basados en estado (autenticación, permisos, flujo de onboarding).

permanentRedirect (308): usar para cambios de URL definitivos (migración de /blog/vieja-url → /blog/nueva-url). Google transfiere PageRank. Los browsers recuerdan y ya no hacen la request.

En middleware: usar NextResponse.redirect() con el status code explícito.


39. ¿Cómo depurás problemas de hidratación en Next.js?

Respuesta: Los errores de hidratación ocurren cuando el HTML generado por el servidor no coincide con lo que React renderiza en el cliente. Causas comunes:

  1. 1Valores que difieren entre server y client (fechas, Math.random(), window):
tsx
// Malo — Date.now() difiere entre server y client
<p>Generado: {Date.now()}</p>

// Bien — generar en servidor y pasar como prop, o suprimir en cliente
  1. 2Extensiones del browser que modifican el DOM (password managers, adblockers)
  1. 3CSS-in-JS sin SSR support correcto
  1. 4Acceso a localStorage/window en el render inicial:
tsx
// Malo
const theme = localStorage.getItem("theme"); // falla en servidor

// Bien — solo en efectos o con "use client" + mounted state
const [theme, setTheme] = useState("");
useEffect(() => setTheme(localStorage.getItem("theme") ?? ""), []);

Cómo diagnosticar: el error de React en consola muestra exactamente qué nodo difiere. suppressHydrationWarning en el elemento como último recurso (nunca para datos importantes).


40. ¿Qué cambió en Next.js 15 respecto a versiones anteriores?

Respuesta: Los cambios más importantes de Next.js 15:

  1. 1fetch no cachea por defecto — el mayor breaking change. En v14, fetch era force-cache por defecto. En v15 es no-store. Si migrás una app, los GETs que antes eran estáticos ahora son dinámicos.
  1. 2params y searchParams son ahora Promises:
tsx
// v15 — params es un Promise
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params; // hay que awaitearlo
}
  1. 3unstable_after API — corre código después de que la response se envió al cliente (ideal para analytics, logging):
tsx
import { unstable_after as after } from "next/server";
export default function Page() {
  after(() => { logPageView(); }); // corre después de enviar el HTML
  return <PageContent />;
}
  1. 4React 19 por defecto — useActionState, useFormStatus, use(), optimistic updates mejorados.
  1. 5Turbopack estable para dev — el bundler en Rust que reemplaza Webpack para desarrollo. Hasta 5x más rápido en cold start, hasta 96% más rápido en HMR.

Preguntas de Escenario (las que más se hacen en técnicas)

41. "Tenés una página de producto con datos que cambian una vez por día. ¿Cómo la implementarías?"

Respuesta modelo: ISR con revalidate: 86400 (24 horas). Se pre-genera en build time y se regenera en background una vez por día:

tsx
// App Router
export default async function ProductPage({ params }) {
  const product = await fetch(`/api/products/${params.id}`, {
    next: { revalidate: 86400 },
  }).then(r => r.json());
  return <Product data={product} />;
}

Si los datos cambian de forma predecible (ej: el equipo editorial publica a las 9am), agregar un webhook desde el CMS que llame a revalidatePath o revalidateTag — así la página siempre está fresca cuando el contenido se actualiza.


42. "El dashboard de tu app carga lento. ¿Qué checkeás primero?"

Respuesta modelo estructurada:

  1. 1Network tab — ¿Cuánto tarda el TTFB? ¿Hay waterfall de API calls?
  2. 2¿Las queries son en paralelo? Promise.all vs waterfall:
tsx
// Malo — waterfall
const user = await getUser(id);
const orders = await getOrders(id); // espera a user innecesariamente

// Bien — paralelo
const [user, orders] = await Promise.all([getUser(id), getOrders(id)]);
  1. 3¿Hay N+1 queries? — un bucle que hace una query por item
  2. 4¿El bundle del cliente es grande? — Bundle Analyzer
  3. 5¿Las imágenes están optimizadas? — next/image
  4. 6¿Los Suspense boundaries están bien ubicados? — ¿el loading más lento bloquea el resto?

43. ¿Cómo pasarías un proyecto grande de Pages Router a App Router?

Respuesta: Migración incremental — App Router y Pages Router coexisten en el mismo proyecto:

  1. 1Empezar por rutas nuevas en app/ — no tocar nada de pages/
  2. 2Migrar rutas simples (solo SSG, sin lógica compleja) primero
  3. 3El shared root layout (app/layout.tsx) reemplaza a _app.tsx y _document.tsx
  4. 4Migrar getStaticProps → Server Components con fetch
  5. 5Migrar getServerSideProps → Server Components con cache: 'no-store'
  6. 6Mover estado global a Context en un Client Component wrapper
  7. 7Migrar API routes a Route Handlers
  8. 8Al final, cuando todas las rutas estén en app/, eliminar el directorio pages/

La trampa más común: intentar migrar todo de una vez. La migración incremental permite deployar en cada paso sin regressions.


Preguntas Bonus de Senior

44. ¿Cómo implementarías un sistema de feature flags en Next.js?

tsx
// lib/flags.ts
import { unstable_flag as flag } from "@vercel/flags/next";

export const newDashboard = flag({
  key: "new-dashboard",
  async decide() {
    const user = await auth();
    return user?.plan === "pro"; // flag por plan
  },
});

// app/dashboard/page.tsx
import { newDashboard } from "@/lib/flags";

export default async function Dashboard() {
  const showNew = await newDashboard();
  return showNew ? <NewDashboard /> : <OldDashboard />;
}

45. ¿Qué es el Turbopack y cómo impacta el desarrollo?

Respuesta: Turbopack es el nuevo bundler de Next.js escrito en Rust. En Next.js 15, es estable para next dev. Mejoras reportadas:

  • ~76% más rápido en arranque del servidor de dev
  • ~96% más rápido en Fast Refresh (HMR)
  • Caché incremental persistente entre sesiones

Para habilitarlo: next dev --turbopack (o es el default en Next.js 15 para dev).

Todavía no se usa en next build (producción) — webpack sigue siendo el bundler de producción por ahora. Turbopack para prod está en roadmap.


Tips finales para la entrevista

Antes de terminar, tres patrones que distinguen a los candidatos que pasan:

1. Siempre mencionás los tradeoffs. No existe una respuesta universalmente correcta — SSR vs SSG depende de los requisitos. Decir "depende de X y Y" demuestra madurez.

2. Hablás de la experiencia del usuario final. LCP, CLS, TTFB no son solo métricas técnicas — explican por qué las decisiones de performance importan al negocio.

3. Conocés las versiones. Next.js 15 cambió defaults importantes (fetch caching, params como Promise). Mencionarlo muestra que seguís el ecosystem activamente.


*Este artículo es parte del material de preparación de [InterviewHack.ai](https://interviewhack.ai) — preparación de entrevistas con tu CV y las vacantes reales que te interesan.*

FAQ

¿Cuál es la diferencia entre App Router y Pages Router en Next.js?+

App Router (Next.js 13+) usa React Server Components por defecto, layouts anidados nativos, y data fetching con async/await directo. Pages Router usa getServerSideProps/getStaticProps y todo es Client Component. App Router reduce el bundle del cliente y permite streaming con Suspense.

¿Qué son los React Server Components en Next.js?+

Son componentes que corren exclusivamente en el servidor — su código nunca llega al browser. Pueden hacer queries a la DB directamente con async/await, sin hooks ni useEffect. En App Router son el default; marcás 'use client' solo cuando necesitás interactividad o APIs del browser.

¿Cuándo usás SSR vs SSG vs ISR en Next.js?+

SSR cuando el contenido varía por usuario o request (dashboards, datos en tiempo real). SSG cuando el contenido es estático y se puede generar en build time (landing pages, posts). ISR cuando el contenido cambia periódicamente pero no por usuario — combina velocidad de SSG con frescura configurable.

¿Qué son los Server Actions en Next.js?+

Funciones async marcadas con 'use server' que corren en el servidor pero se llaman desde el cliente o formularios HTML. Eliminan la necesidad de crear API routes para mutaciones. Pueden acceder a la DB directamente y llamar a revalidatePath o revalidateTag para actualizar la caché.

¿Qué cambió en Next.js 15 respecto a versiones anteriores?+

El cambio más importante: fetch ya no cachea por defecto (era force-cache en v14, ahora es no-store). También: params y searchParams son ahora Promises que hay que awaitear, React 19 por defecto, Turbopack estable para dev, y la API unstable_after para código post-response.

¿Cómo protegés rutas que requieren autenticación en Next.js?+

Dos capas: middleware (corre en Edge Runtime, verifica si existe el token, redirige a /login rápidamente) y layouts del App Router (validan permisos complejos con la DB, como roles o suscripciones activas). El middleware protege el perímetro; el layout valida lógica de negocio.

¿Qué es ISR y cómo se implementa en App Router?+

Incremental Static Regeneration permite que páginas estáticas se regeneren en background sin rebuild completo. En App Router: fetch('/api/data', { next: { revalidate: 3600 } }) revalida cada hora. También podés invalidar on-demand con revalidatePath('/ruta') o revalidateTag('mi-tag') desde un Server Action o API route.

¿Qué es el Middleware de Next.js y para qué se usa?+

El middleware corre en Edge Runtime antes de que la request llegue a la página — latencia mínima desde cualquier región. Se usa para auth checks, internacionalización, A/B testing, rewrites y rate limiting. Limitación: no puede usar Node.js APIs como fs o módulos nativos, solo Web APIs estándar.

Artículos relacionados

Cómo usar el método STAR en entrevistas (con ejemplos reales)

Aprende a responder preguntas difíciles usando el método STAR en entrevistas. Consejos y ejemplos concretos para roles remotos tech de LATAM.

Cómo conseguir trabajo remoto en dólares desde LATAM: guía real

Descubre consejos concretos para conseguir trabajo remoto en dólares desde LATAM: estrategias de búsqueda, preparación y entrevista para roles tecnológicos.

Las mejores preguntas para hacerle al entrevistador al final

Descubre las mejores preguntas para hacerle al entrevistador al final, útiles para entrevistas tech remotas, diferenciándote y logrando roles en dólares.

Cómo preparar entrevistas de desarrollo sin experiencia previa

Consejos prácticos para enfrentar entrevistas de tu primer trabajo como desarrollador, incluso sin experiencia. Técnicas para destacar y convencer en cada etapa.

Preparate para tu entrevista real

Pegá el link de tu vacante: investigamos quién te entrevista y te ensayamos en vivo.

Empezar gratis →

¿Tenés entrevista próxima? Instalá el copiloto en vivo →

InterviewHack.ai

Preparate para la entrevista exacta: quién te entrevista, tu CV a medida y coach real.

Producto

VacantesRevisar CV (ATS) gratis¿Cómo suena tu inglés?¿Te pagan bien?Reporte de sueldos LATAMCursos gratisBlogCV a medidaPráctica habladaEs gratis

Empleos remotos

ReactPythonFull-StackLATAMArgentinaMéxicoVer todas →

Preparate

Práctica habladaFrontendBackendAI EngineerPor empresaVendete con tu CV

Empresa

Buscás talentoAcerca deContactoPrivacidadTérminos

© 2026 InterviewHack.ai · Tu CV es tuyo. Nunca se usa para entrenar nada. · Un producto de IA-PTY