Setup moderno de Next.js con App Router, TypeScript y Tailwind CSS

DUGLAS MORENODUGLAS MORENO 👁 12

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 --version

    Salida esperada (ejemplo):

    v20.11.0
  • npm (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 --app

Salida 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 dev

El 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 dev

Salida 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.3s

Abrí 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 paths permite importar componentes con @/components/Button en 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 = nextConfig

Con 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/cache o SWR) para evitar solicitudes en cada renderizado.

8. Buenas prácticas y errores comunes

Buenas prácticas

  1. Mantener componentes puros y pequeños: separar la lógica de presentación de la lógica de datos.
  2. Usar el alias @/ para importaciones absolutas y evitar rutas relativas largas.
  3. Aprovechar el Server Components por defecto: solo marcá como 'use client' cuando necesites interactividad (hooks, estado, event listeners).
  4. Configurar ESLint y Prettier para mantener un estilo consistente.
  5. Utilizar el built‑in <Image> de Next.js para optimizar imágenes.
  6. Aprovechar el caching de Next.js con revalidate en fetch o usando el segmento de ruta dynamic cuando sea necesario.
  7. Escribir tests unitarios con Jest o Vitest para tus componentes puros.
  8. Documentar los props con JSDoc o TypeScript interfaces.

Errores comunes

  • **Olvidar agregar `

Comentarios (0)

Sé el primero en comentar.

Dejá tu comentario