TypeScript tsconfig for React Development: Essential Settings
The tsconfig.json file controls how TypeScript compiles your React code. It determines which JavaScript version to target, whether to enforce strict type checking, how to resolve imports, and which libraries to include. Understanding the key settings helps you optimize TypeScript for your project's needs and avoid common pitfalls.
What Are the Most Important tsconfig Settings?
The tsconfig.json has dozens of options, but most React projects only need to configure a handful. Here is a breakdown of the essential settings:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"allowImportingTsExtensions": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Let me explain each critical setting and why it matters for React development.
What Does the target Setting Do?
The target option determines which JavaScript version TypeScript emits. Setting target: "ES2020" means TypeScript compiles your modern TypeScript code to JavaScript that runs on ES2020-compatible browsers (all modern browsers from 2020 onwards):
{
"compilerOptions": {
"target": "ES2020"
}
}
When you set target: "ES2020", TypeScript keeps modern features like arrow functions, classes, and template literals as-is. If you set target: "ES2015", TypeScript compiles const to var and arrow functions to function declarations. For React projects in 2026, always use ES2020 or higher (like ES2022). Only use lower targets if you need to support older browsers (which is rare).
Why Is strict Mode Essential?
The "strict": true option enables all strict type-checking rules. It is the single most important setting for catching bugs:
{
"compilerOptions": {
"strict": true
}
}
When strict is true, TypeScript enforces:
noImplicitAny: Variables must have explicit types (cannot beanyimplicitly).strictNullChecks:nullandundefinedare separate types; you must handle them explicitly.strictFunctionTypes: Function types are checked strictly.strictPropertyInitialization: Class properties must be initialized.
Here is the difference:
// With strict: false (dangerous)
function processData(data) {
// data is implicitly any
return data.toUpperCase(); // Runtime error if data is not a string
}
// With strict: true (safe)
function processData(data: string) {
// Must declare the type
return data.toUpperCase(); // Guaranteed to work
}
Always enable strict mode. If you are inheriting a non-strict codebase, enable it and fix the errors incrementally.
How Does Module Resolution Work?
The moduleResolution option determines how TypeScript finds imported modules. For Vite and bundler-based projects, use "moduleResolution": "bundler":
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}
The bundler mode matches how Vite and modern bundlers resolve modules. It understands package.json exports fields and resolves to the correct entry point for each import:
// TypeScript looks for Button.ts, Button.tsx, or Button/index.tsx
import { Button } from './components/Button';
// Resolves to the export field in package.json for npm packages
import { Button } from '@mycompany/ui-lib';
Other options like "node" or "classic" work but are less appropriate for frontend projects. Stick with bundler for React projects using modern build tools.
What About the lib Setting?
The lib option tells TypeScript which global types to include. For React projects, include at least ES2020, DOM, and DOM.Iterable:
{
"compilerOptions": {
"lib": ["ES2020", "DOM", "DOM.Iterable"]
}
}
ES2020includes JavaScript built-ins likePromise,Array, andStringmethods.DOMincludes browser APIs likedocument,window, andHTMLElement.DOM.Iterableincludes iteration over DOM collections.
If your project uses Web Workers, add "WebWorker". If it uses Node.js APIs (which is rare in browser React), add "ESNext". For most React projects, ["ES2020", "DOM", "DOM.Iterable"] is sufficient.
How Do You Configure JSX Settings?
The jsx option determines how TypeScript handles JSX syntax. For modern React (17+), use "jsx": "react-jsx":
{
"compilerOptions": {
"jsx": "react-jsx"
}
}
This tells TypeScript to use the automatic JSX transform introduced in React 17, which means you do not need to import React in every file:
// With "jsx": "react-jsx", this just works
export function App() {
return <div>Hello React</div>;
}
// With "jsx": "react", you would need:
// import React from 'react';
// export function App() { ... }
The "react-jsx" setting is the modern default for React projects. If you are using an older project or a non-React JSX framework, consult your framework's documentation.
What Is the esModuleInterop Setting?
The esModuleInterop option allows interoperability between ES modules and CommonJS. Set it to true in React projects:
{
"compilerOptions": {
"esModuleInterop": true
}
}
This setting allows you to import CommonJS modules using ES module syntax without issues:
// Works with esModuleInterop: true
import React from 'react';
// Without it, you would need:
// import * as React from 'react';
Always enable esModuleInterop unless your project uses a strict ES-module-only setup.
Why Should You Enable isolatedModules?
The isolatedModules option tells TypeScript to check that each file can be compiled independently. This is important for bundlers like Vite and Esbuild, which compile one file at a time:
{
"compilerOptions": {
"isolatedModules": true
}
}
When enabled, TypeScript prevents certain valid-looking TypeScript code that would fail at bundle time:
// ERROR with isolatedModules: true
// Cannot use type-only exports without exporting the type by name
export { type User } from './types';
// Safe: explicit export
export type { User } from './types';
Enable isolatedModules to ensure your code compiles correctly with Vite.
What About noEmit and allowImportingTsExtensions?
The noEmit option tells TypeScript not to emit .js files. Since your bundler (Vite) handles the output, TypeScript's only job is type-checking:
{
"compilerOptions": {
"noEmit": true
}
}
The allowImportingTsExtensions option allows you to import .ts and .tsx files with their extensions:
{
"compilerOptions": {
"allowImportingTsExtensions": true
}
}
This is useful when working with Vite and bundlers that natively support TypeScript. Standard Node.js projects do not use this.
How Do You Handle Slow Type-Checking?
If your project has many dependencies, skipLibCheck speeds up compilation by skipping type-checking of declaration files in node_modules:
{
"compilerOptions": {
"skipLibCheck": true
}
}
This is safe in most cases—you trust that npm packages have correct type definitions. Disable it only if you encounter type errors in third-party libraries.
Key Takeaways
"strict": trueis non-negotiable; it enables all strict type-checking rules."target": "ES2020"targets modern browsers; adjust only if supporting legacy environments."jsx": "react-jsx"enables the automatic JSX transform for React 17+."moduleResolution": "bundler"matches Vite's module resolution strategy."noEmit": truetells TypeScript to skip file output (Vite handles it)."skipLibCheck": truespeeds up compilation by skipping node_modules type-checking.
Frequently Asked Questions
Can I start with a loose tsconfig and tighten it later?
Not recommended. Enabling strict mode from the start is easier than retrofitting hundreds of files. If inheriting a non-strict project, enable it incrementally in specific directories using a referenced tsconfig.
What is a tsconfig.app.json?
Some projects split tsconfig.json into multiple files: tsconfig.json (base), tsconfig.app.json (app code), and tsconfig.test.json (tests). This allows different settings for different contexts. Vite scaffolds projects with this pattern for flexibility. For simple projects, a single tsconfig.json is fine.
Should I set resolveJsonModule?
Yes, if you import JSON files in your code:
import config from './config.json';
Set "resolveJsonModule": true to allow this. It is safe and commonly used.
What is forceConsistentCasingInFileNames?
Enable "forceConsistentCasingInFileNames": true to catch mistakes where you import a file with the wrong case (e.g., import X from './Component' when the file is component.ts). This prevents bugs on case-sensitive file systems (Linux, Mac).
Can I use different tsconfig files for different environments?
Yes, use "references" in your root tsconfig.json to include other configs:
{
"compilerOptions": { ... },
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.test.json" }
]
}
Each referenced config can have different settings for its context.