How to Init a TypeScript Project for a React Application

To init a TypeScript project for a React application, configure tsconfig.json with "jsx": "react-jsx" to enable the automatic JSX runtime, enable "strict": true for comprehensive type checking, and set "module": "ESNext" with "moduleResolution": "NodeNext" for optimal bundler compatibility.

Initializing a TypeScript project for a React application requires specific compiler options to leverage modern JSX transforms and ensure long-term maintainability. According to the microsoft/TypeScript source code, the compiler defines these behaviors in src/compiler/commandLineParser.ts and references base defaults in src/tsconfig-base.json. This guide distills the essential configuration steps derived from the TypeScript repository itself to optimize performance and type safety in React codebases.

Essential Compiler Flags for React

When configuring a React project, specific flags in tsconfig.json control JSX emission, module resolution, and build performance. The following options are defined in the TypeScript compiler source and represent the current best practices:

  • "jsx": "react-jsx" – Enables the automatic JSX runtime introduced in React 17. This eliminates the need for import React from "react" in every file and produces smaller bundle sizes. The valid values for this flag are defined in src/compiler/commandLineParser.ts at lines 40-55.

  • "jsxImportSource": "react" – Specifies the module specifier for the JSX factory functions. If using alternative frameworks like Preact or Solid, you can redirect this to their respective packages. The diagnostic messages for this option are located in src/compiler/diagnosticMessages.json at lines 6288-6290.

  • "module": "ESNext" and "moduleResolution": "NodeNext" – Aligns emitted code with modern bundlers such as Vite, Webpack 5, or ESBuild. The TypeScript repository itself uses NodeNext as the default resolution strategy in src/tsconfig-base.json at lines 9-11.

  • "strict": true – Activates the full suite of strictness checks including noImplicitAny, strictNullChecks, and strictFunctionTypes. This catches potential runtime errors during compilation.

  • "isolatedModules": true – Ensures each file can be transpiled independently, which is required by fast transpilers like ESBuild and Babel. This prevents cross-file dependencies during the emit phase.

  • "skipLibCheck": true – Bypasses type checking for declaration files in node_modules, significantly reducing compilation time without affecting your application's type safety.

  • "esModuleInterop": true and "allowSyntheticDefaultImports": true – Facilitates ergonomic imports from CommonJS modules such as react-dom.

Create a tsconfig.json that extends a base configuration, following the architectural pattern used throughout the microsoft/TypeScript repository. The base config at src/tsconfig-base.json enables "composite": true and "incremental": true at lines 13-17, which are essential for project references and fast rebuilds.

{
  "extends": "./tsconfig-base.json",
  "compilerOptions": {
    "target": "ES2020",
    "jsx": "react-jsx",
    "module": "ESNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "resolveJsonModule": true,
    "types": ["react", "react-dom"]
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

The "include": ["src"] directive restricts the compiler to your source directory, reducing the scope of type checking and improving performance. Extending a base configuration keeps shared settings DRY while allowing project-specific overrides.

Installing Dependencies

Install the compiler and React type definitions as development dependencies, while keeping React itself as a runtime dependency:

npm install -D typescript @types/react @types/react-dom
npm install react react-dom

The @types packages provide declaration files that enable IntelliSense and type checking for the React APIs without including source code in your production bundle.

Enabling Fast Incremental Builds

The TypeScript compiler supports incremental compilation through the "incremental" and "composite" flags. When these are enabled in your base configuration (as seen in src/tsconfig-base.json), the compiler generates a tsconfig.tsbuildinfo cache file that tracks the state of the compilation.

To leverage this in your React project, use the build mode commands:

tsc -b          # Build using the cache

tsc -b --watch  # Watch mode for development

The build mode (-b) respects project references and only recompiles files that have changed or whose dependencies have changed, dramatically reducing build times in large codebases.

JSX Pragma and Codefixes

The TypeScript compiler recognizes JSX pragmas such as /** @jsx */ and /** @jsxImportSource */ defined in src/compiler/types.ts at lines 10266-10278. These comments allow per-file overrides of the JSX factory.

If you omit the "jsx" flag, the language service can automatically suggest a fix. The implementation in src/services/codefixes/fixEnableJsxFlag.ts at line 42 demonstrates how the compiler mutates tsconfig.json to add "jsx": "react" when JSX is detected without the proper configuration.

Maintainability Best Practices

Separate concerns by maintaining a root-level tsconfig-base.json with universal settings, and project-specific tsconfig.json files that extend it. This mirrors the structure of the microsoft/TypeScript repository itself.

Avoid the any type by enforcing strict null checks. When you need temporary escape hatches, prefer unknown with type guards over any.

Configure path mapping to simplify imports in deep directory structures:

{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "components/*": ["components/*"],
      "utils/*": ["utils/*"]
    }
  }
}

