Una línea de base de React lista para producción: qué hace realmente cada paquete
Configura Vite, Tailwind v4, Redux Toolkit, React Router, Jest y Prettier para una aplicación React, y comprende por qué cada paquete y línea de configuración está presente.
Ejecutar npm create vite te proporciona una aplicación React que se muestra, pero no es la que podrías entregar a usuarios reales: no cuenta con sistema de estilos, estado compartido, enrutamiento, pruebas ni un formato de código acordado. Esta guía construye paso a paso esa base que falta utilizando Tailwind CSS, Redux Toolkit, React Router, Jest con React Testing Library y Prettier. Para cada paquete, responde a dos preguntas: ¿qué hace realmente? y, ¿qué fallará si lo omitimos? Al final tendrás una base funcional sobre la cual desarrollar nuevas funciones y, lo que es igualmente importante, podrás leer tu propio package.json y explicar cada línea.
Un vistazo a la tecnología utilizada:
- Tailwind CSS para el estilo
- Redux Toolkit para los datos compartidos de la aplicación
- React Router para la navegación entre páginas
Algunos de estos se instalan en una sola línea. Otros ocultan detalles sorprendentes; por ejemplo, “React Testing Library” en realidad son tres paquetes con funciones distintas.
Comience con un proyecto Vite nuevo utilizando la plantilla React + TypeScript:
npm create vite@latest react-production-stack -- --template react-ts
cd react-production-stack
npm install
Tailwind CSS: el estilo se define primero
Paquetes: tailwindcss, @tailwindcss/vite
Dado que el estilo afecta a cada componente, tiene sentido verificar que funcione antes de agregar cualquier otra cosa.
npm install tailwindcss @tailwindcss/vite
Esto instala ambos paquetes como dependencias normales en lugar de devDependencies. Estrictamente hablando, ninguno de los paquetes se ejecuta en el navegador: el plugin de Vite realiza su trabajo en el momento de la compilación y solo el CSS generado termina en el paquete de producción. Por eso, muchos equipos los incluyen como devDependencies, y para una aplicación de página única empaquetada, cualquiera de las opciones genera el mismo resultado. Elige una convención y sé consistente con ella.
A continuación, registra el plugin junto con el plugin de React en la configuración de Vite:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
})
Luego reemplaza el contenido de src/index.css con una sola importación. Este es todo el contenido del archivo:
/* Tailwind v4 is CSS-first. No config file, no content globs. */
@import 'tailwindcss';
Eso realmente es todo lo necesario para la configuración. Tailwind v4 sigue un enfoque basado en CSS: no existe un archivo tailwind.config.js ni una lista de globos de contenido, ya que escanea automáticamente los archivos fuente en busca de nombres de clases.
Verificación del funcionamiento
Agregue temporalmente algunas clases utilitarias a un encabezado en App.tsx, por ejemplo text-3xl font-bold text-blue-600, ejecute npm run dev y verifique que el encabezado cambie. Si lo hace, significa que el plugin y la importación de CSS están conectados.
¿Por qué utilidades en lugar de hojas de estilo separadas?
Tailwind mantiene los estilos directamente en el marcado que afectan. Con un archivo CSS separado, es fácil editar un componente y olvidarse de su hoja de estilo, lo que hace que se acumulen gradualmente reglas obsoletas. En particular, los paneles de control repiten una y otra vez los mismos componentes básicos (tarjetas, insignias, botones), y componerlos a partir de un conjunto compartido de utilidades mantiene su consistencia visual con menos código por mantener. El sacrificio es un cambio de hábito: en lugar de inventar nombres de clases como .card-header-active, se compone cada elemento a partir de clases pequeñas y predefinidas.
Redux Toolkit: un almacén y un puente hacia React
Paquetes: @reduxjs/toolkit, react-redux
Estos dos paquetes son fáciles de confundir, pero realizan funciones diferentes:
@reduxjs/toolkites el almacén en sí: alberga los datos de la aplicación y aplica las actualizaciones a ellos.react-reduxes la conexión con React: proporciona<Provider>y los hooks que utilizan los componentes para leer y actualizar esos datos.
Necesitas ambos, ya que ninguno puede realizar el trabajo del otro.
npm install @reduxjs/toolkit react-redux
Crea el almacén en src/app/store.ts. Comienza con un mapa de reducidores vacío y exporta dos tipos derivados del almacén para que el resto de la aplicación no tenga que escribirlos a mano:
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: {},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
Por ahora, el objeto reducer: {} permanece vacío. Las secciones se añadirán cuando se necesiten funcionalidades reales, como proyectos o tareas; no tiene sentido crear estado antes de que cualquier pantalla lo utilice.
Luego se definen ganchos tipados en src/app/hooks.ts. Las herramientas withTypes, disponibles en las versiones recientes de React Redux, vinculan useDispatch y useSelector a los tipos de tu almacén una sola vez, de modo que los componentes obtienen inferencia de tipos completa sin necesidad de anotar cada llamada:
import { useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from './store'
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
Proporcionar el almacén al árbol de componentes
En este punto el almacén ya existe, pero React no tiene conocimiento de él. <Provider> lo hace disponible para cada componente que se encuentre debajo de él, por lo que debe colocarse en la parte superior del árbol en src/main.tsx:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import { store } from './app/store'
import App from './App'
import './index.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
)
Cualquier elemento renderizado dentro de <Provider> ahora puede llamar a useAppSelector y useAppDispatch.
Verificación de funcionamiento
Inicie la aplicación y confirme que la página sigue renderizándose sin el error "no se pudo encontrar el contexto react-redux". Ese error aparece siempre que un componente utiliza los hooks de Redux fuera de un Provider. Con una tienda vacía aún no hay nada más que probar.
Cuándo usar Redux y cuándo basta con useState
No todo pertenece a Redux, y almacenar todo el estado en la tienda es tan erróneo como mantenerlo todo localmente. Una regla práctica:
useStatepara datos relevantes para una sola pantalla o componente: si un modal está abierto, el valor actual de un campo de entrada, la opción seleccionada en un menú desplegable.
Si algún estado de otro modo tendría que transmitirse a través de varias capas o duplicarse entre pantallas, eso es una buena señal de que pertenece al almacén de estado.
React Router: enrutamiento antes de la primera página real
Paquete: react-router
Agregar enrutamiento antes de que exista cualquier página real puede parecer prematuro, pero da buenos resultados rápidamente: cada nueva pantalla se convierte en un <Route> adicional, en lugar de requerir una modificación posterior que obligue a reestructurar la aplicación.
npm install react-router
Coloque la tabla de rutas en su propio módulo, src/routes/AppRoutes.tsx. Por ahora, asigna / a un componente de marcador temporal estilizado con las herramientas de Tailwind:
import { Route, Routes } from 'react-router'
function Placeholder() {
return (
<div className="flex min-h-screen items-center justify-center">
<p className="text-slate-600">Routes coming soon</p>
</div>
)
}
export function AppRoutes() {
return (
<Routes>
<Route path="/" element={<Placeholder />} />
</Routes>
)
}
src/App.tsx simplemente renderiza esa tabla de rutas:
import { AppRoutes } from './routes/AppRoutes'
function App() {
return <AppRoutes />
}
export default App
Finalmente, envuelva la aplicación en <BrowserRouter> dentro de src/main.tsx, junto al proveedor de Redux:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router'
import { store } from './app/store'
import App from './App'
import './index.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</StrictMode>,
)
La cadena resultante es main.tsx → <App /> → <AppRoutes /> → el <Route> que coincida con la URL. Redux y el router son independientes, por lo que su orden de anidamiento no importa; el único requisito es que ambos envuelvan a <App>.
Verificación de funcionamiento
Ejecuta npm run dev y abre /. Si aparece el texto de marcador, <BrowserRouter>, <Routes> y <Route> están conectados correctamente.
Jest y React Testing Library: cuatro tareas, once paquetes
Paquetes: jest, @testing-library/react, babel-jest y varios más
Este es el paso que lleva más tiempo. Los conceptos no son difíciles, pero “añadir pruebas” implica instalar unos once paquetes que cubren cuatro responsabilidades distintas, y luego hacer que pase una sola prueba con el router activo. Agrupar los paquetes por tarea hace que todo sea mucho más fácil de seguir.
Grupo A: el ejecutor de pruebas y un navegador simulado
npm install -D jest jest-environment-jsdom
- jest es el ejecutor. Descubre los archivos
*.test.tsx, los ejecuta y reporta las pruebas exitosas y fallidas. Nada más en esta sección funciona sin él. - jest-environment-jsdom es necesario porque Jest se ejecuta en Node, donde no existe
document. Proporciona un DOM simulado para que los componentes tengan un lugar donde renderizarse.
Grupo B: React Testing Library está formado por tres paquetes
npm install -D @testing-library/react
npm install -D @testing-library/jest-dom
npm install -D @testing-library/user-event
Lo que la gente llama “React Testing Library” en realidad son tres bibliotecas, cada una con su propia función:
- @testing-library/react renderiza un componente en la página simulada y proporciona consultas como
screen.getByText(...).
toBeInTheDocument(), por lo que no es necesario comparar manualmente los resultados de las consultas con null.En resumen: renderizar, verificar y interactuar. Tres tareas, tres paquetes, y casi siempre se desea tenerlos todos.
Grupo C: la cadena de herramientas Babel que permite a Jest leer TSX
Este grupo existe por una sola razón: Jest no puede comprender por sí mismo los archivos de TypeScript o JSX.
- babel-jest conecta los dos. Jest pasa cada archivo por Babel antes de ejecutarlo.
- @babel/preset-typescript elimina las anotaciones de tipo. No realiza ninguna verificación de tipos; simplemente quita
: stringy sintaxis similar. - @babel/preset-react compila JSX en llamadas a funciones comunes.
- @babel/preset-env transforma la sintaxis moderna en lo que admite tu versión de Node.
A diferencia del Grupo B, instala estos elementos juntos con una sola orden. Todos los presets requieren un @babel/core compatible, y instalarlos por separado en un proyecto que ya contiene Jest (que incluye sus propias dependencias de Babel) puede hacer que npm intente armonizar versiones incompatibles. Un síntoma común es el error ERESOLVE unable to resolve dependency tree al instalar un solo paquete posteriormente. Instalar todo el grupo de una vez permite a npm resolver un conjunto coherente.
Otra trampa, más sutil, proviene de copiar comandos largos de PDF o páginas web. El texto con formato suelto puede convertirse en saltos de línea reales al pegarse, por lo que un nombre de paquete como @babel/preset-typescript se divide en dos, y la shell ejecuta la parte final como un comando separado e sin sentido. Las continuaciones de línea explícitas colocan los saltos exactamente donde se desea. A continuación se muestra la sintaxis de la Síntesis de Comandos de Windows:
npm install -D babel-jest ^
@babel/core ^
@babel/preset-env ^
@babel/preset-react ^
@babel/preset-typescript
El ^ al final indica a cmd.exe que el comando continúa en la línea siguiente. En PowerShell, el carácter de continuación es un backtick, y en bash o zsh es una barra invertida. Sea cual sea la shell, sigue siendo exactamente un npm install.
Grupo D: tipos solo para tu editor
npm install -D @types/jest
Este paquete no afecta de ninguna manera a la ejecución de las pruebas; para entonces Babel ya ha eliminado todos los tipos. Su existencia sirve para que TypeScript y tu editor reconozcan funciones globales como test(...) y expect(...) en lugar de marcarlas como errores.
Agregar los scripts de prueba
Instalar Jest no te proporciona la orden npm test, por lo que debes agregar los scripts tú mismo en package.json:
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"test": "jest",
"test:watch": "jest --watch"
}
npm test ejecuta toda la suite una sola vez. npm run test:watch permanece en ejecución y vuelve a ejecutar solo las pruebas afectadas por el archivo que acabas de guardar; mantén esta terminal abierta mientras trabajas.
Hay dos comandos más que vale la pena recordar para cuando surjan problemas:
npx jest src/App.test.tsx # run one file only
npx jest --clearCache # when Jest keeps showing an error
# you already fixed
La orden de caché es más importante de lo que parece. Jest almacena en caché los archivos transformados, por lo que después de modificar babel.config.cjs o jest.config.cjs, puede seguir sirviendo la salida antigua y reportar un error que ya se ha solucionado. Cuando una corrección parezca no funcionar, borre la caché antes de concluir que la solución es incorrecta.
La configuración completa de pruebas
A continuación se muestra cada archivo de configuración en su totalidad, con una explicación de qué es lo que se encarga cada parte.
babel.config.cjs
Los valores predefinidos imitan al Grupo C: apuntan a la versión actual de Node, utilizan el entorno de ejecución JSX automático para que los archivos no necesiten importar React, y eliminan TypeScript. El plugin integrado maneja algo que Jest no puede hacer: import.meta, que el código de Vite utiliza para cosas como import.meta.env y el reemplazo en caliente de módulos, pero que no es válido en la salida CommonJS que ejecuta Jest aquí.
function stripImportMeta() {
return {
visitor: {
MetaProperty(path) {
path.replaceWithSourceString('({ url: "", hot: undefined })')
},
},
}
}
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript',
],
plugins: [stripImportMeta],
}
Se trata de un verdadero complemento de Babel, escrito como una función inline en lugar de un paquete instalado; Babel acepta ambas formas. MetaProperty es el tipo de nodo AST que utiliza Babel para import.meta, y el visitador reemplaza cada aparición por un objeto simple que cuenta con un url vacío y un hot indefinido. Tenga en cuenta que esto también oculta cualquier valor de import.meta.env del código sometido a pruebas, por lo que un componente que lea variables de entorno necesitará simularlas por separado.
jest.config.cjs
Este archivo conecta el ejecutor con todo lo demás. Selecciona el entorno jsdom, carga un archivo de configuración una vez que el entorno está listo, envía cada archivo de JavaScript y TypeScript a través de babel-jest, y mapea las importaciones de estilos e imágenes a módulos ficticios.
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'json'],
transform: {
'^.+\\.(ts|tsx|js|jsx|mjs)