Back

Cómo Integrar ShadCN con Next.js

Cómo Integrar ShadCN con Next.js

Configurar una biblioteca de componentes UI moderna no debería sentirse como armar muebles sin instrucciones. Si estás desarrollando con Next.js 15 y quieres componentes hermosos y personalizables sin la frustración de caja negra de las bibliotecas tradicionales, esta guía te muestra exactamente cómo integrar Shadcn UI en tu proyecto.

Este artículo cubre el proceso completo de configuración de Shadcn UI con Next.js, desde la creación inicial del proyecto hasta la instalación de componentes, configuración de Tailwind CSS e implementación adecuada del modo oscuro. Aprenderás cómo evitar problemas comunes de compatibilidad con React 19 y configurar un sistema de componentes listo para producción que realmente posees.

Puntos Clave

  • Shadcn UI copia el código fuente de los componentes directamente a tu proyecto, dándote control completo de propiedad y personalización
  • El CLI maneja la instalación y gestión de dependencias, haciendo la configuración sencilla incluso con consideraciones de compatibilidad de React 19
  • La integración con Tailwind CSS es fluida, con soporte integrado de temas e implementación de modo oscuro
  • Los componentes son agnósticos al framework y funcionan tanto con App Router como con Pages Router en Next.js

Por Qué Elegir Shadcn UI para Tu Proyecto Next.js

Shadcn UI adopta un enfoque fundamentalmente diferente a las bibliotecas de componentes. En lugar de instalar una dependencia que no puedes modificar, Shadcn copia el código fuente de los componentes directamente a tu proyecto. Esto significa que obtienes:

  • Propiedad completa de tus componentes - modifica cualquier cosa, en cualquier momento
  • Diseño Tailwind-first que se integra perfectamente con tus estilos existentes
  • Cero sobrecarga en tiempo de ejecución - los componentes son solo código en tu proyecto
  • Seguridad de tipos incorporada desde el principio
  • Accesibilidad manejada correctamente por defecto a través de primitivos de Radix UI

Este enfoque brilla particularmente con el App Router de Next.js 15, donde los componentes de servidor y los límites de cliente requieren un diseño cuidadoso de componentes.

Requisitos Previos y Configuración Inicial

Antes de comenzar tu configuración de Shadcn UI con Next.js, asegúrate de tener:

  • Node.js 18.17 o superior
  • Un gestor de paquetes (npm, pnpm, yarn, o bun)
  • Familiaridad básica con React y Tailwind CSS

Creando Tu Proyecto Next.js 15

Comienza creando un nuevo proyecto Next.js con TypeScript y Tailwind CSS:

npx create-next-app@latest my-app --typescript --tailwind --app

Cuando se te solicite, selecciona estas opciones:

  • ✓ ¿Te gustaría usar ESLint?
  • ✓ ¿Te gustaría usar el directorio src/? (recomendado)
  • ✓ ¿Te gustaría personalizar el alias de importación por defecto? No

Navega a tu proyecto:

cd my-app

Instalando y Configurando Shadcn UI

El CLI de Shadcn maneja la mayor parte del trabajo pesado para la instalación de componentes. Ejecuta el comando de inicialización:

npx shadcn@latest init

Verás varias solicitudes de configuración:

  1. Estilo: Elige tu estilo preferido (Default/New York)
  2. Color base: Selecciona de los esquemas de color disponibles
  3. Variables CSS: Usa para mejor soporte de temas

Manejando Dependencias Peer de React 19

Si estás usando npm con React 19, encontrarás advertencias de dependencias peer:

npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree

El CLI te pedirá que elijas una estrategia de resolución:

? How would you like to proceed?
 Use --force
  Use --legacy-peer-deps

Selecciona --legacy-peer-deps para el enfoque más seguro. Los usuarios de pnpm, yarn o bun no verán estas advertencias y pueden proceder normalmente.

Agregando Tus Primeros Componentes

Con Shadcn inicializado, agrega componentes usando el CLI:

npx shadcn@latest add button card

Este comando:

  1. Descarga el código fuente del componente
  2. Lo coloca en src/components/ui/
  3. Instala cualquier dependencia requerida (como primitivos de Radix UI)
  4. Actualiza las importaciones de tus componentes

Así es como se ve un componente button típico de Shadcn después de la instalación:

// src/components/ui/button.tsx
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input bg-background hover:bg-accent",
        secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : "button"
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    )
  }
)
Button.displayName = "Button"

export { Button, buttonVariants }

Observa cómo el componente es completamente personalizable - puedes modificar variantes, agregar nuevas, o cambiar el estilo completamente.

Configurando Tailwind CSS para Shadcn

Shadcn UI funciona tanto con Tailwind CSS v3 como v4. El CLI configura automáticamente tu tailwind.config.js durante la inicialización. Esto es lo que se agrega:

// tailwind.config.js
module.exports = {
  darkMode: ["class"],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
  ],
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        popover: {
          DEFAULT: "hsl(var(--popover))",
          foreground: "hsl(var(--popover-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
}

Actualiza Tu globals.css

El CLI también actualiza tu globals.css con variables CSS para temas:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --primary: 221.2 83.2% 53.3%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96%;
    --secondary-foreground: 222.2 84% 4.9%;
    --muted: 210 40% 96%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96%;
    --accent-foreground: 222.2 84% 4.9%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 40% 98%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --ring: 221.2 83.2% 53.3%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
    --popover: 222.2 84% 4.9%;
    --popover-foreground: 210 40% 98%;
    --primary: 217.2 91.2% 59.8%;
    --primary-foreground: 222.2 84% 4.9%;
    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;
    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;
    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 40% 98%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --ring: 224.3 76.3% 94.1%;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}

