Next.js
Crear y configurar un proyecto Next.js
Configura una base reproducible de Next.js con TypeScript, scripts, variables de entorno, estructura, linting, build y controles de producción.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
Next.js
Configura una base reproducible de Next.js con TypeScript, scripts, variables de entorno, estructura, linting, build y controles de producción.
Crear un proyecto Next.js no consiste únicamente en ejecutar create-next-app. El objetivo es obtener una base reproducible donde versiones, scripts, variables, estructura, linting y producción tengan contratos claros desde el inicio.
La CLI oficial genera una configuración coherente para la versión actual del framework:
pnpm create next-app@latest my-app
cd my-app
pnpm devEn Next.js 16.2, los valores recomendados incluyen TypeScript, ESLint, Tailwind CSS, App Router, Turbopack y el alias @/*. Estas elecciones son una base, no una arquitectura completa.
El proyecto queda preparado para cuatro actividades diferentes:
next dev → desarrollar
next build → validar y generar output
next start → ejecutar output Node.js
eslint → analizar calidad estáticaDesde Next.js 16, next build ya no ejecuta el linter automáticamente. El pipeline debe llamar eslint de forma explícita si quieres que un error de lint bloquee la entrega.
La línea actual requiere como mínimo Node.js 20.9. También debes alinear:
Una aplicación puede funcionar localmente y fallar durante build si esos entornos difieren.
Puedes documentarla mediante .nvmrc, .node-version, Volta o el campo engines:
{
"engines": {
"node": ">=20.9"
},
"packageManager": "pnpm@10.13.1"
}El campo engines no siempre instala la versión; funciona como restricción o señal según la herramienta. CI y hosting deben configurarse explícitamente.
pnpm create next-app@latest my-app --yes--yes evita preguntas y utiliza preferencias guardadas o defaults recomendados. Es conveniente para automatización, pero conviene conocer las decisiones generadas.
Con configuración interactiva puedes elegir:
src.No habilites una herramienta porque aparece en el prompt. Cada opción crea mantenimiento, reglas y expectativas de equipo.
Para aprender qué es realmente necesario:
mkdir my-app
cd my-app
pnpm init
pnpm add next@latest react@latest react-dom@latest
pnpm add -D typescript @types/node @types/react @types/react-dom eslint eslint-config-nextScripts mínimos:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"typecheck": "tsc --noEmit"
}
}Estructura mínima del App Router:
my-app/
├─ app/
│ ├─ layout.tsx
│ └─ page.tsx
├─ public/
├─ package.json
├─ tsconfig.json
├─ next-env.d.ts
└─ next.config.tsToda aplicación App Router necesita un layout raíz:
// app/layout.tsx
import type { Metadata } from "next";
import type { ReactNode } from "react";
import "./globals.css";
export const metadata: Metadata = {
title: {
default: "My App",
template: "%s | My App",
},
description: "Aplicación construida con Next.js.",
};
type RootLayoutProps = {
children: ReactNode;
};
export default function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="es">
<body>{children}</body>
</html>
);
}app/layout.tsx envuelve todas las rutas del árbol.<html> y <body> en el root layout.metadata define valores base para las rutas.No coloques allí cada provider, request o consulta global. Todo lo agregado al root participa en una frontera muy amplia.
// app/page.tsx
export default function HomePage() {
return (
<main>
<h1>Home</h1>
<p>El proyecto está funcionando.</p>
</main>
);
}page.tsx hace público el segmento. Un archivo común dentro de app no crea una URL por sí solo.
Una base real puede evolucionar así:
src/
├─ app/
│ ├─ (marketing)/
│ ├─ dashboard/
│ ├─ api/
│ └─ layout.tsx
├─ features/
│ ├─ auth/
│ ├─ orders/
│ └─ products/
├─ components/
│ └─ ui/
├─ lib/
└─ server/Define entradas del router y composición por segmento. No necesita almacenar toda la lógica del producto.
Agrupa capacidades del dominio: componentes, schemas, Actions, queries y tipos de una feature.
Contiene primitives compartidas sin depender de un dominio específico.
Puede contener acceso a datos, autenticación o servicios exclusivamente servidor. Usa server-only cuando quieras detectar imports accidentales desde cliente.
La estructura debe seguir cómo cambia el producto, no una plantilla universal.
Usar src separa código de aplicación de archivos raíz:
src/app/
public/
next.config.ts
tsconfig.json
.env.localLos archivos .env* permanecen en la raíz, no dentro de src.
No existe diferencia funcional fundamental entre app y src/app; es una decisión de organización.
create-next-app suele configurar @/*:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}Uso:
import { ProductCard } from "@/features/products/components/product-card";Un alias reduce imports relativos largos, pero no crea límites arquitectónicos. Todavía puedes provocar ciclos o importar código servidor desde cliente.
Next.js genera tipos dentro de .next/types y mantiene next-env.d.ts.
No edites manualmente next-env.d.ts; es administrado por el framework.
Una configuración estricta ayuda a detectar estados inválidos:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noEmit": true
}
}next build ejecuta validación de tipos salvo que se configure lo contrario. Ignorar errores mediante typescript.ignoreBuildErrors debe ser excepcional: puede desplegar código incompatible.
Un script explícito evita depender de comportamientos históricos:
{
"scripts": {
"lint": "eslint . --max-warnings=0"
}
}Puedes utilizar ESLint o Biome según el equipo. El valor no está en instalar ambos, sino en tener reglas reproducibles y ejecutarlas en CI.
Next.js carga archivos .env* desde la raíz:
DATABASE_URL="postgresql://..."
NEXT_PUBLIC_ANALYTICS_ID="analytics-public-id"Una variable sin NEXT_PUBLIC_ está destinada al entorno servidor:
const databaseUrl = process.env.DATABASE_URL;No basta con el nombre. Si serializas el valor o lo pasas a una Client Component, lo filtras igualmente.
const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID;El valor se inserta en el bundle durante next build. Después del build no cambia aunque la plataforma modifique la variable.
Nunca uses NEXT_PUBLIC_ para secretos.
process.env produce strings opcionales. Valida al arrancar o construir:
// src/env.ts
import { z } from "zod";
const serverSchema = z.object({
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(32),
});
export const serverEnv = serverSchema.parse({
DATABASE_URL: process.env.DATABASE_URL,
AUTH_SECRET: process.env.AUTH_SECRET,
});Mantén este módulo fuera del grafo cliente.
Next.js busca valores en process.env y después en archivos específicos del entorno. Los .local normalmente no se versionan. .env.test puede versionarse para defaults de pruebas; .env.test.local no.
La configuración vive junto a package.json:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "cdn.example.com",
pathname: "/products/**",
},
],
},
};
export default nextConfig;Este archivo se ejecuta durante fases de servidor y build; no entra en el bundle del navegador.
No copies flags experimentales de un tutorial. Cada opción puede cambiar caches, routing, bundles o compatibilidad.
En Next.js 16, Turbopack es predeterminado para desarrollo y build.
No configures experimental.turbo; esa opción pertenece a versiones anteriores. La configuración actual utiliza turbopack.
Una personalización webpack no se traduce automáticamente a Turbopack. Antes de migrar un proyecto con loaders o plugins propios, verifica compatibilidad.
create-next-app permite habilitarlo. Su integración puede aplicar memoización automática, pero no corrige componentes impuros ni algoritmos costosos.
Actívalo cuando:
No lo uses como razón para ignorar arquitectura y medición.
Un package.json útil:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint . --max-warnings=0",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:e2e": "playwright test",
"check": "pnpm lint && pnpm typecheck && pnpm test && pnpm build"
}
}No toda aplicación necesita estas herramientas desde el primer commit, pero producción debe validar al menos tipos, lint, pruebas relevantes y build.
Versiona un único lockfile. No mezcles package-lock.json, pnpm-lock.yaml y yarn.lock.
El lockfile conserva versiones transitivas para que local, CI y producción resuelvan el mismo grafo.
Para CI:
pnpm install --frozen-lockfile
pnpm checkDebe excluir:
.next/
node_modules/
.env*.localNo excluyas el lockfile. No versiones secretos. Revisa que artefactos locales de herramientas no entren al repositorio.
pnpm devPrioriza feedback y diagnósticos.
pnpm buildAnaliza rutas, tipos y output.
pnpm startEjecuta el build generado. No funciona antes de crear .next.
El comando y output dependen de configuración y plataforma. Una aplicación con funciones dinámicas no puede convertirse en estática solo cambiando un flag.
src/
├─ app/
│ ├─ (public)/
│ │ └─ page.tsx
│ ├─ dashboard/
│ │ ├─ layout.tsx
│ │ └─ page.tsx
│ └─ api/
├─ features/
│ ├─ auth/
│ └─ organizations/
├─ server/
│ ├─ auth.ts
│ └─ db.ts
└─ env.tsFlujo:
features concentra contratos de negocio.server mantiene infraestructura privada.env.ts valida configuración.check.Esto no resuelve todavía multi-tenancy ni autorización; solo crea límites donde implementarlos.
El build puede descubrir incompatibilidades, tipos y rutas no visitadas. Ejecuta next build antes de integrar.
El prefijo NEXT_PUBLIC_ significa que el valor puede inspeccionarse. Rota inmediatamente cualquier secreto filtrado.
Aumenta deuda y mezcla versiones. Elimina opciones sin propósito medido.
Permite desplegar contratos rotos. Corrige el tipo o valida el dato, no desactives el chequeo global.
Oculta responsabilidades. Agrupa utilidades por dominio o propósito.
Puede fallar el build o filtrar dependencias. Añade server-only y revisa el grafo.
Diferencias de Node generan builds inconsistentes y deprecaciones de plataforma.
next.config mínimo.next build ejecutado en CI.create-next-app crea una base; no sustituye decisiones arquitectónicas.NEXT_PUBLIC_ inserta valores públicos durante build..env vive en la raíz incluso usando src.next.config debe contener únicamente necesidades verificadas.NEXT_PUBLIC_API_KEY no puede guardar una credencial privada?next dev?src?next-env.d.ts?next build ya no ejecuta el linter; debe llamarse mediante un script explícito.App Router y routing basado en archivos explica cómo la estructura del proyecto se transforma en un árbol de URLs, layouts y boundaries.