Why does a new React Native app's tsconfig.json just extend @react-native/typescript-config, and what does that base config give you?
answer
- one extends line in the template
- versioned with react-native itself
- strict, noEmit, jsx react-native
- bundler resolution plus react-native condition
- skipLibCheck and Jest types
basics
~20 sThe base config encodes React Native's type-checking setup: strict checks, noEmit, JSX kept as react-native, bundler-style resolution with the react-native export condition Metro uses, Jest types and skipLibCheck. It is versioned with React Native, so upgrades update it.
solid answer
~40 s`@react-native/typescript-config` is a package published from the React Native repo, versioned with it (0.87.1 alongside `react-native` 0.87.1), whose only export is a `tsconfig.json`. New apps extend it so their TypeScript settings match how Metro and Babel actually treat the code. It turns on `strict`, sets `noEmit` because Babel produces the JavaScript, keeps JSX with `jsx: "react-native"`, uses `moduleResolution: "bundler"` with `customConditions: ["react-native"]` so `tsc` picks the same package export branches Metro does, adds Jest's types, enables `skipLibCheck` and `isolatedModules`, and excludes `Pods`. Its README says to update it in sync with the rest of the app, and the Upgrade Helper diff bumps it on every upgrade.
go deeper
Recall that a new app extends @react-native/typescript-config and that it turns on strict checking with noEmit.
Explain the key options: noEmit, jsx react-native, bundler resolution with the react-native condition, skipLibCheck and Jest types.
Extend it without breaking it: override arrays carefully, keep skipLibCheck, and upgrade it together with react-native and the Babel and Metro packages.
Decide how far a team diverges from the upstream base config and how shared configs across several apps stay aligned with React Native releases.
## What the package is `@react-native/typescript-config` lives in the React Native monorepo under `packages/typescript-config`. Its `package.json` exports a single file, `tsconfig.json`, and its version tracks React Native's: in the 0.87.1 release it is also 0.87.1. Its README describes it as the default `tsconfig.json` used by newly built React Native apps, customised for specific React Native versions and meant to be **updated in sync with the rest of your app**. A new app's own `tsconfig.json` is therefore usually one line: ```json { "extends": "@react-native/typescript-config" } ``` ## What it sets, and why | Option | Value | Why it fits React Native | |---|---|---| | `strict` | `true` | full strict checking from day one | | `noEmit` | `true` | Babel, not `tsc`, produces the JavaScript Metro bundles | | `jsx` | `"react-native"` | JSX is preserved, matching a check-only setup | | `isolatedModules` | `true` | flags code a per-file Babel transform cannot compile | | `moduleResolution` | `"bundler"` | resolves imports the way a bundler such as Metro does | | `customConditions` | `["react-native"]` | picks the `react-native` branch of packages' `exports`, the condition Metro's React Native config also uses | | `types` | `["jest"]` | Jest globals such as `describe` and `expect` type-check in tests | | `skipLibCheck` | `true` | errors inside third-party `.d.ts` files stay out of your results | | `allowJs` | `true` | JavaScript files can be imported from TypeScript | | `target` / `module` | `"esnext"` | no down-levelling, since `tsc` emits nothing | It also enables `resolveJsonModule`, `allowImportingTsExtensions`, `esModuleInterop` and `allowSyntheticDefaultImports`, lists a set of `lib` entries up to newer ECMAScript features, and **excludes `**/Pods/**`**, so the CocoaPods folder under `ios/` is never type-checked. ## Why matching Metro matters The two settings that most often surprise people are the resolution ones. Many packages publish different files for different environments through the `exports` field of `package.json`. **Metro** resolves with a `react-native` condition; if `tsc` resolved with different conditions, it could type-check your code against a web or Node build of a library that the app never actually loads. Setting `moduleResolution: "bundler"` and `customConditions: ["react-native"]` keeps the type checker looking at the same files the bundle will contain. ## Extending it safely Put project-specific options in your own `compilerOptions`; they override the base: - `paths` for aliases, mirrored in Babel; - stricter options your team wants in addition to `strict`; - `include` or `exclude` to scope the program. Things to be careful with: 1. **Turning off `skipLibCheck`** floods results with errors from dependencies you cannot fix; the Strict TypeScript API guidance in 0.87 explicitly asks you to keep it on. 2. **Overriding `customConditions`** replaces the array rather than merging it, so keep `"react-native"` in any list you write. 3. **Copying the options instead of extending** freezes them; you then miss the changes each release makes to the base. ## During upgrades Because the package is versioned with React Native, an upgrade bumps it together with `react-native`, `@react-native/babel-preset` and `@react-native/metro-config`. The Upgrade Helper shows it in the `package.json` diff, and the TypeScript docs point to the Helper to find the versions matching your React Native release. ## What the base config leaves to you The package deliberately does not decide everything. Settings you add yourself, when you need them: - `paths` for import aliases, which also need a Babel counterpart; - `include` or `files`, if the program should be narrower than the project folder; - additional strictness flags beyond `strict`, if the team wants them; - per-folder configs, for example for scripts that run in Node rather than in the app. To see the final, merged result of your file plus the base, run `npx tsc --showConfig`. It prints the effective options, which is the quickest way to confirm that an override took effect or that an array such as `customConditions` was replaced rather than merged.
- In a React Native tsconfig that adds its own customConditions, why must "react-native" still be listed?An array option set in your `compilerOptions` replaces the base config's array rather than merging with it. Leaving out `"react-native"` makes `tsc` resolve packages' `exports` without the React Native condition Metro uses, so types may come from a different build than the one the app loads.
- Why does @react-native/typescript-config exclude **/Pods/**?`ios/Pods` is generated by CocoaPods from native dependencies and is not part of the app's JavaScript or TypeScript source. Excluding it keeps `tsc` from scanning a large generated folder that it has no reason to check.
saying these in an interview costs you the question
- The base config makes tsc emit the JavaScript Metro bundles
- Copying the options is better than extending the package
- Setting skipLibCheck to false makes the app safer
- The package version is unrelated to the react-native version