Lecture : 12 minutes · Niveau : intermédiaire · Mise à jour : avril 2026
tsconfig.json est le fichier qui dicte le comportement du compilateur TypeScript. Mal configuré, il laisse passer des erreurs et limite la valeur du langage. Bien configuré, il transforme TypeScript en filet de sécurité solide. Ce guide passe en revue chaque option qui compte vraiment et donne des recommandations applicables tout de suite.
Voir aussi → TypeScript et JavaScript moderne : guide pratique.
Sommaire
- Pourquoi
strict: truen’est qu’un début - Les options ciblées par
strict - Options strictes additionnelles
- Modules : NodeNext, Bundler, ou autre
- Target et lib
- Paths aliases pour imports propres
- Source maps et déclarations
- tsconfig pour différents contextes
- Performance de compilation
- FAQ
1. Pourquoi strict: true n’est qu’un début
strict: true active une famille d’options qui ensemble forcent une bonne discipline de typage. C’est le minimum syndical pour un projet sérieux. Mais TypeScript propose des options encore plus strictes en dehors de cette famille — souvent désactivées par défaut alors qu’elles évitent des classes entières de bugs réels.
L’objectif d’une config stricte n’est pas de torturer les développeurs : c’est d’attraper à la compilation ce qui sinon planterait en production avec un message obscur à 3h du matin. Chaque option supplémentaire activée représente une catégorie de bugs que le compilateur garantit absente du code.
// Le strict minimum à connaître
{
"compilerOptions": {
"strict": true, // active la famille strict
"noUncheckedIndexedAccess": true, // accès tableau/objet → undefined possible
"noImplicitOverride": true, // override explicite obligatoire
"noFallthroughCasesInSwitch": true, // pas de chute de case sans break
"noUnusedLocals": true, // variables locales inutilisées
"noUnusedParameters": true, // paramètres inutilisés
"exactOptionalPropertyTypes": true, // strict sur les optionnels
"noPropertyAccessFromIndexSignature": true
}
}
2. Les options ciblées par strict
strict: true est équivalent à activer toutes ces options ensemble :
strictNullChecks
function trouver(id: number): User | undefined {
return users.find(u => u.id === id);
}
// strictNullChecks: false (mauvais)
const nom = trouver(1).nom; // bug à l'exécution si pas trouvé
// strictNullChecks: true (bon)
const u = trouver(1);
if (u) {
console.log(u.nom);
}
Cette option seule attrape probablement la majorité des bugs null is not an object qu’on rencontre en JavaScript.
noImplicitAny
// noImplicitAny: false (mauvais)
function process(data) { // data est any implicite
return data.foo;
}
// noImplicitAny: true (bon)
function process(data: unknown) {
// forcer le typage explicite
}
Force à typer explicitement les paramètres et retours quand l’inférence ne peut pas le faire.
strictFunctionTypes
Vérifie la covariance des paramètres de fonction avec plus de rigueur. Concept avancé mais utile pour éviter des cas pernicieux.
strictBindCallApply
Vérifie les types lors de l’usage de .bind(), .call(), .apply() sur les fonctions.
strictPropertyInitialization
class Service {
private client: Client; // erreur : pas initialisé
constructor() {
// oubli d'initialiser this.client
}
}
Force à initialiser les propriétés de classe dans le constructeur ou avec une valeur par défaut.
alwaysStrict
Émet "use strict" en tête des fichiers JavaScript produits. Standard moderne.
noImplicitThis
Erreur si this est implicitement de type any.
useUnknownInCatchVariables
try {
// ...
} catch (e) { // e est `unknown` (pas any)
if (e instanceof Error) {
console.log(e.message);
}
}
unknown force à vérifier le type avant utilisation, contrairement à any qui désactive le contrôle.
3. Options strictes additionnelles
Ces options ne sont pas dans strict mais sont fortement recommandées.
noUncheckedIndexedAccess
C’est probablement la plus impactante après strict.
const liste: string[] = ["a", "b", "c"];
// Sans noUncheckedIndexedAccess
const x = liste[10]; // x: string (FAUX)
console.log(x.toUpperCase()); // crash à l'exécution
// Avec noUncheckedIndexedAccess
const x = liste[10]; // x: string | undefined
if (x) {
console.log(x.toUpperCase());
}
TypeScript par défaut ne signale pas qu’un accès à un index hors-limites peut retourner undefined. Cette option corrige ce trou. Coût : il faut vérifier ou non-null-asserter (!) à chaque accès indexé. Bénéfice : zéro Cannot read property of undefined en production.
exactOptionalPropertyTypes
interface Config {
port?: number;
}
const c: Config = { port: undefined }; // erreur avec exactOptionalPropertyTypes
Distingue clairement « propriété absente » et « propriété présente avec valeur undefined ». Évite des bugs subtils.
noUnusedLocals et noUnusedParameters
Erreur sur les variables ou paramètres déclarés mais jamais lus. Force à nettoyer le code mort. Pour les paramètres volontairement ignorés, préfixer par _ les exclut de la vérification.
noImplicitReturns
function classer(x: number): string {
if (x > 0) return "positif";
// erreur : pas de return dans toutes les branches
}
noFallthroughCasesInSwitch
switch (val) {
case 1:
console.log("un");
// erreur : pas de break
case 2:
console.log("deux");
break;
}
noPropertyAccessFromIndexSignature
Pour les objets avec une signature d’index, force obj["key"] au lieu de obj.key. Distinction utile pour la lisibilité.
noImplicitOverride
class A {
greet() { /* ... */ }
}
class B extends A {
override greet() { /* ... */ } // mot-clé `override` obligatoire
}
Empêche d’override accidentellement une méthode parent.
4. Modules : NodeNext, Bundler, ou autre
Le choix de module et moduleResolution est plus subtil qu’il n’y paraît et a beaucoup évolué.
Pour Node.js récent (LTS 20+)
{
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
NodeNext fait correspondre exactement le comportement réel de Node.js, y compris les distinctions ESM/CJS modernes. Demande des extensions .js explicites dans les imports même pour les fichiers .ts :
// Imports avec extension
import { foo } from "./util.js"; // pointe vers util.ts en source
Pour bundler (Vite, esbuild, webpack)
{
"module": "ESNext",
"moduleResolution": "Bundler"
}
Bundler est plus permissif : pas besoin d’extensions explicites, le bundler gère la résolution.
À éviter en 2026
module: "CommonJS" reste valide mais limité aux projets legacy Node. moduleResolution: "Node" est l’ancien comportement, dépassé.
5. Target et lib
target
Détermine la version JavaScript émise.
{ "target": "ES2022" } // recommandé en 2026 pour serveur
{ "target": "ES2020" } // pour navigateurs un peu plus anciens
Plus le target est récent, plus le code émis est compact et lisible (les fonctionnalités modernes ne sont pas transpilées). Mais doit être supporté par la cible d’exécution.
lib
Quels types de bibliothèques sont disponibles globalement.
{ "lib": ["ES2022", "DOM"] } // navigateur + JS moderne
{ "lib": ["ES2022"] } // Node.js, pas de DOM
{ "lib": ["ES2022", "WebWorker"] } // worker
Sans DOM, TypeScript signalera document is not defined dans un contexte serveur — utile pour s’assurer qu’on n’utilise pas accidentellement des APIs navigateur dans du code Node.
6. Paths aliases pour imports propres
Pour éviter les import "../../../../utils/foo" à n’en plus finir :
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@/*": ["*"],
"@components/*": ["components/*"],
"@lib/*": ["lib/*"]
}
}
}
Usage :
import { Button } from "@components/Button";
import { fetcher } from "@lib/fetcher";
Attention : ces alias doivent aussi être configurés au niveau du runtime ou bundler (Vite, Node avec tsx, Jest avec moduleNameMapper). TypeScript seul ne suffit pas pour l’exécution.
7. Source maps et déclarations
Source maps
{
"sourceMap": true,
"inlineSources": true
}
Permet de déboguer le code TypeScript original même quand on exécute le JavaScript compilé. Indispensable en développement, optionnel en production selon politique de débogage.
Fichiers de déclaration
Pour une bibliothèque :
{
"declaration": true,
"declarationMap": true,
"outDir": "./dist"
}
Génère les fichiers .d.ts à côté du JavaScript. declarationMap permet au navigateur « go to definition » de pointer vers le source TypeScript original.
8. tsconfig pour différents contextes
Application Node.js / API
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Application Frontend (Vite, React)
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"strict": true,
"noUncheckedIndexedAccess": true,
"noEmit": true, // Vite gère le build
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"allowImportingTsExtensions": true
},
"include": ["src/**/*"]
}
noEmit: true car le bundler (Vite) compile, TypeScript fait juste la vérification de types. isolatedModules: true requis pour les bundlers qui compilent fichier par fichier.
Bibliothèque publiée
Combinaison qui produit JS + .d.ts pour distribution :
{
"compilerOptions": {
"target": "ES2020", // un peu plus conservateur pour large compatibilité
"module": "ESNext",
"moduleResolution": "Bundler",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
9. Performance de compilation
Sur des gros projets (50k+ lignes), le compilateur peut devenir lent. Quelques leviers :
incremental
{ "incremental": true, "tsBuildInfoFile": "./.tsbuildinfo" }
Cache la dernière compilation, recompile seulement ce qui a changé. Gain spectaculaire sur les builds répétés.
skipLibCheck
Saute la vérification des fichiers .d.ts des dépendances. Active par défaut dans la plupart des templates modernes. Gain significatif, risque limité.
Project references
Pour des monorepos : découper la base en projets TypeScript séparés avec references. Chaque projet est compilé indépendamment et peut être mis en cache.
{
"references": [
{ "path": "../shared" },
{ "path": "../api" }
]
}
Outils alternatifs pour le build
esbuild, swc ou tsc avec --watch peuvent être plus rapides selon le besoin. tsc reste le seul à faire la vérification de types complète. Pour la prod : tsc --noEmit pour la vérif + esbuild pour la compilation effective est un combo populaire.
Voir aussi → JavaScript moderne : patterns pratiques pour les patterns de code qui exploitent au mieux ces vérifications.
10. FAQ
Faut-il toujours activer noUncheckedIndexedAccess ?
Oui pour un nouveau projet. Sur un projet existant, c’est l’option strictes qui fait le plus de bruit à l’activation (beaucoup d’erreurs apparaissent). À introduire après les autres options strictes, en corrigeant progressivement.
strict: true casse mon legacy, comment migrer progressivement ?
Activer strict: false mais activer une à une les options qui le composent : strictNullChecks, puis noImplicitAny, puis les autres. Permet une migration par étapes maîtrisées. Voir Migrer un projet JavaScript vers TypeScript.
skipLibCheck désactive-t-il vraiment des protections importantes ?
Pas vraiment en pratique. Les .d.ts des bibliothèques sont supposés corrects, et leurs erreurs propres ne devraient pas bloquer la compilation de votre code. Activer skipLibCheck: true est presque toujours la bonne décision.
Puis-je avoir plusieurs tsconfig dans un même projet ?
Oui. Pattern courant : tsconfig.json racine pour la config commune, tsconfig.build.json qui étend pour la production (exclut tests, source maps allégés), tsconfig.test.json pour les tests. Utiliser extends: "./tsconfig.json" pour hériter.
target: "ESNext" ou une version fixe ?
Préférer une version fixe (ES2022 par exemple) à ESNext. ESNext signifie « la version la plus récente connue par cette version de TypeScript », ce qui change implicitement à chaque mise à jour de TypeScript. Une version fixe rend le comportement prévisible et reproductible.
Comment gérer un fichier qui doit être compilé avec une autre config ?
Avec un tsconfig séparé qui inclut juste ce fichier, ou avec des // @ts-ignore / // @ts-expect-error localisés (à utiliser avec parcimonie). Pour des cas comme l’utilisation de import.meta qui requiert un module spécifique, un fichier dédié peut être judicieux.
Y a-t-il des options à éviter absolument ?
noEmitOnError: false est dangereux : permet de produire du JavaScript même quand TypeScript signale des erreurs. Mieux vaut le laisser à true (défaut) et corriger les erreurs. allowJs n’est utile qu’en migration progressive ; à désactiver une fois la migration terminée.
Articles liés (cluster TypeScript et JavaScript moderne)
- 👉 TypeScript et JavaScript moderne : guide pratique (pillar)
- 👉 Migrer un projet JavaScript vers TypeScript
- 👉 JavaScript moderne : patterns pratiques
Article mis à jour le 25 avril 2026. Pour signaler une erreur ou suggérer une amélioration, écrivez-nous.