Next.js + Tailwind CSS: mejores prácticas — guía completa de configuración a modo oscuro (edición 2025)

Mira el className de ese botón en VS Code: veintitrés clases. De bg-blue-500 a dark:hover:bg-blue-800, apretujadas en una sola línea, con la barra de desplazamiento horizontal estirada al máximo. Un compañero pasa por tu puesto, echa un vistazo a la pantalla y dice: «¿Qué es esto?»
En ese momento no sabía qué responder. Llevo casi dos años con Tailwind; va rápido, sí, pero el código se parece cada vez más a un jeroglífico. Copiar y pegar es un placer momentáneo; mantenerlo, un infierno. Si quieres unificar el radio de todos los botones, tienes que buscar rounded-lg archivo por archivo y cambiarlo uno a uno.
No eres el único. En 2025, Tailwind CSS ha pasado a v4, Next.js está en la 15, y la configuración, el modo oscuro y la optimización de rendimiento han cambiado por completo. Al principio yo también estaba perdido: ¿dónde está el archivo de configuración? ¿Adónde se fue darkMode: 'class'? Después de tropezar con un montón de obstáculos, por fin entendí cómo funciona.
En este artículo comparto lo que he aprendido en la práctica. No es un «repetidor de la documentación oficial», sino métodos probados en proyectos reales: cómo evitar que las clases exploten, cómo implementar el modo oscuro con elegancia y cómo bajar el CSS de 500 KB a 50 KB. Si Tailwind te ha torturado con sus clases o estás dudando si actualizar a v4, sigue leyendo.
Novedades de 2025: Tailwind CSS v4 + Next.js 15
Empecemos por el cambio más grande de v4: el archivo de configuración ha desaparecido.
Sí, ese tailwind.config.js que conocíamos se ha vuelto opcional en v4. La primera vez que lo leí pensé que era una broma de internet. Abrí un proyecto nuevo con Next.js 15.3 y, efectivamente, no estaba. El equipo de Tailwind lo llama «filosofía zero-config»: escaneo automático de archivos del proyecto, listo para usar.
Eso no significa que no puedas personalizar nada. Al contrario: v4 traslada la personalización a un lugar más intuitivo — global.css. Ahora tus colores de tema, espaciados y fuentes se definen con variables en el archivo CSS:
@theme {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--font-sans: 'Inter', sans-serif;
}
Al principio me costó acostumbrarme; pensé «¿no es un paso atrás?». Tras dos días vi que, en realidad, es más rápido de modificar. Antes, para cambiar un color de tema había que reiniciar el servidor de desarrollo y esperar la compilación; ahora cambias una variable CSS y el hot reload responde al instante. Además, los diseñadores entienden las variables CSS sin preguntarme «¿qué azul es exactamente blue-500?».
Otra mejora tangible es la velocidad. v4 reescribió el motor en Rust; la documentación dice que es 5 veces más rápido. Lo probé: el arranque en frío pasó de 8 segundos a menos de 2. No es un benchmark artificial — si arrancas el servidor de desarrollo una docena de veces al día, ese tiempo acumulado da para dos cafés más.
En Next.js 15, App Router ya es la configuración estándar. Combinado con Tailwind, el aislamiento de estilos en componentes de servidor funciona bien; no hay que preocuparse por la contaminación de estilos. Solo recuerda añadir 'use client' en componentes de cliente; si no, el cambio de modo oscuro dará problemas (lo veremos más adelante).
Un detalle que muchos pasan por alto: en v4, el border-color por defecto es currentColor. Eso significa que los bordes sin color explícito siguen el color del texto. Suena trivial, pero si migras directamente desde v3 puedes ver «desaparecer» montones de bordes — en realidad adoptan el color del texto. Me pilló; tardé un rato en darme cuenta.
En resumen, v4 cambia mucho, pero va en la dirección correcta: más rápido, más simple y más intuitivo. Tras la curva de adaptación inicial, no querrás volver atrás.
Cómo resolver las clases demasiado largas: la forma correcta de encapsular componentes
Volvamos al botón del principio: ¿qué hacemos con más de veinte clases?
Mucha gente piensa primero en @apply. Metes las clases de Tailwind en un archivo CSS, le pones un nombre como .btn-primary y parece más limpio. Yo también lo hice hasta que vi a Adam Wathan, autor de Tailwind, decir en Twitter: «Si usas @apply a menudo, quizá no entiendes la filosofía de Tailwind.»
Suena duro, pero tiene sentido. @apply compila las utilidades de antemano en el CSS; pierdes la generación bajo demanda de Tailwind. Crees que «encapsulas», pero en realidad inflas manualmente el CSS — en un proyecto mío, abusar de @apply hizo que el CSS de producción pasara de 30 KB a 120 KB.
¿Cuál es la forma correcta? Encapsulación de componentes.
Encapsula combinaciones habituales en componentes React; las clases siguen siendo las mismas, pero solo las escribes una vez:
// ❌ Antes: repetir en cada sitio
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg shadow-md transition duration-200">
Enviar
</button>
// ✅ Ahora: encapsular en un componente
<Button variant="primary">Enviar</Button>
Dentro del componente Button las clases siguen ahí, pero el resto del código queda limpio. Y si quieres ajustar el estilo de todos los botones, cambias un solo archivo.
Pero eso no basta. Los botones tienen distintos estados: primario, secundario, peligro… ¿Crear un componente para cada uno? Ahí entra cva (class-variance-authority).
Esta librería gestiona variantes de componentes y encaja muy bien con Tailwind:
import { cva, type VariantProps } from 'class-variance-authority'
const buttonStyles = cva(
// Estilos base
'font-bold rounded-lg transition duration-200',
{
variants: {
variant: {
primary: 'bg-blue-500 hover:bg-blue-700 text-white',
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-800',
danger: 'bg-red-500 hover:bg-red-700 text-white'
},
size: {
sm: 'py-1 px-3 text-sm',
md: 'py-2 px-4',
lg: 'py-3 px-6 text-lg'
}
},
defaultVariants: {
variant: 'primary',
size: 'md'
}
}
)
export function Button({
variant,
size,
children,
...props
}: VariantProps<typeof buttonStyles> & React.ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button className={buttonStyles({ variant, size })} {...props}>
{children}
</button>
)
}
Ahora se usa mucho mejor:
<Button variant="primary">Guardar</Button>
<Button variant="danger" size="lg">Eliminar</Button>
<Button variant="secondary" size="sm">Cancelar</Button>
TypeScript comprueba los nombres de variante; si te equivocas, error en compilación. shadcn/ui funciona así, por eso su código se lee tan bien.
Dicho esto, @apply no está totalmente prohibido. Al sobrescribir estilos de librerías de terceros a veces no puedes encapsular un componente; ahí un @apply puntual no pasa nada. Para tus propios componentes, encapsula siempre que puedas; no te saltes el paso por pereza.
Configuración de temas personalizados: construye tu sistema de diseño
Tras encapsular componentes, la siguiente pregunta: ¿cómo mantener un estilo coherente en todo el proyecto?
En proyectos anteriores usaba cinco o seis azules distintos: blue-400, blue-500, #3B82F6, rgb(59, 130, 246)… El diseñador negaba con la cabeza: «¿Cuál es la guía de estilo?» Con el tiempo entendí que hace falta un sistema de diseño que fije colores, tipografías y espaciados.
En v4 esto es especialmente cómodo. ¿Recuerdas el @theme de antes? Ahí se define todo:
/* app/globals.css */
@import 'tailwindcss';
@theme {
/* Colores de marca */
--color-brand-primary: #3b82f6;
--color-brand-secondary: #8b5cf6;
/* Colores semánticos */
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
/* Neutros (de claro a oscuro) */
--color-neutral-50: #f9fafb;
--color-neutral-100: #f3f4f6;
--color-neutral-500: #6b7280;
--color-neutral-900: #111827;
/* Familias tipográficas */
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'Fira Code', monospace;
/* Espaciado (cuadrícula de 8 px en el diseño) */
--spacing-unit: 0.5rem; /* 8px */
/* Radios de borde */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 1rem;
}
Después úsalos directamente en las clases de Tailwind:
<div className="bg-brand-primary text-neutral-50 rounded-md">
Fondo con color de marca
</div>
Fíjate: no uso bg-blue-500, sino bg-brand-primary. Cambiar el color de marca es modificar una variable y afecta a todo el sitio; no hace falta buscar blue-500 cien veces con grep.
Si prefieres el enfoque de v3 con archivo de configuración, puedes crear tailwind.config.ts:
import type { Config } from 'tailwindcss'
export default {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
// Extender el tema por defecto (recomendado)
colors: {
brand: {
primary: '#3b82f6',
secondary: '#8b5cf6',
},
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
},
},
} satisfies Config
extend es clave. Si escribes theme.colors directamente, sobrescribes todos los colores por defecto de Tailwind y pierdes bg-red-500 y similares. extend añade, no reemplaza.
Otro truco: combinar variables CSS con la config de Tailwind permite cambiar el tema en tiempo de ejecución:
:root {
--color-primary: #3b82f6;
}
[data-theme='purple'] {
--color-primary: #8b5cf6;
}
// tailwind.config.ts
colors: {
primary: 'var(--color-primary)',
}
Al cambiar de tema no recompilas CSS; basta con modificar un atributo del DOM. Muy útil en productos SaaS donde el usuario elige su color.
Con el sistema de diseño montado, el trabajo en equipo fluye mejor. Un recién llegado abre globals.css y sabe qué colores usar. Se acabó el «este azul lo elegí al tuntún».
Modo oscuro: la mejor solución sin parpadeos
En modo oscuro cometí mi error más grave.
La primera vez seguí un tutorial de v3, puse un botón de cambio y, al pulsar, la página parpadeaba en blanco antes de oscurecerse. Los usuarios se quejaron de que «los cegaba». Eso se llama flash of unstyled content (FOUC), culpa del renderizado en servidor de Next.js.
En v4 la configuración del modo oscuro cambió. ¿Recuerdas darkMode: 'class' de v3? Ya no hace falta; la estrategia por clase es la predeterminada. Pero el parpadeo sigue ahí; hay que resolverlo con la librería next-themes.
Instálala:
npm install next-themes
Luego envuelve el layout raíz con ThemeProvider:
// app/layout.tsx
import { ThemeProvider } from 'next-themes'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="es" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
)
}
suppressHydrationWarning es imprescindible. next-themes añade class="dark" al <html> en el cliente, distinto del HTML del servidor; React avisa, y este atributo silencia la advertencia.
El botón de cambio va en un componente de cliente:
'use client'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'
export function ThemeToggle() {
const [mounted, setMounted] = useState(false)
const { theme, setTheme } = useTheme()
useEffect(() => setMounted(true), [])
if (!mounted) return null // Evitar desajuste SSR
return (
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
className="rounded-lg p-2 hover:bg-neutral-100 dark:hover:bg-neutral-800"
>
{theme === 'dark' ? '🌞' : '🌙'}
</button>
)
}
Las variables CSS también deben adaptarse al modo oscuro:
@theme {
--color-bg-primary: #ffffff;
--color-text-primary: #111827;
}
.dark {
--color-bg-primary: #111827;
--color-text-primary: #f9fafb;
}
O usa directamente el prefijo dark: en las clases de Tailwind:
<div className="bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-50">
Modo oscuro adaptativo
</div>
Sobre el diseño de color: el modo oscuro no es invertir blanco y negro. El negro puro (#000000) cansa la vista; mejor un gris oscuro (#111827 o #1a1a1a). El texto blanco puro también deslumbra; #f9fafb es más cómodo. Otro detalle: las sombras en modo oscuro deben invertirse o no se percibe profundidad:
// Modo claro: sombra hacia abajo
<div className="shadow-lg dark:shadow-none dark:ring-1 dark:ring-neutral-800">
En modo oscuro, ring (borde) suele funcionar mejor que la sombra.
Las imágenes también pueden quedar demasiado brillantes en modo oscuro. Un filtro ayuda:
.dark img {
filter: brightness(0.9);
}
Con estos detalles, el modo oscuro deja de ser un checkbox y pasa a ser usable de verdad.
Optimización de rendimiento: CSS pequeño y rápido
Lo de bajar el CSS de 500 KB a 50 KB no es exageración; lo hice en un proyecto real.
El modo JIT de v4 viene activado por defecto y genera estilos bajo demanda, lo cual ya es rápido. Pero aún hay margen; la clave está en la configuración de content.
Muchos escriben algo así:
// ❌ Alcance de escaneo demasiado amplio
content: [
'./**/*.{js,ts,jsx,tsx}',
]
Eso escanea todo el proyecto, incluidos node_modules y .next, perdiendo tiempo. Sé más preciso:
// ✅ Solo los directorios necesarios
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./lib/**/*.{js,ts}',
]
Lo probé: tras el cambio, el arranque del servidor de desarrollo fue un 40 % más rápido.
Otro problema habitual: clases dinámicas. Por ejemplo:
// ❌ Estas clases serán eliminadas por purge
const colors = ['red', 'blue', 'green']
<div className={`bg-${colors[0]}-500`}>
Tailwind no detecta bg-red-500 completo; en producción se elimina y la página queda sin estilo. Escribe la clase completa:
// ✅ Clases completas
const colorMap = {
red: 'bg-red-500',
blue: 'bg-blue-500',
green: 'bg-green-500',
}
<div className={colorMap[color]}>
O usa safelist para forzar su conservación:
// tailwind.config.ts
safelist: [
{
pattern: /bg-(red|blue|green)-500/,
},
]
No abuses de safelist; demasiadas entradas vuelven a inflar el CSS. Prioriza clases completas en código; safelist solo si no hay alternativa.
v4 comprime el CSS automáticamente en producción. Si quieres ir más allá, configura cssnano:
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
},
}
Un punto que se pasa por alto: monitorizar el tamaño del bundle. Yo uso @next/bundle-analyzer de forma periódica:
npm install --save-dev @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
// Tu otra configuración
})
Luego ejecuta ANALYZE=true npm run build y obtienes un informe visual de qué paquetes pesan demasiado.
El caso de Netflix es instructivo: en su página Top 10 el CSS ocupa solo 6,5 KB. ¿Cómo? Llevando la limpieza al extremo, conservando solo lo necesario. No hace falta llegar tan lejos, pero la idea vale: no metas de todo en el proyecto; basta con lo que uses.
En una revisión de código vi que un compañero importaba todo @heroicons/react y solo usaba dos iconos. Cambiar a importación bajo demanda restó 200 KB al bundle. Detalle pequeño, pero suma.
La optimización de rendimiento no es un truco de una vez; es un hábito. Antes de cada release, ejecuta el análisis del bundle para que el CSS no se descontrole.
Migrar de v3 a v4: guía de actualización gradual
¿Conviene actualizar si aún usas v3? Depende.
Proyectos nuevos: v4 sin dudarlo. Proyectos existentes: evalúa el coste. v4 trae cambios breaking; no basta con npm install.
El cambio más grande es la configuración. Lo que tenías en tailwind.config.js de v3 se traslada a global.css:
/* Antes en tailwind.config.js */
module.exports = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
},
},
},
}
/* Ahora en globals.css */
@theme {
--color-primary: #3b82f6;
}
Las utilidades personalizadas también cambian. Antes @layer utilities, ahora @utility:
/* v3 */
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
/* v4 */
@utility text-balance {
text-wrap: balance;
}
Otra trampa: las clases de componente ya no admiten variantes. En v3 podías hacer:
/* v3 permitido */
@layer components {
.btn {
@apply px-4 py-2 rounded;
}
}
/* y luego hover:btn, dark:btn, etc. */
En v4 hover:btn falla. Convierte a utility o encapsula en un componente React.
Y el tema del color de borde: v4 usa border-color: currentColor por defecto y muchos bordes «desaparecen». Busca border globalmente y añade border-neutral-300 donde falte color:
// v3: el borde era gris automáticamente
<div className="border"></div>
// v4: el borde sigue el color del texto; hay que especificarlo
<div className="border border-neutral-300"></div>
Recomiendo migrar por fases:
- Paso 1: instala v4, arranca en desarrollo y revisa problemas visibles
- Paso 2: busca
@layery conviértelo a@utilityo@theme - Paso 3: busca
bordery añade color donde falte - Paso 4: mueve
themedel config al CSS y prueba por partes - Paso 5: si clases dinámicas desaparecen en purge, usa safelist
El proceso suele llevar medio día o uno, según el tamaño del proyecto. No lo hagas todo de golpe; migra por lotes para poder revertir.
Si usas shadcn/ui u otra librería, comprueba primero la compatibilidad con v4. Yo actualicé antes que la librería y los componentes quedaron hechos un desastre.
v4 es una buena evolución, pero no es obligatorio subir ya. Si v3 te va bien, no hay prisa. La deuda técnica se paga, pero elige el momento.
Conclusión
Del botón con 23 clases al <Button variant="primary"> de hoy — casi dos años de camino.
Tailwind y Next.js juntos son potentes, pero no funcionan solos. Los cambios de v4 asustan al principio, pero van hacia configuración más simple, mejor rendimiento y mejor experiencia de desarrollo.
Las técnicas de este artículo — encapsulación, temas, modo oscuro, rendimiento — no hace falta aplicarlas todas. Elige las que resuelvan tu dolor actual y pruébalas. No intentes refactorizar todo el proyecto de una vez; es agotador y arriesgado.
Mi consejo: empieza por encapsular componentes. Dedica una tarde a Button, Card e Input con cva para variantes. Riesgo bajo, beneficio inmediato. Después piensa en modo oscuro y temas personalizados.
Sobre actualizar a v4, no te apresures. Mira si el ecosistema está maduro, si tus librerías lo soportan y si tienes tiempo. Lo nuevo no siempre es lo mejor; lo adecuado sí.
Si has tenido problemas parecidos con Tailwind o conoces mejores prácticas, comenta. Quizá tu enfoque sea más elegante que el mío.
Los ejemplos de código están en GitHub (enlace al final del artículo); puedes usarlos directamente. Si algo falla, abre un Issue.
No te quedes solo leyendo: pruébalo en código. Sin ejecutarlo, no se aprende de verdad.
Flujo completo de configuración Next.js + Tailwind CSS v4
Pasos completos desde la configuración inicial hasta encapsulación de componentes, optimización de rendimiento y modo oscuro
⏱️ Estimated time: 3 hr
- 1
Step 1: Configuración básica de Tailwind v4
Cambios en v4:
• El archivo de configuración desaparece (filosofía zero-config)
• La personalización pasa a global.css con variables CSS
• Motor reescrito en Rust, 5 veces más rápido
Configurar global.css:
```css
@theme {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--font-sans: 'Inter', sans-serif;
}
```
Ventajas:
• Cambias variables CSS y el hot reload responde al instante
• Los diseñadores también lo entienden
• No hace falta reiniciar el servidor de desarrollo
Punto clave: la filosofía de v4 es zero-config; escaneo automático de archivos, listo para usar. - 2
Step 2: Resolver clases demasiado largas
Problema: clases enormes, 23 en una sola línea.
Solución: encapsulación de componentes
Usar cva para variantes:
```tsx
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground',
destructive: 'bg-destructive text-destructive-foreground',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 px-3',
lg: 'h-11 px-8',
},
},
}
)
export function Button({ variant, size, className, ...props }) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
)
}
```
Resultado: de 23 clases a 3 (variant, size, className)
Punto clave: dedica una tarde a Button, Card e Input con cva para variantes. - 3
Step 3: Optimización de rendimiento (500 KB → 50 KB)
Métodos de optimización:
1. Configuración de purge:
```js
// tailwind.config.js (v3)
module.exports = {
content: ['./app/**/*.{js,ts,jsx,tsx}'],
// Solo las clases realmente usadas
}
```
2. Importación bajo demanda:
```tsx
// No importes la librería entera
import { Button } from '@/components/ui/button'
```
3. Evitar clases dinámicas:
```tsx
// ❌ Incorrecto: las clases dinámicas no pasan purge
const color = `bg-${theme}-500`
// ✅ Correcto: clases completas
const color = theme === 'blue' ? 'bg-blue-500' : 'bg-red-500'
```
4. Modo JIT (v3):
```js
module.exports = {
mode: 'jit', // Generación bajo demanda
}
```
Resultado: de 500 KB a 50 KB (reducción del 90 %) - 4
Step 4: Configuración del modo oscuro
v4 ya no necesita darkMode; usa next-themes:
Instalar next-themes:
```bash
npm install next-themes
```
Configurar ThemeProvider:
```tsx
'use client'
import { ThemeProvider } from 'next-themes'
export function Providers({ children }) {
return (
<ThemeProvider attribute="class" defaultTheme="system">
{children}
</ThemeProvider>
)
}
```
Usar el prefijo dark::
```tsx
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
Contenido
</div>
```
Puntos clave:
• v4 soporta dark: automáticamente
• next-themes gestiona el tema
• No hace falta configurar darkMode
FAQ
¿Qué cambia en Tailwind v4?
1. El archivo de configuración desaparece (filosofía zero-config)
• tailwind.config.js pasa a ser opcional
• Escaneo automático de archivos del proyecto
2. La personalización pasa a global.css
• Variables CSS para colores, espaciados y fuentes
• Cambias variables y el hot reload responde al instante
3. Motor reescrito en Rust
• 5 veces más rápido
• Arranque en frío de 8 s a menos de 2 s
4. Modo oscuro simplificado
• Ya no hace falta configurar darkMode
• Soporte automático del prefijo dark:
Ventajas:
• Configuración más simple
• Mayor velocidad
• Hot reload más rápido
Nota: v4 aún estaba en beta cuando se escribió este artículo; en producción conviene esperar a una versión estable.
¿Cómo resolver las clases demasiado largas?
Solución: encapsulación de componentes
Usar cva para variantes:
```tsx
import { cva } from 'class-variance-authority'
const buttonVariants = cva(
'inline-flex items-center justify-center',
{
variants: {
variant: {
default: 'bg-primary',
destructive: 'bg-destructive',
},
size: {
default: 'h-10 px-4',
sm: 'h-9 px-3',
},
},
}
)
export function Button({ variant, size, className, ...props }) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
)
}
```
Resultado:
• De 23 clases a 3
• Variantes centralizadas
• Más fácil de mantener
Consejo: dedica una tarde a Button, Card e Input con cva para variantes.
¿Cómo optimizar el rendimiento de Tailwind (500 KB → 50 KB)?
1. Configuración de purge:
```js
module.exports = {
content: ['./app/**/*.{js,ts,jsx,tsx}'],
}
```
2. Importación bajo demanda:
```tsx
import { Button } from '@/components/ui/button'
```
3. Evitar clases dinámicas:
```tsx
// ❌ Incorrecto
const color = `bg-${theme}-500`
// ✅ Correcto
const color = theme === 'blue' ? 'bg-blue-500' : 'bg-red-500'
```
4. Modo JIT (v3):
```js
module.exports = {
mode: 'jit',
}
```
Resultado: de 500 KB a 50 KB (reducción del 90 %)
Punto clave: incluye solo las clases que realmente usas; evita clases dinámicas.
¿Cómo configurar el modo oscuro en Tailwind v4?
Instalación:
```bash
npm install next-themes
```
Configuración:
```tsx
'use client'
import { ThemeProvider } from 'next-themes'
export function Providers({ children }) {
return (
<ThemeProvider attribute="class" defaultTheme="system">
{children}
</ThemeProvider>
)
}
```
Uso:
```tsx
<div className="bg-white dark:bg-gray-900">
Contenido
</div>
```
Puntos clave:
• v4 soporta dark: automáticamente
• next-themes gestiona el tema
• No hace falta configurar darkMode
Nota: añade suppressHydrationWarning en la etiqueta html.
¿Conviene actualizar a Tailwind v4?
Ventajas:
• 5 veces más rápido
• Configuración más simple
• Hot reload más rápido
Desventajas:
• Estaba en beta cuando se escribió este artículo
• El ecosistema puede no estar maduro
• La migración lleva tiempo
Recomendación:
• Proyectos nuevos: puedes probar v4
• Proyectos existentes: espera a una versión estable
• Si dudas: quédate en v3 hasta que v4 se estabilice
Punto clave: lo nuevo no siempre es lo mejor; lo adecuado sí. Revisa si el ecosistema y tus librerías lo soportan y si tienes tiempo para migrar.
¿Cómo gestionar temas en Tailwind?
Definir en global.css:
```css
@theme {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--font-sans: 'Inter', sans-serif;
}
```
Uso:
```tsx
<div className="bg-primary text-primary-foreground">
Contenido
</div>
```
Ventajas:
• Cambias variables CSS y el hot reload responde al instante
• Los diseñadores también lo entienden
• No hace falta reiniciar el servidor de desarrollo
Consejo:
• Empieza por encapsular componentes
• Dedica una tarde a Button, Card e Input
• Usa cva para variantes
• Después piensa en temas personalizados
14 min de lectura · Publicado el: 20 dic 2025 · Actualizado el: 21 ago 2026
Guía completa de Next.js
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Modo oscuro en Next.js: guía completa con next-themes
Del parpadeo al cero flash: implementa modo oscuro en Next.js con next-themes paso a paso. Código completo, cómo funciona por dentro y solución de problemas habituales.
Parte 44 de 51
Siguiente
Next.js App Router + shadcn/ui: guía para mezclar componentes de servidor y cliente
Guía detallada para mezclar correctamente Server Components y Client Components en Next.js App Router: integración con shadcn/ui, diseño del flujo de datos, corrección de errores habituales y optimización de rendimiento
Parte 46 de 51



Comentarios
Inicia sesión con GitHub para dejar un comentario