跳到内容

TypeScript

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

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

须知:如果你已经有一个 jsconfig.json 文件,请将旧 jsconfig.json 中的 paths 编译器选项复制到新的 tsconfig.json 文件中,并删除旧 jsconfig.json 文件。

示例

检查 Next.js 配置文件类型

你可以通过使用 next.config.ts 在 Next.js 配置中使用 TypeScript 并导入类型。

next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  /* config options here */
}
 
export default nextConfig

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

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

next.config.js
// @ts-check
 
/** @type {import('next').NextConfig} */
const nextConfig = {
  /* config options here */
}
 
module.exports = nextConfig

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

注意:仅适用于 Node.js v22.10.0+ 并且仅当该功能启用时。Next.js 不会启用它。

Next.js 通过在 v22.10.0 中添加的 process.features.typescript 检测 Node.js 原生 TypeScript 解析器。当存在时,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 选择启用。

终端
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'
 
// Top-level await and dynamic import are supported
const flags = await import('./flags.js').then((m) => m.default ?? m)
 
const nextConfig: NextConfig = {
  /* config options here */
  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 路由中 next/linkhref 属性。在 App 路由中,它还对 next/navigation 方法(如 pushreplaceprefetch)进行类型检查。它不对 Pages 路由中的 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.jsoninclude 数组中来确保包含生成的 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: 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>
    </>
  )
}

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

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

静态生成和服务器端渲染

对于 getStaticPropsgetStaticPathsgetServerSideProps,你可以分别使用 GetStaticPropsGetStaticPathsGetServerSideProps 类型。

pages/blog/[slug].tsx
import type { GetStaticProps, GetStaticPaths, GetServerSideProps } from 'next'
 
export const getStaticProps = (async (context) => {
  // ...
}) satisfies GetStaticProps
 
export const getStaticPaths = (async () => {
  // ...
}) satisfies GetStaticPaths
 
export const getServerSideProps = (async (context) => {
  // ...
}) satisfies GetServerSideProps

须知: satisfies 是在 TypeScript 4.9 中添加的。我们建议升级到最新版本的 TypeScript。

与 API 路由一起使用

以下是如何为 API 路由使用内置类型的示例

pages/api/hello.ts
import type { NextApiRequest, NextApiResponse } from 'next'
 
export default function handler(req: NextApiRequest, res: NextApiResponse) {
  res.status(200).json({ name: 'John Doe' })
}

你也可以对响应数据进行类型检查

pages/api/hello.ts
import type { NextApiRequest, NextApiResponse } from 'next'
 
type Data = {
  name: string
}
 
export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  res.status(200).json({ name: 'John Doe' })
}

与自定义 App 一起使用

如果你有 自定义 App,你可以使用内置类型 AppProps 并将文件名更改为 ./pages/_app.tsx,如下所示:

import type { AppProps } from 'next/app'
 
export default function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}

增量类型检查

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: {
    // !! WARN !!
    // Dangerously allow production builds to successfully complete even if
    // your project has type errors.
    // !! WARN !!
    ignoreBuildErrors: true,
  },
}
 
export default nextConfig

须知:你可以运行 tsc --noEmit 来在构建之前自行检查 TypeScript 错误。这对于 CI/CD 管道很有用,你可以在部署之前检查 TypeScript 错误。

自定义类型声明

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

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

版本变更

版本更改
v15.0.0next.config.ts 支持已添加到 TypeScript 项目。
v13.2.0静态类型链接处于 Beta 阶段。
v12.0.0SWC 现已默认用于编译 TypeScript 和 TSX,以实现更快的构建。
v10.2.1当在你的 tsconfig.json 中启用时,已添加 增量类型检查 支持。