Implementando Modo Oscuro con next-themes

Para una implementación de modo oscuro lista para producción, usa next-themes para evitar problemas de hidratación.

Instalar next-themes

npm install next-themes

Crear un Proveedor de Tema

Crea src/components/theme-provider.tsx:

"use client"

import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

Actualizar Tu Layout Raíz

Modifica src/app/layout.tsx:

import { ThemeProvider } from "@/components/theme-provider"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

Crear un Componente de Alternancia de Tema

"use client"

import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"

export function ThemeToggle() {
  const { setTheme, theme } = useTheme()

  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
    >
      <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">Alternar tema</span>
    </Button>
  )
}

Trabajando con los Cambios de React 19

React 19 introduce varios cambios que afectan cómo trabajas con componentes de Shadcn:

Manejo Simplificado de ref

React 19 permite pasar ref como una prop regular, eliminando la necesidad de forwardRef en muchos casos:

// Enfoque anterior (React 18)
const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ className, ...props }, ref) => {
    return <input ref={ref} className={className} {...props} />
  }
)

// Nuevo enfoque (React 19)
function Input({ className, ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
  return <input ref={ref} className={className} {...props} />
}

Nuevos Hooks de Formulario

Los hooks useActionState y useFormStatus de React 19 funcionan perfectamente con componentes de formulario de Shadcn:

import { useActionState } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"

function ContactForm() {
  const [state, formAction] = useActionState(async (prevState, formData) => {
    // Lógica de server action aquí
    const email = formData.get("email")
    // Procesar formulario...
    return { success: true }
  }, { success: false })

  return (
    <form action={formAction}>
      <Input name="email" type="email" required />
      <Button type="submit">Enviar</Button>
      {state.success && <p>¡Gracias por suscribirte!</p>}
    </form>
  )
}

Problemas Comunes y Soluciones

Errores de Compilación con Tailwind CSS

Si encuentras errores de compilación después de la configuración, asegúrate de que tu configuración de PostCSS sea correcta:

// postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

Estilos de Componente No Aplicados

Verifica que tu globals.css esté importado en tu layout raíz:

import "./globals.css"

Errores de TypeScript con Props de Componente

Asegúrate de que tu tsconfig.json incluya las rutas correctas:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Mejores Prácticas para Producción

  1. Personaliza la utilidad cn: La función cn en lib/utils.ts combina clases inteligentemente. Extiéndela para tus necesidades específicas.

  2. Crea variantes de componentes: Usa CVA (class-variance-authority) para crear variantes de componentes consistentes en toda tu aplicación.

  3. Optimiza el tamaño del bundle: Solo instala componentes que realmente uses. Cada componente es independiente.

  4. Prueba la accesibilidad: Los componentes de Shadcn usan primitivos de Radix UI que manejan la accesibilidad, pero siempre prueba con lectores de pantalla.

  5. Control de versiones: Como los componentes se copian a tu proyecto, confírmalos al control de versiones y rastrea los cambios.

Conclusión

Configurar Shadcn UI con Next.js te da un sistema de componentes moderno que equilibra flexibilidad con velocidad de desarrollo. A diferencia de las bibliotecas de componentes tradicionales, posees cada línea de código, puedes personalizar cualquier cosa y no estás bloqueado en las decisiones de diseño de otra persona.

La combinación del App Router de Next.js 15, las mejoras de React 19 y Tailwind CSS crea una base poderosa para construir aplicaciones web modernas. Con el enfoque de Shadcn de copiar componentes en lugar de instalarlos como dependencias, obtienes lo mejor de ambos mundos: desarrollo rápido y control completo.

Preguntas Frecuentes

Al usar npm con React 19, necesitarás usar la bandera --legacy-peer-deps durante la instalación de componentes debido a conflictos de dependencias peer. Los gestores de paquetes como pnpm, yarn y bun manejan estas dependencias más elegantemente y no requieren banderas especiales. El resultado final es el mismo independientemente del gestor de paquetes que elijas.

Sí, Shadcn UI funciona tanto con App Router como con Pages Router. El código del componente en sí es agnóstico al framework. La principal diferencia está en cómo implementas proveedores como el proveedor de tema - en Pages Router, envolverías tu app en _app.tsx en lugar del layout raíz.

Como Shadcn copia componentes directamente a tu proyecto, puedes modificarlos como cualquier otro código. Abre el archivo del componente en src/components/ui/, ajusta las clases de Tailwind o variantes CVA, y guarda. También puedes modificar las variables de tema globales en tu CSS para cambiar colores y espaciado en todos los componentes a la vez.

No, solo debes instalar componentes según los necesites. Cada componente es independiente e incluye sus propias dependencias. Esto mantiene el tamaño de tu bundle mínimo y tu proyecto organizado. Usa npx shadcn@latest add [nombre-componente] para agregar componentes individuales cuando los necesites.

Como los componentes se copian a tu proyecto, no se actualizarán automáticamente cuando Shadcn lance nuevas versiones. Puedes verificar manualmente la documentación de Shadcn UI para actualizaciones y ya sea volver a ejecutar el comando add para componentes específicos o aplicar cambios manualmente. Esto te da control sobre cuándo y cómo se actualizan los componentes en tu proyecto.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers