Une base React prête pour la production : ce que fait réellement chaque package.
Installer Vite, Tailwind v4, Redux Toolkit, React Router, Jest et Prettier pour une application React, ainsi que comprendre pourquoi chaque package et ligne de configuration est présent.
L’exécution de npm create vite vous fournit une application React qui s’affiche, mais ce n’est pas celle que l’on pourrait donner à des utilisateurs réels : il n’y a ni système de mise en forme, ni état partagé, ni routage, ni tests, et aucun format de code standardisé. Ce guide construit progressivement ces éléments manquants à l’aide de Tailwind CSS, Redux Toolkit, React Router, Jest avec React Testing Library et Prettier. Pour chaque package, il répond à deux questions : quel est son fonctionnement réel, et qu’est-ce qui ne fonctionne plus si on l’omet ? À la fin, vous disposerez d’une base fonctionnelle sur laquelle développer des fonctionnalités, et, tout aussi important, vous serez capable de lire votre propre package.json et d’expliquer chaque ligne.
Aperçu de la stack :
- Tailwind CSS pour la mise en forme
- Redux Toolkit pour les données partagées de l’application
- React Router pour la navigation entre pages
React Testing Library est en réalité trois paquets ayant chacun des fonctions distinctes.
Commencez par un projet Vite vierge en utilisant le modèle React + TypeScript :
npm create vite@latest react-production-stack -- --template react-ts
cd react-production-stack
npm install
Tailwind CSS : le stylage est défini en premier
Paquets : tailwindcss, @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite
Cela installe les deux paquets en tant que dépendances régulières plutôt qu’en tant que devDependencies. Stricto sensu, aucun des paquets ne s’exécute dans le navigateur : le plugin Vite effectue son travail au moment de la compilation et seuls les CSS générés finissent dans le bundle de production. De nombreuses équipes les placent donc en tant que devDependencies, et pour une application single-page compilée, les deux choix produisent le même résultat. Choisissez une convention et restez cohérent.
Ensuite, enregistrez le plugin aux côtés du plugin React dans la configuration Vite :
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
})
Puis remplacez le contenu de src/index.css par une seule importation. Voici le contenu intégral du fichier :
/* Tailwind v4 is CSS-first. No config file, no content globs. */
@import 'tailwindcss';
C’est vraiment tout pour la configuration. Tailwind v4 est basé sur le CSS en premier : il n’y a ni tailwind.config.js ni liste de globules de contenu, car il cherche automatiquement les noms de classes dans vos fichiers sources.
Vérifier son fonctionnement
Ajoutez temporairement quelques classes utilitaires à un en-tête dans App.tsx, par exemple text-3xl font-bold text-blue-600, exécutez npm run dev, et vérifiez que l’en-tête a changé. S’il change, le plugin et l’import CSS sont bien connectés.
Pourquoi des classes utilitaires plutôt que des feuilles de style séparées
Tailwind conserve les styles directement sur le markup qu’ils affectent. Avec un fichier CSS distinct, il est facile d’éditer un composant tout en oubliant sa feuille de style, ce qui entraîne progressivement l’accumulation de règles inutilisées ou obsolètes. Les tableaux de bord, en particulier, répètent souvent les mêmes éléments (cartes, badges, boutons) ; en les composant à partir d’un ensemble commun de classes utilitaires, on assure leur cohérence visuelle tout en nécessitant moins de code à maintenir. Le compromis réside dans un changement d’habitude : au lieu d’inventer des noms de classes tels que .card-header-active, on assemble chaque élément à partir de petites classes prédéfinies.
Redux Toolkit : un store et un pont vers React
Packages : @reduxjs/toolkit, react-redux
Ces deux packages sont facilement confondus, mais ils remplissent des fonctions différentes :
@reduxjs/toolkitest le store lui-même : il stocke les données de l’application et y applique des mises à jour.react-reduxest la connexion vers React : il fournit<Provider>ainsi que les hooks que les composants utilisent pour lire et mettre à jour ces données.
Vous avez besoin des deux, car aucun ne peut remplacer le rôle de l’autre.
npm install @reduxjs/toolkit react-redux
Créez le store dans src/app/store.ts. Il commence avec un tableau réducteur vide et exporte deux types dérivés du store afin que le reste de l’application n’ait jamais à les écrire manuellement :
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: {},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
L’objet reducer: {} reste pour l’instant vide. Les slices seront ajoutés une fois que des fonctionnalités réelles, telles que des projets ou des tâches, en auront besoin ; il n’y a aucun intérêt à créer un état avant que n’importe quelle interface ne l’utilise.
Ensuite, définissez des hooks typés dans src/app/hooks.ts. Les outils withTypes, disponibles dans les dernières versions de React Redux, lient useDispatch et useSelector aux types de votre store une seule fois, permettant ainsi aux composants d’obtenir une inférence de type complète sans avoir à annoter chaque appel :
import { useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from './store'
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
Fournir le store à l’arbre des composants
À ce stade, le store existe, mais React n’en a aucune connaissance. <Provider> le rend disponible à tous les composants qui se trouvent en dessous de lui, c’est pourquoi il doit être placé tout en haut de l’arbre dans 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>,
)
Tout ce qui est affiché à l’intérieur de <Provider> peut désormais appeler useAppSelector et useAppDispatch.
Vérifier que cela fonctionne
Démarrez l’application et confirmez que la page s’affiche toujours sans l’erreur "could not find react-redux context". Cette erreur apparaît chaque fois qu’un composant utilise les hooks Redux en dehors d’un Provider. Avec un store vide, il n’y a rien d’autre à tester pour l’instant.
Quand recourir à Redux et quand useState suffit
Tout ne doit pas être stocké dans Redux, et placer tout l’état dans le store est une erreur tout autant que de garder tout en local. Une règle pratique :
useStatepour les données pertinentes pour une seule page ou composant : par exemple, si un modal est ouvert, la valeur actuelle d’un champ de saisie, l’option sélectionnée dans un menu déroulant.
Si un état devrait normalement être transmis à travers plusieurs niveaux ou dupliqué entre les écrans, c’est un bon signe qu’il doit être stocké dans le store.
React Router : routage avant la première page réelle
Paket : react-router
Intégrer le routage avant même l’existence d’une page réelle peut sembler prématuré, mais cela en vaut la peine rapidement : chaque nouvel écran devient une <Route> supplémentaire, plutôt que de devoir restructurer l’application ultérieurement.
npm install react-router
Mettez la table des routes dans son propre module, src/routes/AppRoutes.tsx. Pour l’instant, elle associe / à un composant de remplacement stylisé avec les outils 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 affiche ensuite simplement cette table des routes :
import { AppRoutes } from './routes/AppRoutes'
function App() {
return <AppRoutes />
}
export default App
Finalement, enveloppez l’application dans <BrowserRouter> à l’intérieur de src/main.tsx, à côté du fournisseur 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 chaîne résultante est main.tsx → <App /> → <AppRoutes /> → le composant <Route> correspondant à l’URL. Redux et le routeur sont indépendants, donc l’ordre de leur emboîtement n’a pas d’importance ; la seule exigence est que tous deux enveloppent <App>.
Vérifier son fonctionnement
Exécutez npm run dev et ouvrez /. Si le texte de remplacement apparaît, <BrowserRouter>, <Routes> et <Route> sont tous correctement connectés.
Jest et React Testing Library : quatre tâches, onze paquets
Paquets : jest, @testing-library/react, babel-jest ainsi que plusieurs autres
C’est l’étape qui prend le plus de temps. Les concepts ne sont pas difficiles, mais « ajouter des tests » signifie en réalité installer une dizaine de paquets couvrant quatre responsabilités distinctes, puis faire en sorte qu’un seul test passe avec le routeur en place. Regrouper les paquets par tâche rend tout cela beaucoup plus facile à suivre.
Groupe A : l’exécuteur de tests et un navigateur simulé
npm install -D jest jest-environment-jsdom
- jest est l’exécuteur. Il découvre les fichiers
*.test.tsx, les exécute et indique les tests réussis ou échoués. Rien d’autre dans cette section ne fonctionne sans lui. - jest-environment-jsdom est nécessaire car Jest s’exécute sous Node, où il n’y a pas de
document. Il fournit un DOM simulé afin que les composants aient un endroit où s’afficher.
Groupe B : React Testing Library se compose de trois paquets
npm install -D @testing-library/react
npm install -D @testing-library/jest-dom
npm install -D @testing-library/user-event
Ce que les gens appellent "React Testing Library" est en réalité trois bibliothèques, chacune ayant sa propre fonction :
- @testing-library/react affiche un composant sur la page simulée et fournit des méthodes de recherche telles que
screen.getByText(...).
toBeInTheDocument(), ce qui vous évite de comparer manuellement les résultats des requêtes à null.En bref : afficher, vérifier, interagir. Trois tâches, trois packages, et vous en avez presque toujours besoin tous.
Groupe C : la chaîne d’outils Babel qui permet à Jest de lire le TSX
Ce groupe existe pour une seule raison : Jest ne peut pas comprendre seul les fichiers TypeScript ou JSX.
- babel-jest relie les deux. Jest fait passer chaque fichier par Babel avant de l’exécuter.
- @babel/preset-typescript supprime les annotations de type. Il ne vérifie pas le type de quoi que ce soit ; il se contente d’enlever les syntaxes
: stringet similaires. - @babel/preset-react compile JSX en appels de fonction ordinaires.
- @babel/preset-env transforme la syntaxe moderne en quelque chose que votre version de Node prend en charge.
Contrairement au Groupe B, installez-les tous ensemble en une seule commande. Ces présets exigent tous un @babel/core compatible, et les installer séparément dans un projet qui contient déjà Jest (qui intègre ses propres dépendances Babel) peut amener npm à essayer de concilier des versions incompatibles. Un symptôme courant est une erreur ERESOLVE unable to resolve dependency tree lors de la prochaine installation d’un seul package. Installer l’ensemble du groupe en une seule fois permet à npm de résoudre un ensemble cohérent.
Un deuxième piège, plus subtil, provient de la copie de commandes longues depuis des PDF ou des pages web. Le texte avec saut de ligne automatique peut se transformer en vrais sauts de ligne lorsqu’il est collé, ce qui fait que le nom d’un paquet comme @babel/preset-typescript est divisé en deux, et le shell exécute la seconde partie comme une commande séparée et sans sens. Les continuations de ligne explicites placent les sauts exactement là où vous le souhaitez. Ce qui suit utilise la syntaxe de l’invite de commandes Windows :
npm install -D babel-jest ^
@babel/core ^
@babel/preset-env ^
@babel/preset-react ^
@babel/preset-typescript
Le ^ à la fin indique à cmd.exe que la commande se poursuit sur la ligne suivante. Dans PowerShell, le caractère de continuation est un backtick, tandis que dans bash ou zsh c’est un backslash. Quel que soit le shell, il s’agit toujours d’une seule commande npm install.
Groupe D : types uniquement pour votre éditeur
npm install -D @types/jest
Ce package n’a aucun effet sur le déroulement des tests ; Babel a déjà supprimé tous les types à ce stade. Il existe afin que TypeScript et votre éditeur reconnaissent des variables globales comme test(...) et expect(...) au lieu de les signaler comme des erreurs.
Ajout des scripts de test
L’installation de Jest ne vous fournit pas de commande npm test ; vous devez donc ajouter manuellement les scripts dans package.json:
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"test": "jest",
"test:watch": "jest --watch"
}
npm test exécute l’ensemble des tests une seule fois. npm run test:watch reste en exécution et ne réexécute que les tests affectés par le fichier que vous venez de sauvegarder ; gardez-le ouvert dans un deuxième terminal pendant que vous travaillez.
Deux autres commandes méritent d’être mémorisées en cas de problème :
npx jest src/App.test.tsx # run one file only
npx jest --clearCache # when Jest keeps showing an error
# you already fixed
La commande de cache est plus importante qu’il n’y paraît. Jest met en cache les fichiers transformés, de sorte que, après avoir modifié babel.config.cjs ou jest.config.cjs, il peut continuer à servir les anciennes versions et signaler une erreur que vous avez déjà résolue. Lorsqu’une correction ne semble pas fonctionner, videz le cache avant de conclure que la correction est erronée.
La configuration complète du test
Ci-dessous se trouvent toutes les fiches de configuration dans leur intégralité, accompagnées d’une explication de la fonction de chaque élément.
babel.config.cjs
Les préférences prédéfinies reproduisent celles du Groupe C : elles ciblent la version actuelle de Node, utilisent l’exécutable JSX automatique afin que les fichiers n’aient pas besoin d’importer React, et suppriment TypeScript. Le plugin intégré gère quelque chose que Jest ne peut pas faire : import.meta, que le code Vite utilise pour des éléments tels que import.meta.env et le remplacement en temps réel de modules, mais qui n’est pas valide dans le format CommonJS utilisé par Jest.
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],
}
Ce est un véritable plugin Babel, écrit sous forme de fonction intégrée plutôt que de package installé ; Babel accepte les deux formes. MetaProperty est le type de nœud AST que Babel utilise pour import.meta, et le visiteur remplace chaque occurrence par un objet simple possédant une url vide et une valeur hot non définie. Notez que cela cache également toutes les valeurs de import.meta.env pour le code testé, ce qui signifie qu’un composant lisant des variables d’environnement devra les simuler séparément.
jest.config.cjs
Ce fichier relie le moteur d’exécution à tout le reste. Il sélectionne l’environnement jsdom, charge un fichier de configuration une fois l’environnement prêt, fait passer chaque fichier JavaScript et TypeScript par babel-jest, et mappe les imports de styles et d’images vers des modules de substitution.
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'json'],
transform: {
'^.+\\.(ts|tsx|js|jsx|mjs)