Configuring tsconfig.json in a Next.js project is a common task once you decide to use TypeScript for your React-based application. While Next.js offers a pretty smooth TypeScript integration out of the box, understanding how to customize and optimize your tsconfig.json can make a big difference in developer experience, build performance, and maintainability.
In my experience, many developers either stick with the default tsconfig.json that Next.js generates or try to heavily customize it without fully understanding the implications. This can lead to confusing type errors, slower builds, or even runtime issues. So, I’ll walk through what the core concepts are, what the default setup looks like, and how you can tweak it for real-world projects.
When you add TypeScript to a Next.js project (usually by adding a tsconfig.json file or running touch tsconfig.json and starting the dev server), Next.js automatically detects it and generates a default tsconfig.json for you if one doesn’t exist. This default config is designed to work well with Next.js’s file system routing, React, and modern JavaScript features.
Here’s a simplified version of what Next.js typically generates:
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
Let’s break down some important options here:
target: "es5" ensures compatibility with older browsers, but you can bump this up to es6 or later if you don’t need legacy support.lib includes DOM and ESNext features, which is essential for React and modern JS.allowJs: true lets you gradually migrate from JavaScript to TypeScript by allowing JS files.skipLibCheck: true speeds up build times by skipping type checks on declaration files (.d.ts), which is usually safe.strict: false disables all strict type-checking options by default, but I usually recommend enabling strict mode in production projects.noEmit: true tells TypeScript not to output compiled files since Next.js handles the build pipeline.jsx: "preserve" keeps JSX syntax intact for Next.js’s Babel pipeline to process.Once you have the basics, you’ll likely want to customize your tsconfig.json to fit your team’s needs and project scale. Here are some common adjustments and why you might make them:
By default, Next.js sets strict to false. I almost always recommend turning this on for better type safety:
{
"compilerOptions": {
"strict": true
}
}
Strict mode enables a bunch of helpful checks like noImplicitAny, strictNullChecks, and strictFunctionTypes. This helps catch bugs early but can be a bit overwhelming at first if you’re migrating a large JS codebase.
If your app targets modern browsers or Node.js environments, you can update target and module for better performance and cleaner output:
{
"compilerOptions": {
"target": "es6",
"module": "esnext"
}
}
Using es6 or later as the target allows you to use native features like async/await without transpilation. esnext modules enable tree shaking and better bundling.
One of the most practical tweaks is adding paths to avoid long relative imports like ../../../components/Button. For example:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@components/*": ["components/*"],
"@utils/*": ["utils/*"]
}
}
}
This lets you import components like:
import Button from '@components/Button';
Remember to update your jsconfig.json or next.config.js if you’re using custom webpack aliases to keep everything in sync.
By default, Next.js includes all .ts and .tsx files and excludes node_modules. In larger monorepos or multi-package setups, you might want to be more explicit:
{
"include": ["src/**/*", "next-env.d.ts"],
"exclude": ["node_modules", "dist"]
}
This helps keep the TypeScript compiler focused and speeds up builds.
noEmit: Next.js expects noEmit: true because it handles compilation internally. Changing this can cause duplicate outputs or conflicts.next-env.d.ts: This file is auto-generated and includes necessary type declarations for Next.js. Accidentally deleting or excluding it leads to missing types and weird errors.paths in tsconfig.json without updating webpack or IDE settings causes unresolved imports and poor DX.jsx incorrectly: Changing jsx from preserve to react or react-jsx breaks Next.js’s Babel pipeline, causing build failures.TypeScript compilation speed can be a bottleneck in large Next.js projects. Here are some tips to keep builds fast:
skipLibCheck: true is a must-have to avoid type checking all dependencies, which rarely helps and wastes time.incremental: true to enable incremental builds, speeding up subsequent compilations.dist, build, or public from the compiler.include narrow to only source files.While tsconfig.json itself doesn’t directly impact security, good TypeScript practices help prevent bugs that can lead to vulnerabilities:
null or undefined errors that could crash your app.noEmit or deleting next-env.d.ts.| Feature | Next.js Default tsconfig | Create React App (CRA) | Custom React + Webpack |
|---|---|---|---|
noEmit |
true (Next.js handles build) | false (CRA emits files) | Depends on setup |
jsx option |
preserve (Next.js uses Babel) | react-jsx (new JSX transform) | Varies |
| Strict mode | off by default | off by default | Varies |
| Path aliases | Needs manual config | Needs manual config | Fully customizable |
Imagine you’re working on a large Next.js app with multiple teams. You want to enforce strict typing to reduce bugs but also keep build times manageable. Here’s what I’d do:
strict: true but selectively disable some strict flags if they cause too many false positives initially (e.g., strictNullChecks can be tricky).paths to create aliases for shared components and utilities, improving code readability.skipLibCheck: true to speed up builds.incremental: true for faster rebuilds during development.tsconfig.json as the codebase evolves, avoiding config drift.By doing this, you balance developer productivity, code safety, and build performance — all crucial for scaling a Next.js project.