TypeScript
Next.js comes with built-in TypeScript, automatically installing the necessary packages and configuring the proper settings when you create a new project with create-next-app
.
To add TypeScript to an existing project, rename a file to .ts
/ .tsx
. Run next dev
and next build
to automatically install the necessary dependencies and add a tsconfig.json
file with the recommended config options.
Good to know: If you already have a
jsconfig.json
file, copy thepaths
compiler option from the oldjsconfig.json
into the newtsconfig.json
file, and delete the oldjsconfig.json
file.
IDE Plugin
Next.js includes a custom TypeScript plugin and type checker, which VSCode and other code editors can use for advanced type-checking and auto-completion.
You can enable the plugin in VS Code by:
- Opening the command palette (
Ctrl/⌘
+Shift
+P
) - Searching for "TypeScript: Select TypeScript Version"
- Selecting "Use Workspace Version"

Now, when editing files, the custom plugin will be enabled. When running next build
, the custom type checker will be used.
The TypeScript plugin can help with:
- Warning if the invalid values for segment config options are passed.
- Showing available options and in-context documentation.
- Ensuring the
'use client'
directive is used correctly. - Ensuring client hooks (like
useState
) are only used in Client Components.
🎥 Watch: Learn about the built-in TypeScript plugin → YouTube (3 minutes)
End-to-End Type Safety
The Next.js App Router has enhanced type safety. This includes:
- No serialization of data between fetching function and page: You can
fetch
directly in components, layouts, and pages on the server. This data does not need to be serialized (converted to a string) to be passed to the client side for consumption in React. Instead, sinceapp
uses Server Components by default, we can use values likeDate
,Map
,Set
, and more without any extra steps. Previously, you needed to manually type the boundary between server and client with Next.js-specific types. - Streamlined data flow between components: With the removal of
_app
in favor of root layouts, it is now easier to visualize the data flow between components and pages. Previously, data flowing between individualpages
and_app
were difficult to type and could introduce confusing bugs. With colocated data fetching in the App Router, this is no longer an issue.
Data Fetching in Next.js now provides as close to end-to-end type safety as possible without being prescriptive about your database or content provider selection.
We're able to type the response data as you would expect with normal TypeScript. For example:
async function getData() {
const res = await fetch('https://api.example.com/...')
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
return res.json()
}
export default async function Page() {
const name = await getData()
return '...'
}
For complete end-to-end type safety, this also requires your database or content provider to support TypeScript. This could be through using an ORM or type-safe query builder.
Route-Aware Type Helpers
Next.js generates global helpers for App Router route types. These are available without imports and are generated during next dev
, next build
, or via next typegen
:
Examples
Type checking next.config.ts
You can use TypeScript and import types in your Next.js configuration by using next.config.ts
.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
/* config options here */
}
export default nextConfig
Good to know: Module resolution in
next.config.ts
is currently limited toCommonJS
. This may cause incompatibilities with ESM only packages being loaded innext.config.ts
.
When using the next.config.js
file, you can add some type checking in your IDE using JSDoc as below:
// @ts-check
/** @type {import('next').NextConfig} */
const nextConfig = {
/* config options here */
}
module.exports = nextConfig
Statically Typed Links
Next.js can statically type links to prevent typos and other errors when using next/link
, improving type safety when navigating between pages.
Works in both the Pages and App Router for the href
prop in next/link
. In the App Router, it also types next/navigation
methods like push
, replace
, and prefetch
. It does not type next/router
methods in Pages Router.
Literal href
strings are validated, while non-literal href
s may require a cast with as Route
.
To opt-into this feature, typedRoutes
need to be enabled and the project needs to be using TypeScript.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
typedRoutes: true,
}
export default nextConfig
Next.js will generate a link definition in .next/types
that contains information about all existing routes in your application, which TypeScript can then use to provide feedback in your editor about invalid links.
Good to know: If you set up your project without
create-next-app
, ensure the generated Next.js types are included by adding.next/types/**/*.ts
to theinclude
array in yourtsconfig.json
:
{
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": ["node_modules"]
}
Currently, support includes any string literal, including dynamic segments. For non-literal strings, you need to manually cast with as Route
. The example below shows both next/link
and next/navigation
usage:
'use client'
import type { Route } from 'next'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
export default function Example() {
const router = useRouter()
const slug = 'nextjs'
return (
<>
{/* Link: literal and dynamic */}
<Link href="/about" />
<Link href={`/blog/${slug}`} />
<Link href={('/blog' + slug) as Route} />
{/* TypeScript error if href is not a valid route */}
<Link href="/aboot" />
{/* Router: literal and dynamic strings are validated */}
<button onClick={() => router.push('/about')}>Push About</button>
<button onClick={() => router.replace(`/blog/${slug}`)}>
Replace Blog
</button>
<button onClick={() => router.prefetch('/contact')}>
Prefetch Contact
</button>
{/* For non-literal strings, cast to Route */}
<button onClick={() => router.push(('/blog' + slug) as Route)}>
Push Non-literal Blog
</button>
</>
)
}
The same applies for redirecting routes defined by middleware:
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname === '/middleware-redirect') {
return NextResponse.redirect(new URL('/', request.url))
}
return NextResponse.next()
}
import type { Route } from 'next'
export default function Page() {
return <Link href={'/middleware-redirect' as Route}>Link Text</Link>
}
To accept href
in a custom component wrapping next/link
, use a generic:
import type { Route } from 'next'
import Link from 'next/link'
function Card<T extends string>({ href }: { href: Route<T> | URL }) {
return (
<Link href={href}>
<div>My Card</div>
</Link>
)
}
You can also type a simple data structure and iterate to render links:
import type { Route } from 'next'
type NavItem<T extends string = string> = {
href: T
label: string
}
export const navItems: NavItem<Route>[] = [
{ href: '/', label: 'Home' },
{ href: '/about', label: 'About' },
{ href: '/blog', label: 'Blog' },
]
Then, map over the items to render Link
s:
import Link from 'next/link'
import { navItems } from './nav-items'
export function Nav() {
return (
<nav>
{navItems.map((item) => (
<Link key={item.href} href={item.href}>
{item.label}
</Link>
))}
</nav>
)
}
How does it work?
When running
next dev
ornext build
, Next.js generates a hidden.d.ts
file inside.next
that contains information about all existing routes in your application (all valid routes as thehref
type ofLink
). This.d.ts
file is included intsconfig.json
and the TypeScript compiler will check that.d.ts
and provide feedback in your editor about invalid links.
Type IntelliSense for Environment Variables
During development, Next.js generates a .d.ts
file in .next/types
that contains information about the loaded environment variables for your editor's IntelliSense. If the same environment variable key is defined in multiple files, it is deduplicated according to the Environment Variable Load Order.
To opt-into this feature, experimental.typedEnv
needs to be enabled and the project needs to be using TypeScript.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
typedEnv: true,
},
}
export default nextConfig
Good to know: Types are generated based on the environment variables loaded at development runtime, which excludes variables from
.env.production*
files by default. To include production-specific variables, runnext dev
withNODE_ENV=production
.
With Async Server Components
To use an async
Server Component with TypeScript, ensure you are using TypeScript 5.1.3
or higher and @types/react
18.2.8
or higher.
If you are using an older version of TypeScript, you may see a 'Promise<Element>' is not a valid JSX element
type error. Updating to the latest version of TypeScript and @types/react
should resolve this issue.
Incremental type checking
Since v10.2.1
Next.js supports incremental type checking when enabled in your tsconfig.json
, this can help speed up type checking in larger applications.
Custom tsconfig
path
In some cases, you might want to use a different TypeScript configuration for builds or tooling. To do that, set typescript.tsconfigPath
in next.config.ts
to point Next.js to another tsconfig
file.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
typescript: {
tsconfigPath: 'tsconfig.build.json',
},
}
export default nextConfig
For example, switch to a different config for production builds:
import type { NextConfig } from 'next'
const isProd = process.env.NODE_ENV === 'production'
const nextConfig: NextConfig = {
typescript: {
tsconfigPath: isProd ? 'tsconfig.build.json' : 'tsconfig.json',
},
}
export default nextConfig
Why you might use a separate tsconfig
for builds
You might need to relax checks in scenarios like monorepos, where the build also validates shared dependencies that don't match your project's standards, or when loosening checks in CI to continue delivering while migrating locally to stricter TypeScript settings (and still wanting your IDE to highlight misuse).
For example, if your project uses useUnknownInCatchVariables
but some monorepo dependencies still assume any
:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"useUnknownInCatchVariables": false
}
}
This keeps your editor strict via tsconfig.json
while allowing the production build to use relaxed settings.
Good to know:
- IDEs typically read
tsconfig.json
for diagnostics and IntelliSense, so you can still see IDE warnings while production builds use the alternate config. Mirror critical options if you want parity in the editor.- In development, only
tsconfig.json
is watched for changes. If you edit a different file name viatypescript.tsconfigPath
, restart the dev server to apply changes.- The configured file is used in
next dev
,next build
,next lint
, andnext typegen
.
Disabling TypeScript errors in production
Next.js fails your production build (next build
) when TypeScript errors are present in your project.
If you'd like Next.js to dangerously produce production code even when your application has errors, you can disable the built-in type checking step.
If disabled, be sure you are running type checks as part of your build or deploy process, otherwise this can be very dangerous.
Open next.config.ts
and enable the ignoreBuildErrors
option in the typescript
config:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
typescript: {
// !! WARN !!
// Dangerously allow production builds to successfully complete even if
// your project has type errors.
// !! WARN !!
ignoreBuildErrors: true,
},
}
export default nextConfig
Good to know: You can run
tsc --noEmit
to check for TypeScript errors yourself before building. This is useful for CI/CD pipelines where you'd like to check for TypeScript errors before deploying.
Custom type declarations
When you need to declare custom types, you might be tempted to modify next-env.d.ts
. However, this file is automatically generated, so any changes you make will be overwritten. Instead, you should create a new file, let's call it new-types.d.ts
, and reference it in your tsconfig.json
:
{
"compilerOptions": {
"skipLibCheck": true
//...truncated...
},
"include": [
"new-types.d.ts",
"next-env.d.ts",
".next/types/**/*.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": ["node_modules"]
}
Version Changes
Version | Changes |
---|---|
v15.0.0 | next.config.ts support added for TypeScript projects. |
v13.2.0 | Statically typed links are available in beta. |
v12.0.0 | SWC is now used by default to compile TypeScript and TSX for faster builds. |
v10.2.1 | Incremental type checking support added when enabled in your tsconfig.json . |
Was this helpful?