Tema
Crear proyecto
Vas a crear tu primer proyecto de Next.js con la configuración recomendada (App Router, TypeScript, Tailwind y ESLint), entender cada archivo y carpeta que se genera, y conocer los comandos y la configuración básica.
Concepto
create-next-app es el asistente oficial: crea la estructura, instala dependencias y deja todo configurado. Next.js usa una convención clara: las carpetas dentro de app/ son las rutas de tu sitio.
Requisitos: Node.js 20.9 o superior (instalación).
Crear el proyecto
bash
npx create-next-app@latest mi-sitioEl asistente pregunta (respuestas recomendadas):
txt
✔ Would you like to use the recommended Next.js defaults? › No, customize settings
✔ Would you like to use TypeScript? › Yes
✔ Which linter would you like to use? › ESLint
✔ Would you like to use Tailwind CSS? › Yes
✔ Would you like your code inside a `src/` directory? › No
✔ Would you like to use App Router? (recommended) › Yes
✔ Would you like to use Turbopack? › Yes
✔ Would you like to customize the import alias (`@/*` by default)? › NoO en un solo comando sin preguntas:
bash
npx create-next-app@latest mi-sitio --ts --tailwind --eslint --app --use-npm --import-alias "@/*"bash
cd mi-sitio
npm run devAbre http://localhost:3000.
Estructura generada
txt
mi-sitio/
├── app/ ← rutas de tu app (App Router)
│ ├── favicon.ico
│ ├── globals.css ← estilos globales (incluye Tailwind)
│ ├── layout.tsx ← layout raíz: envuelve TODAS las páginas
│ └── page.tsx ← página de inicio "/"
├── public/ ← archivos estáticos servidos tal cual (/logo.svg)
├── next.config.ts ← configuración de Next.js
├── tsconfig.json ← configuración de TypeScript (incluye alias @/*)
├── eslint.config.mjs
├── postcss.config.mjs ← Tailwind CSS v4
├── package.json
└── next-env.d.ts ← tipos de Next (no lo edites)Los archivos clave
tsx
import type { Metadata } from 'next'
import { Geist } from 'next/font/google'
import './globals.css'
// next/font descarga la fuente en el build y la sirve desde tu dominio
const geist = Geist({ subsets: ['latin'], variable: '--font-geist' })
// SEO por defecto para todo el sitio
export const metadata: Metadata = {
title: 'Mi sitio',
description: 'Creado con Next.js'
}
// El layout raíz es obligatorio y debe tener <html> y <body>
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="es">
<body className={`${geist.variable} antialiased`}>{children}</body>
</html>
)
}tsx
// Página de inicio: corresponde a la ruta "/"
export default function Home() {
return (
<main className="mx-auto max-w-2xl p-8">
<h1 className="text-4xl font-bold">¡Hola, Next.js! 👋</h1>
<p className="mt-4 text-gray-600">
Edita <code>app/page.tsx</code> y guarda para ver los cambios.
</p>
</main>
)
}css
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
}ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
/* opciones de configuración */
}
export default nextConfigjson
{
"name": "mi-sitio",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "eslint"
},
"dependencies": {
"next": "^16.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "^16.0.0",
"tailwindcss": "^4",
"typescript": "^5"
}
}Versiones
Next.js publica versiones mayores aproximadamente una vez al año. Este curso es válido para Next.js 15 y 16: la estructura del App Router, Server Components y Server Actions son iguales. Cuando haya diferencias importantes entre versiones, lo indicaremos.
Comandos
| Comando | Qué hace |
|---|---|
npm run dev | Servidor de desarrollo con recarga instantánea (Turbopack) |
npm run build | Compila la app optimizada para producción |
npm start | Sirve el build de producción (requiere npm run build antes) |
npm run lint | Revisa errores con ESLint |
Al ejecutar npm run build verás un resumen de tus rutas:
txt
Route (app) Size First Load JS
┌ ○ / 5.2 kB 107 kB
├ ○ /nosotros 142 B 102 kB
└ ƒ /dashboard 1.1 kB 103 kB
○ (Static) prerendered as static content
ƒ (Dynamic) server-rendered on demandTu primera ruta nueva
Crear una ruta es crear una carpeta con un archivo page.tsx:
txt
app/
├── page.tsx → /
├── nosotros/
│ └── page.tsx → /nosotros
└── servicios/
├── page.tsx → /servicios
└── diseno-web/
└── page.tsx → /servicios/diseno-webtsx
import Link from 'next/link'
export default function Nosotros() {
return (
<main className="mx-auto max-w-2xl p-8">
<h1 className="text-3xl font-bold">Nosotros</h1>
<p className="mt-4">Somos un equipo apasionado por la web.</p>
{/* Link navega sin recargar y precarga la página destino */}
<Link href="/" className="mt-6 inline-block text-violet-700 underline">
← Volver al inicio
</Link>
</main>
)
}tsx
import Link from 'next/link'
export default function Home() {
return (
<main className="mx-auto max-w-2xl p-8">
<h1 className="text-4xl font-bold">Bienvenido</h1>
<nav className="mt-6 flex gap-4">
<Link href="/nosotros" className="text-violet-700 underline">Nosotros</Link>
<Link href="/servicios" className="text-violet-700 underline">Servicios</Link>
</nav>
</main>
)
}Usa siempre Link de next/link para navegar dentro de tu sitio: evita recargas y precarga automáticamente las páginas de los enlaces visibles.
Organizar el código
Sólo las carpetas con page.tsx (o route.ts) se vuelven rutas públicas. Puedes guardar otros archivos dentro de app/ sin problema, pero es común tener carpetas al nivel raíz:
txt
mi-sitio/
├── app/ ← sólo rutas, layouts y archivos especiales
├── components/ ← componentes reutilizables (Header, Boton...)
├── lib/ ← utilidades, cliente de base de datos, validaciones
├── public/
└── ...Gracias al alias @/* configurado en tsconfig.json, importas sin rutas relativas largas:
tsx
import Header from '@/components/Header'
import { formatearPrecio } from '@/lib/formato'También puedes crear carpetas privadas dentro de app/ con guion bajo (app/_components/): Next.js las ignora como rutas.
Variables de entorno
Crea .env.local en la raíz (ya está en .gitignore):
env
# Sólo en el servidor (seguras para secretos)
DATABASE_URL="postgresql://usuario:password@localhost:5432/mi_sitio"
API_SECRET="clave-super-secreta"
# Visibles en el navegador: prefijo NEXT_PUBLIC_
NEXT_PUBLIC_SITE_URL="http://localhost:3000"tsx
// En un Server Component o Server Action: cualquier variable
const secreto = process.env.API_SECRET
// En un Client Component: sólo las NEXT_PUBLIC_
const url = process.env.NEXT_PUBLIC_SITE_URLSeguridad
Las variables NEXT_PUBLIC_ se incrustan en el JavaScript público. Nunca pongas secretos con ese prefijo.
Extensiones recomendadas
- ESLint y Tailwind CSS IntelliSense para VS Code.
- React Developer Tools en el navegador.
- Prettier con
prettier-plugin-tailwindcsspara ordenar las clases automáticamente:
bash
npm i -D prettier prettier-plugin-tailwindcssjson
// .prettierrc
{ "semi": false, "singleQuote": true, "plugins": ["prettier-plugin-tailwindcss"] }Buena práctica
Mantén app/ enfocado en rutas. Los componentes reutilizables van en components/ y la lógica (acceso a datos, validaciones) en lib/. Te lo agradecerás cuando el proyecto crezca.
Error común
Usar etiquetas a normales para enlaces internos. Funcionan, pero recargan toda la página y pierdes la precarga. Usa Link (y a sólo para enlaces externos).
Ejercicios
- Crea un proyecto
agenciay agrega las rutas/servicios,/portafolioy/contacto, cada una con un título y un enlace de regreso al inicio. - Crea un componente
components/Navbar.tsxcon enlaces a todas las páginas y agrégalo al layout raíz para que aparezca en todo el sitio.
Ver solución
tsx
// components/Navbar.tsx
import Link from 'next/link'
const enlaces = [
{ href: '/', texto: 'Inicio' },
{ href: '/servicios', texto: 'Servicios' },
{ href: '/portafolio', texto: 'Portafolio' },
{ href: '/contacto', texto: 'Contacto' }
]
export default function Navbar() {
return (
<header className="border-b">
<nav className="mx-auto flex max-w-5xl gap-6 p-4">
<span className="font-bold text-violet-700">Agencia</span>
{enlaces.map((e) => (
<Link key={e.href} href={e.href} className="hover:text-violet-700">
{e.texto}
</Link>
))}
</nav>
</header>
)
}tsx
// app/layout.tsx
import type { Metadata } from 'next'
import Navbar from '@/components/Navbar'
import './globals.css'
export const metadata: Metadata = { title: 'Agencia', description: 'Agencia de desarrollo web' }
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="es">
<body>
<Navbar />
{children}
</body>
</html>
)
}tsx
// app/servicios/page.tsx (igual para portafolio y contacto)
import Link from 'next/link'
export default function Servicios() {
return (
<main className="mx-auto max-w-5xl p-8">
<h1 className="text-3xl font-bold">Servicios</h1>
<Link href="/" className="mt-4 inline-block underline">← Inicio</Link>
</main>
)
}Resumen
npx create-next-app@latestcrea un proyecto con App Router, TypeScript y Tailwind.app/layout.tsxenvuelve todas las páginas; cada carpeta conpage.tsxes una ruta.npm run devpara desarrollar;npm run build+npm startpara producción.- Navega con
Link; variables secretas en.env.localy públicas conNEXT_PUBLIC_.