Practical Code Examples

Basic React Entry Point

With "jsx": "react-jsx" configured, you no longer need to import React explicitly:

import { createRoot } from "react-dom/client";
import App from "./App";

const root = createRoot(document.getElementById("root")!);
root.render(<App />);

The compiler automatically injects the JSX runtime import based on the pragma definitions in src/compiler/types.ts.

Custom JSX Factory Configuration

To use Preact instead of React, modify the import source:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}

The corresponding source file imports from Preact:

import { render } from "preact";
import App from "./App";

render(<App />, document.body);

Build Scripts Configuration

Add the following scripts to your package.json to leverage incremental compilation:

{
  "scripts": {
    "build": "tsc -b",
    "watch": "tsc -b --watch",
    "type-check": "tsc --noEmit"
  }
}

The type-check script runs a full type check without emitting files, useful for CI pipelines where the bundler handles transpilation.

Summary

  • Configure tsconfig.json with "jsx": "react-jsx" to utilize React 17's automatic runtime, eliminating redundant imports and reducing bundle size.
  • Enable strict mode ("strict": true) to catch null pointer exceptions and implicit any types before runtime.
  • Use modern module resolution ("moduleResolution": "NodeNext") as implemented in src/tsconfig-base.json to ensure compatibility with ES modules.
  • Leverage incremental builds via tsc -b and the "composite": true setting to cache compilation state and speed up rebuilds.
  • Reference source locations such as src/compiler/commandLineParser.ts and src/services/codefixes/fixEnableJsxFlag.ts to understand how the compiler validates and fixes configuration.

Frequently Asked Questions

What is the difference between "jsx": "react" and "jsx": "react-jsx"?

The "react" setting transforms JSX into React.createElement calls and requires an explicit import React from "react" in every file. The "react-jsx" setting, defined in src/compiler/commandLineParser.ts, uses the automatic JSX runtime introduced in React 17, injecting the jsx runtime functions automatically and allowing you to omit the React import.

Why is "isolatedModules": true required for many React projects?

This flag ensures each file can be transpiled independently without cross-file type information, which is necessary for transpilers like Babel and ESBuild that process files in parallel. As noted in the TypeScript diagnostic system, this prevents subtle runtime errors when const enums or types are used across file boundaries in non-TypeScript transpilation pipelines.

How do I enable incremental compilation in a TypeScript React project?

Set "incremental": true and "composite": true in your base configuration (as seen in src/tsconfig-base.json lines 13-17), then use tsc -b instead of tsc to build. The compiler generates a .tsbuildinfo file that caches the dependency graph and emitted file hashes, allowing subsequent builds to skip unchanged files.

Where are JSX pragma comments like /** @jsx */ handled in the TypeScript source?

Pragma definitions for JSX directives are stored in src/compiler/types.ts at lines 10266-10278. These pragmas allow per-file overrides of the JSX factory function and import source, providing flexibility when mixing different JSX frameworks within a single project or migrating between them.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →