Menu

TypeScript

Next.js 内置了 TypeScript,当你使用 create-next-app 创建新项目时,会自动安装必要的包并配置适当的设置。

要将 TypeScript 添加到现有项目,请将文件重命名为 .ts / .tsx。运行 next devnext build 会自动安装必要的依赖项,并添加一个包含推荐配置选项的 tsconfig.json 文件。

值得注意的是:如果你已经有一个 jsconfig.json 文件,请将旧 jsconfig.json 中的 paths 编译器选项复制到新的 tsconfig.json 文件中,然后删除旧的 jsconfig.json 文件。

IDE 插件

Next.js 包含一个自定义 TypeScript 插件和类型检查器,VSCode 和其他代码编辑器可以使用它们进行高级类型检查和自动补全。

你可以在 VS Code 中通过以下方式启用该插件:

  1. 打开命令面板(Ctrl/⌘ + Shift + P
  2. 搜索"TypeScript: Select TypeScript Version"
  3. 选择"Use Workspace Version"
TypeScript Command Palette

现在,在编辑文件时,自定义插件将被启用。运行 next build 时,将使用自定义类型检查器。

TypeScript 插件可以帮助:

  • 如果传递了无效的段配置选项值,会发出警告。
  • 显示可用选项和上下文文档。
  • 确保正确使用 'use client' 指令。
  • 确保客户端钩子(如 useState)仅在客户端组件中使用。

**🎥 观看:**了解内置 TypeScript 插件 → YouTube(3 分钟)

端到端类型安全

Next.js App Router 具有增强的类型安全。这包括:

  1. 获取函数和页面之间无需序列化数据:你可以直接在服务器上的组件、布局和页面中 fetch。这些数据不需要被序列化(转换为字符串)就可以传递到客户端供 React 使用。相反,由于 app 默认使用服务器组件,我们可以使用像 DateMapSet 等值,而无需任何额外步骤。以前,你需要使用 Next.js 特定的类型手动在服务器和客户端之间进行类型边界的标记。
  2. 组件之间的数据流更加流畅:通过移除 _app 改用根布局,现在更容易可视化组件和页面之间的数据流。以前,在各个 pages_app 之间流动的数据很难进行类型标注,并且可能引入令人困惑的 bug。通过 App Router 中的协同定位数据获取,这不再是问题。

Next.js 中的数据获取现在提供了尽可能接近端到端的类型安全,而不会对你的数据库或内容提供商的选择进行规定。

我们能够像你期望的普通 TypeScript 那样对响应数据进行类型标注。例如:

app/page.tsx
TypeScript
async function getData() {
  const res = await fetch('https://api.example.com/...')
  // 返回值*不*被序列化
  // 你可以返回 Date、Map、Set 等
  return res.json()
}
 
export default async function Page() {
  const name = await getData()
 
  return '...'
}

对于完整的端到端类型安全,这还需要你的数据库或内容提供商支持 TypeScript。这可以通过使用 ORM 或类型安全的查询构建器来实现。

路由感知的类型辅助工具

Next.js 为 App Router 路由类型生成全局辅助工具。这些工具无需导入即可使用,并在 next devnext build 或通过 next typegen 期间生成:

示例

对 Next.js 配置文件进行类型检查

你可以在 Next.js 配置中使用 TypeScript 并导入类型,方法是使用 next.config.ts

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  /* 配置选项在这里 */
}
 
export default nextConfig

next.config.ts 中的模块解析目前仅限于 CommonJS。但是,当对 Node.js v22.10.0 及更高版本使用 Node.js 原生 TypeScript 解析器时,可以使用 ECMAScript Modules(ESM)语法。

当使用 next.config.js 文件时,你可以在 IDE 中使用 JSDoc 添加一些类型检查,如下所示:

next.config.js
// @ts-check
 
/** @type {import('next').NextConfig} */
const nextConfig = {
  /* 配置选项在这里 */
}
 
module.exports = nextConfig

next.config.ts 使用 Node.js 原生 TypeScript 解析器

注意:仅在 Node.js v22.10.0+ 上可用,且仅在启用该功能时。Next.js 不会启用它。

Next.js 通过 process.features.typescript 检测 Node.js 原生 TypeScript 解析器,该功能在 v22.10.0 中添加。当存在时,next.config.ts 可以使用原生 ESM,包括顶层 await 和动态 import()。此机制继承了 Node 解析器的功能和限制。

在 Node.js 版本 v22.18.0+ 中,process.features.typescript 默认启用。对于 v22.10.022.17.x 之间的版本,使用 NODE_OPTIONS=--experimental-transform-types 选择加入:

