//
Search across all documentation pages
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Set up a TypeScript-powered React project with a well-configured tsconfig.json, understand how .tsx files work, and learn the core type annotations every React developer needs.
// tsconfig.json (Next.js 15 / React 19 recommended)
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}// src/components/Greeting.tsx
type GreetingProps = {
name: string;
age?: number;
};
export function Greeting({ name, age }: GreetingProps) {
return (
<div>
<h1>Hello, {name}</h1>
{age !== undefined && <p>Age: {age}</p>}
</div>
);
}// Usage
<Greeting name="Alice" />
<Greeting name="Bob" age={30} />.tsx files are TypeScript files that support JSX syntax. The jsx: "preserve" setting tells TypeScript to leave JSX untouched for the bundler (Next.js, Vite) to handle.strict: true enables a family of strict checks (strictNullChecks, strictFunctionTypes, noImplicitAny, etc.) that catch the most bugs.moduleResolution: "bundler" matches how modern bundlers resolve imports, supporting package.json exports fields and extensionless imports.@/* let you write import { Button } from "@/components/Button" instead of fragile relative paths.Vite + React setup:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"noEmit": true,
"isolatedModules": true,
"skipLibCheck": true
},
"include": ["src"]
}Adding stricter options incrementally:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true
}
}npm install -D @types/react @types/react-dom. With React 19, types ship with react and @types/react may not be needed depending on your setup.type for props and simple shapes; use interface when you need declaration merging or extends.unknown over any. If you must use any, add a // eslint-disable comment and a TODO to fix it later."strict": true means TypeScript will not flag null or undefined access, defeating much of its value.jsx to "react" instead of "preserve" or "react-jsx" forces you to import React from "react" in every file.skipLibCheck: true is recommended for build speed but can hide type errors in your own .d.ts files.any silently disables type checking for everything that value touches downstream.| Approach | Pros | Cons |
|---|---|---|
strict: true from day one | Catches the most bugs early | Steeper learning curve for beginners |
Gradual adoption (strict: false) | Easier migration from JS | Misses critical null/undefined bugs |
JSDoc types (no .ts files) | Zero build step changes | Verbose, limited type expressiveness |
interface for all props | Declaration merging, familiar OOP style | Cannot express unions or mapped types |
type for all props | Unions, intersections, mapped types | No declaration merging |
strictNullChecks, noImplicitAny, strictFunctionTypes, and other checks as a group."preserve" leaves JSX untouched so the bundler (Next.js, Vite) handles the transformation."react-jsx" for Vite + React projects that use the automatic JSX runtime."react" only if you need the classic React.createElement transform (requires importing React in every file).type when you need unions, intersections, or mapped types.interface when you need declaration merging or extends.type is sufficient and more flexible.exports fields and extensionless imports."node" or "node16" for bundled projects.@types/react may not be needed..tsx files support JSX syntax in addition to TypeScript..ts files are for pure TypeScript logic with no JSX..tsx for any file that returns or contains JSX elements..d.ts files, including your own custom declaration files..d.ts files are also silently ignored.any silently disables type checking for the value and everything it touches downstream.unknown -- it forces you to narrow or validate before accessing properties.any, add a comment explaining why and a TODO to fix it later.{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}@/components/Button to ./src/components/Button.../../../components/Button.noUncheckedIndexedAccess adds | undefined to array and record index access, catching out-of-bounds bugs.exactOptionalPropertyTypes distinguishes between a missing property and one set to undefined.strict: true -- they must be enabled separately.type GreetingProps = {
name: string;
age?: number; // optional
};
function Greeting({ name, age = 25 }: GreetingProps) {
return <p>{name} is {age} years old</p>;
}? in the type.undefined.Reviewed by Chris St. John·Last updated Jul 7, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