Add TypeScript To An Existing React Project
Learn how to add TypeScript to your existing React project in a few simple steps.
tsconfig.json
scares everyone. It's a huge file with a TON of potential options.
But really, there are only a few configuration options you need to care about. Let's figure them out, and cheatsheet them.
Want just the code? Here you go:
json
{"compilerOptions": {/* Base Options: */"esModuleInterop": true,"skipLibCheck": true,"target": "es2022","verbatimModuleSyntax": true,"allowJs": true,"resolveJsonModule": true,"moduleDetection": "force",/* Strictness */"strict": true,"noUncheckedIndexedAccess": true,/* If transpiling with TypeScript: */"moduleResolution": "NodeNext","module": "NodeNext","outDir": "dist","sourceMap": true,/* If NOT transpiling with TypeScript: */"moduleResolution": "Bundler","module": "ESNext","noEmit": true,/* If your code runs in the DOM: */"lib": ["es2022", "dom", "dom.iterable"],/* If your code doesn't run in the DOM: */"lib": ["es2022"],/* If you're building for a library: */"declaration": true,/* If you're building for a library in a monorepo: */"composite": true,"declarationMap": true}}
Here are the base options I recommend for all projects.
json
{"compilerOptions": {"esModuleInterop": true,"skipLibCheck": true,"target": "es2022","verbatimModuleSyntax": true,"allowJs": true,"resolveJsonModule": true,"moduleDetection": "force"}}
esModuleInterop
: Helps mend a few of the fences between CommonJS and ES Modules.skipLibCheck
: Skips checking the types of .d.ts
files. This is important for performance, because otherwise all node_modules
will be checked.target
: The version of JavaScript you're targeting. I recommend es2022
over esnext
for stability.verbatimModuleSyntax
: Gives you strict errors for import
and export
syntax in .cts
and .mts
files.allowJs
and resolveJsonModule
: Allows you to import .js
and .json
files. Always useful.moduleDetection
: This option forces TypeScript to consider all files as modules. This helps to avoid 'cannot redeclare block-scoped variable' errors.Here are the strictness options I recommend for all projects.
json
{"compilerOptions": {"strict": true,"noUncheckedIndexedAccess": true}}
strict
: Enables all strict type checking options. Indispensable.noUncheckedIndexedAccess
: Prevents you from accessing an array or object without first checking if it's defined. This is a great way to prevent runtime errors, and should really be included in strict
.Many folks recommended the strictness options in tsconfig/bases
, a wonderful repo which catalogs TSConfig options. These options include lots of rules which I consider too 'noisy', like noImplicitReturns
, noUnusedLocals
, noUnusedParameters
, and noFallthroughCasesInSwitch
. I recommend you add these rules to your tsconfig.json
only if you want them.
If you're transpiling your code (creating JavaScript files) with tsc
, you'll want these options.
json
{"compilerOptions": {"moduleResolution": "NodeNext","module": "NodeNext","outDir": "dist"}}
moduleResolution
: Tells TypeScript how to resolve modules. NodeNext
is the best option for if the code you're writing is meant to be run in Node.module
: Tells TypeScript what module syntax to use. NodeNext
is the best option for Node.outDir
: Tells TypeScript where to put the compiled JavaScript files. dist
is my preferred convention, but it's up to you.If you're not transpiling your code with tsc
, i.e. using TypeScript as more of a linter, you'll want these options.
json
{"compilerOptions": {"moduleResolution": "Bundler","module": "ESNext","noEmit": true}}
moduleResolution
: Bundler
is the best option for if the code you're writing is meant to be bundled with a tool like Webpack, Rollup, Babel, SWC, or ESBuild.module
: ESNext
is the best option because it most closely mimics how bundlers treat modules.If your code runs in the DOM, you'll want these options.
json
{"compilerOptions": {"lib": ["es2022", "dom", "dom.iterable"]}}
lib
: Tells TypeScript what built-in types to include. es2022
is the best option for stability. dom
and dom.iterable
give you types for window
, document
etc.If your code doesn't run in the DOM, you'll want lib: ["es2022"]
.
json
{"compilerOptions": {"lib": ["es2022"]}}
These are the same as above, but without the dom
and dom.iterable
typings.
If you're building for a library, you'll want declaration: true
.
json
{"compilerOptions": {"declaration": true}}
declaration
: Tells TypeScript to emit .d.ts
files. This is needed so that libraries can get autocomplete on the .js
files you're creating.If you're building for a library in a monorepo, you'll also want these options.
json
{"compilerOptions": {"declaration": true,"composite": true,"sourceMap": true,"declarationMap": true}}
composite
: Tells TypeScript to emit .tsbuildinfo
files. This tells TypeScript that your project is part of a monorepo, and also helps it to cache builds to run faster.sourceMap
and declarationMap
: Tells TypeScript to emit source maps and declaration maps. These are needed so that when consumers of your libraries are debugging, they can jump to the original source code using go-to-definition.Hopefully, I've given you a bit of inspiration for the next time you need to configure TypeScript.
Did I miss anything? Let me know by pinging me on X.
Share this article with your friends
Learn how to add TypeScript to your existing React project in a few simple steps.
Big projects like Svelte and Drizzle are not abandoning TypeScript, despite some recent claims.
Learn different ways to pass a component as a prop in React: passing JSX, using React.ComponentType, and using React.ElementType.
Learn about TypeScript performance and how it affects code type-checking speed, autocomplete, and build times in your editor.
When typing React props in a TypeScript app, using interfaces is recommended, especially when dealing with complex intersections of props.
Learn TypeScript in 2023 with this step-by-step guide. Understand the basics, essential types, unions, and narrowing techniques.