Terminal
NODE_OPTIONS=--experimental-transform-types next <command>

对于 CommonJS 项目(默认)

尽管 next.config.ts 在 CommonJS 项目上支持原生 ESM 语法,但 Node.js 默认仍会假定 next.config.ts 是 CommonJS 文件,导致 Node.js 在检测到模块语法时将文件重新解析为 ESM。因此,我们建议在 CommonJS 项目中使用 next.config.mts 文件,以明确表示它是 ESM 模块:

next.config.mts
import type { NextConfig } from 'next'
 
// 支持顶层 await 和动态 import
const flags = await import('./flags.js').then((m) => m.default ?? m)
 
const nextConfig: NextConfig = {
  /* 配置选项在这里 */
  typedRoutes: Boolean(flags?.typedRoutes),
}
 
export default nextConfig

对于 ESM 项目

package.json 中的 "type" 设置为 "module" 时,你的项目使用 ESM。在 Node.js 文档中了解更多关于此设置的信息。在这种情况下,你可以直接使用 ESM 语法编写 next.config.ts

值得注意的是:当在 package.json 中使用 "type": "module" 时,项目中的所有 .js.ts 文件默认都被视为 ESM 模块。如果需要,你可能需要将具有 CommonJS 语法的文件重命名为 .cjs.cts 扩展名。

静态类型链接

Next.js 可以静态地对链接进行类型检查,以防止在使用 next/link 时出现拼写错误和其他错误,从而提高在页面之间导航时的类型安全性。

在 Pages 和 App Router 中都适用于 next/linkhref 属性。在 App Router 中,它还对 next/navigation 方法(如 pushreplaceprefetch)进行类型检查。它不对 Pages Router 中的 next/router 方法进行类型检查。

字面量 href 字符串会被验证,而非字面量 href 可能需要使用 as Route 进行转换。

要选择加入此功能,需要启用 typedRoutes,并且项目需要使用 TypeScript。

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  typedRoutes: true,
}
 
export default nextConfig

Next.js 将在 .next/types 中生成一个链接定义,其中包含有关应用程序中所有现有路由的信息,TypeScript 可以使用它在编辑器中提供有关无效链接的反馈。

值得注意的是:如果你在没有使用 create-next-app 的情况下设置项目,请确保通过将 .next/types/**/*.ts 添加到 tsconfig.json 中的 include 数组来包含生成的 Next.js 类型:

tsconfig.json
{
  "include": [
    "next-env.d.ts",
    ".next/types/**/*.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": ["node_modules"]
}

目前,支持包括任何字符串字面量,包括动态段。对于非字面量字符串,你需要使用 as Route 手动转换。下面的示例展示了 next/linknext/navigation 的用法:

app/example-client.tsx
'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:字面量和动态 */}
      <Link href="/about" />
      <Link href={`/blog/${slug}`} />
      <Link href={('/blog/' + slug) as Route} />
      {/* 如果 href 不是有效路由,TypeScript 会报错 */}
      <Link href="/aboot" />
 
      {/* Router:字面量和动态字符串都会被验证 */}
      <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>
 
      {/* 对于非字面量字符串,转换为 Route */}
      <button onClick={() => router.push(('/blog/' + slug) as Route)}>
        Push Non-literal Blog
      </button>
    </>
  )
}

这同样适用于由代理定义的重定向路由:

proxy.ts
import { NextRequest, NextResponse } from 'next/server'
 
export function proxy(request: NextRequest) {
  if (request.nextUrl.pathname === '/proxy-redirect') {
    return NextResponse.redirect(new URL('/', request.url))
  }
 
  return NextResponse.next()
}
app/some/page.tsx
import type { Route } from 'next'
 
export default function Page() {
  return <Link href={'/proxy-redirect' as Route}>Link Text</Link>
}

要在包装 next/link 的自定义组件中接受 href,请使用泛型:

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>
  )
}

你还可以对简单的数据结构进行类型标注,并迭代以渲染链接:

components/nav-items.ts
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' },
]

然后,遍历这些项以渲染 Link

components/nav.tsx
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>
  )
}

它是如何工作的?

当运行 next devnext build 时,Next.js 会在 .next 中生成一个隐藏的 .d.ts 文件,其中包含有关应用程序中所有现有路由的信息(所有有效路由作为 Linkhref 类型)。此 .d.ts 文件包含在 tsconfig.json 中,TypeScript 编译器将检查该 .d.ts 并在编辑器中提供有关无效链接的反馈。

