Setup moderno de Next.js con App Router, TypeScript y Tailwind CSS
Introducción
Si estás empezando un nuevo proyecto y querés combinar lo mejor del ecosistema React con un rendimiento de primera, el App Router de Next.js 13+ es la opción más actual. Sumarle TypeScript te brinda tipado estático que evita errores en tiempo de ejecución, y Tailwind CSS permite crear interfaces atractivas sin salir del HTML. En este tutorial vas a aprender, paso a paso, cómo crear un proyecto desde cero, configurar cada herramienta y poner en práctica un ejemplo completo que puedas usar como base para tus propias aplicaciones.
A quién va dirigido: desarrolladores con conocimientos básicos de React y Node.js que quieran iniciar un proyecto moderno sin perder tiempo en configuraciones tediosas.
Qué vas a aprender:
- Instalar Node.js y verificar la versión requerida.
- Crear un proyecto Next.js con el App Router usando
create-next-app. - Estructurar carpetas y archivos de forma escalable.
- Configurar TypeScript (tsconfig.json y next.config.js).
- Instalar y adaptar Tailwind CSS (PostCSS, tailwind.config.js, globals.css).
- Crear páginas y componentes con el nuevo modelo de rutas.
- Aplicar buenas prácticas y evitar errores comunes.
- Hacer un despliegue rápido en Vercel.
Vamos a poner manos a la obra.
1. Requisitos previos
Antes de iniciar, asegurate de tener instalado lo siguiente:
Node.js versión 18.17 o superior (se recomienda la LTS). Podés verificarla con:
node --versionSalida esperada (ejemplo):
v20.11.0npm (viene con Node) o yarn / pnpm si preferís otro gestor. En este tutorial usaremos npm.
Un editor de código como VS Code, con las extensiones recomendadas para TypeScript y Tailwind (ESLint, Prettier, Tailwind CSS IntelliSense).
Si faltara alguno, instalalo antes de continuar.
2. Crear el proyecto Next.js con App Router
Next.js provee un comando interactivo que genera la base del proyecto. Ejecutá:
npx create-next-app@latest mi-app-next --ts --tailwind --eslint --appSalida esperada (resumida)
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
Creating a new Next.js app in /ruta/al/proyecto/mi-app-next.
Installing dependencies: react, react-dom, next
Installing devDependencies: typescript, @types/react, @types/node, eslint, tailwindcss, postcss, autoprefixer
Success! Created mi-app-next at /ruta/al/proyecto/mi-app-next
Inside that directory, you can run several commands:
npm run dev
Starts the development server.
npm run build
Builds the app for production.
npm start
Runs the built app in production mode.
lint
Runs ESLint.
We suggest that you begin by typing:
cd mi-app-next
npm run devEl instalador ya nos dejó configurado TypeScript, Tailwind y el App Router según las opciones que elegimos. Ahora ingresemos al directorio y arranquemos el servidor de desarrollo para verificar que todo funciona.
cd mi-app-next
npm run devSalida esperada
> mi-app-next@0.1.0 dev
> next dev
▲ Next.js 14.1.0
- Local: http://localhost:3000
- Network: http://192.168.1.10:3000
✓ Ready in 2.3sAbrí tu navegador en http://localhost:3000 y deberías ver la pantalla de bienvenida de Next.js.
3. Estructura de carpetas del App Router
Con el App Router, la convención cambió ligeramente. El directorio principal donde vive el código de la aplicación es app/. Dentro de él, cada subcarpeta representa una ruta de la UI y puede contener:
page.tsx→ la página que se renderiza en esa ruta.layout.tsx→ layout compartido (ej. encabezado, footer).loading.tsx→ UI de suspense mientras se carga datos.error.tsx→ manejo de errores.template.tsx→ similar a layout pero se reinicia en cada navegación.
Además, tenés la carpeta components/ (o src/components/ si la elegiste) para componentes reutilizables, y styles/ o app/ para estilos globales.
Vamos a echar un vistazo rápido al árbol generado:
mi-app-next/
├─ app/
│ ├─ layout.tsx
│ ├─ page.tsx
│ └─ ...
├─ components/
├─ public/
├─ styles/
│ └─ globals.css
├─ next.config.js
├─ tsconfig.json
├─ tailwind.config.js
├─ postcss.config.js
├─ package.json
└─ ...4. Configuración de TypeScript
Aunque el instalador ya dejó un tsconfig.json básico, es útil revisarlo y ajustarlo según las necesidades del proyecto. Aquí tenés una versión recomendada que incluye rutas absolutas mediante el alias @/ y opciones estrictas.
tsconfig.json
{
"compilerOptions": {
"target": "es2022",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": false,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"*/*"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"}
}Nota: el campo
pathspermite importar componentes con@/components/Buttonen lugar de rutas relativas largas.
next.config.js
El archivo de configuración de Next normalmente queda así, pero podemos agregar soporte para imágenes, internacionalización o cualquier plugin que necesitemos. Por ahora lo dejamos tal cual, pero lo incluimos para que lo tengas a mano.
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
// Descomentar si usas imágenes externas
// images: {
// domains: ['example.com'],
// },
};
module.exports = nextConfigCon esto, TypeScript ya está listo para ofrecer autocompletado y detección de errores en tiempo de desarrollo.
5. Instalación y configuración de Tailwind CSS
Aunque el comando create-next-app ya instaló Tailwind y creó los archivos de configuración, vamos a revisarlos y asegurarnos de que estén alineados con las mejores prácticas.
tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"
./app/**/*.{js,ts,jsx,tsx},
./components/**/*.{js,ts,jsx,tsx},
./src/**/*.{js,ts,jsx,tsx}, // si usas src/
],
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
},
},
spacing: {
'18': '4.5rem',
},
},
},
plugins: [],
}postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}styles/globals.css
El archivo de estilos globales debe contener las directivas de Tailwind. Verificá que tenga exactamente esto:
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Opcional: estilos personalizados */
html,
body {
@apply bg-gray-50 text-gray-900 antialiased;
}Con esto, Tailwind está listo para usar sus clases utility en cualquier archivo TSX.
Verificación rápida
Creá un componente de prueba para asegurarte de que las clases se aplican:
// components/TestBadge.tsx
export default function TestBadge() {
return (
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-primary-100 text-primary-800">
Tailwind OK
</span>
)
}Luego importalo en app/page.tsx y recargá la página. Deberías ver un badge con el color primario de Tailwind.
6. Creando una página con el App Router
El archivo app/page.tsx corresponde a la ruta raíz (/). Vamos a reemplazar su contenido por una pequeña landing que muestre un título, una descripción y un botón que navegue a una página de ejemplo.
app/page.tsx
import Link from 'next/link'
export default function HomePage() {
return (
<main className="flex min-h-screen flex-col items-center justify-center bg-gray-50 p-8">
<h1 className="mb-4 text-3xl font-bold text-gray-900">
Bienvenido a tu Next.js moderno
</h1>
<p className="mb-6 max-w-xl text-center text-gray-600">
Esta demo muestra el uso del App Router, TypeScript y Tailwind CSS.
</p>
<Link
href=
className="inline-block px-6 py-3 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg transition-shadow shadow-md hover:shadow-lg"
>
Ver ejemplo
</Link>
</main>
)
}Creando la página de ejemplo
Creá la carpeta app/ejemplo/ y dentro el archivo page.tsx.
// app/ejemplo/page.tsx
import { Button } from '@/components/Button'
export default function EjemploPage() {
return (
<section className="max-w-2xl mx-auto py-12 px-4">
<h2 className="mb-6 text-2xl font-semibold text-gray-800">
Página de ejemplo
</h2>
<Button variant="primary" onClick={() => alert('¡Botón clickeado!')>
Hacé clic aquí
</Button>
<p className="mt-4 text-gray-500">
Este botón usa un componente reutilizable con Tailwind.
</p>
</section>
)
}Component Button reutilizable
Creá components/Button.tsx:
import type { ReactNode } from 'react'
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'outline'
children: ReactNode
onClick?: () => void
}
export default function Button({
variant = 'primary',
children,
onClick,
}: ButtonProps) {
const base = "inline-flex items-center px-4 py-2 rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
const variants: Record<string, string> = {
primary: "bg-primary-600 text-primary-50 hover:bg-primary-700",
secondary: "bg-gray-200 text-gray-800 hover:bg-gray-300",
outline: "border border-gray-300 text-gray-700 hover:bg-gray-50",
}
return (
<button
className={`${base} ${variants[variant]}`}
onClick={onClick}
>
{children}
</button>
)
}Con esto tenés una UI funcional, tipada y estilizada sin escribir CSS manual.
7. Uso de datos y fetch en el App Router
El App Router permite cargar datos directamente en componentes de servidor mediante async en page.tsx o layout.tsx. Veamos un ejemplo sencillo que obtiene una lista de posts desde un endpoint falso (JSONPlaceholder).
app/posts/page.tsx
import Link from 'next/link'
type Post = {
userId: number
id: number
title: string
body: string
}
export default async function PostsPage() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
if (!res.ok) {
throw new Error('Failed to fetch posts')
}
const posts: Post[] = await res.json()
return (
<section className="py-8">
<h2 className="mb-4 text-2xl font-bold text-gray-900">
Últimos posts
</h2>
<ul className="space-y-4">
{posts.slice(0, 5).map((post) => (
<li
key={post.id}
className="p-4 bg-white rounded-lg shadow hover:shadow-md transition-shadow"
>
<Link
href={
className="text-primary-600 hover:underline"
>
{post.title}
</Link>
<p className="mt-2 text-sm text-gray-600 line-clamp-2">
{post.body}
</p>
</li>
))}
</ul>
</section>
)
}Importante: este ejemplo hace una petición a un servidor externo. En producción, considerarías usar un endpoint interno o una capa de caché (por ejemplo,
next/cacheo SWR) para evitar solicitudes en cada renderizado.
8. Buenas prácticas y errores comunes
Buenas prácticas
- Mantener componentes puros y pequeños: separar la lógica de presentación de la lógica de datos.
- Usar el alias
@/para importaciones absolutas y evitar rutas relativas largas. - Aprovechar el Server Components por defecto: solo marcá como
'use client'cuando necesites interactividad (hooks, estado, event listeners). - Configurar ESLint y Prettier para mantener un estilo consistente.
- Utilizar el built‑in
<Image>de Next.js para optimizar imágenes. - Aprovechar el caching de Next.js con
revalidateenfetcho usando el segmento de rutadynamiccuando sea necesario. - Escribir tests unitarios con Jest o Vitest para tus componentes puros.
- Documentar los props con JSDoc o TypeScript interfaces.
Errores comunes
- **Olvidar agregar `
DUGLAS MORENO
Comentarios (0)
Sé el primero en comentar.