环境变量的类型智能提示

在开发过程中,Next.js 会在 .next/types 中生成一个 .d.ts 文件,其中包含有关为编辑器的智能提示加载的环境变量的信息。如果在多个文件中定义了相同的环境变量键,则会根据环境变量加载顺序进行去重。

要选择加入此功能,需要启用 experimental.typedEnv,并且项目需要使用 TypeScript。

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  experimental: {
    typedEnv: true,
  },
}
 
export default nextConfig

值得注意的是:类型是根据开发运行时加载的环境变量生成的,默认情况下不包括来自 .env.production* 文件的变量。要包含生产特定的变量,请使用 NODE_ENV=production 运行 next dev

使用异步服务器组件

要在 TypeScript 中使用 async 服务器组件,请确保你使用的是 TypeScript 5.1.3 或更高版本以及 @types/react 18.2.8 或更高版本。

如果你使用的是旧版本的 TypeScript,你可能会看到 'Promise<Element>' is not a valid JSX element 类型错误。更新到最新版本的 TypeScript 和 @types/react 应该可以解决此问题。

增量类型检查

v10.2.1 开始,Next.js 支持在 tsconfig.json 中启用增量类型检查,这可以帮助加快大型应用程序中的类型检查速度。

自定义 tsconfig 路径

在某些情况下,你可能希望为构建或工具使用不同的 TypeScript 配置。为此,请在 next.config.ts 中设置 typescript.tsconfigPath,以将 Next.js 指向另一个 tsconfig 文件。

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  typescript: {
    tsconfigPath: 'tsconfig.build.json',
  },
}
 
export default nextConfig

例如,为生产构建切换到不同的配置:

next.config.ts
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
为什么你可能需要为构建使用单独的 tsconfig

在某些场景中,你可能需要放宽检查,例如在 monorepos 中,构建还会验证不符合项目标准的共享依赖项,或者在 CI 中放宽检查以继续交付,同时在本地迁移到更严格的 TypeScript 设置(并且仍然希望 IDE 突出显示误用)。

例如,如果你的项目使用 useUnknownInCatchVariables,但某些 monorepo 依赖项仍然假定 any

tsconfig.build.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "useUnknownInCatchVariables": false
  }
}

这样可以通过 tsconfig.json 保持编辑器的严格性,同时允许生产构建使用宽松的设置。

值得注意的是

  • IDE 通常读取 tsconfig.json 进行诊断和智能提示,因此你仍然可以看到 IDE 警告,而生产构建使用替代配置。如果你希望在编辑器中保持一致性,请镜像关键选项。
  • 在开发中,只会监视 tsconfig.json 的更改。如果你通过 typescript.tsconfigPath 编辑不同的文件名,请重新启动开发服务器以应用更改。
  • 配置的文件用于 next devnext buildnext typegen

在生产中禁用 TypeScript 错误

当项目中存在 TypeScript 错误时,Next.js 会导致你的生产构建next build)失败。

如果你希望 Next.js 即使应用程序存在错误也能危险地生成生产代码,可以禁用内置的类型检查步骤。

如果禁用,请确保你在构建或部署过程中运行类型检查,否则这可能非常危险。

打开 next.config.ts 并在 typescript 配置中启用 ignoreBuildErrors 选项:

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  typescript: {
    // !! 警告 !!
    // 危险地允许生产构建成功完成,即使
    // 你的项目有类型错误。
    // !! 警告 !!
    ignoreBuildErrors: true,
  },
}
 
export default nextConfig

值得注意的是:你可以在构建之前运行 tsc --noEmit 来自己检查 TypeScript 错误。这对于你希望在部署之前检查 TypeScript 错误的 CI/CD 管道很有用。

自定义类型声明

当你需要声明自定义类型时,你可能会想修改 next-env.d.ts。但是,此文件是自动生成的,因此你所做的任何更改都将被覆盖。相反,你应该创建一个新文件,我们称之为 new-types.d.ts,并在 tsconfig.json 中引用它:

tsconfig.json
{
  "compilerOptions": {
    "skipLibCheck": true
    //...截断...
  },
  "include": [
    "new-types.d.ts",
    "next-env.d.ts",
    ".next/types/**/*.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": ["node_modules"]
}

版本变更

版本变更
v15.0.0为 TypeScript 项目添加了 next.config.ts 支持。
v13.2.0静态类型链接在 beta 版中可用。
v12.0.0SWC 现在默认用于编译 TypeScript 和 TSX 以实现更快的构建。
v10.2.1tsconfig.json 中启用时,添加了增量类型检查支持。