路由

Nitro 支持文件系统路由,可自动将文件映射到路由。路由会在构建时进行编译和代码分割,因此无需运行时路由器。

#路由处理器

路由处理器是一个接收 event 对象(H3Event)并返回响应的函数。

import type { H3Event } from "nitro";

export default (event: H3Event) => {
  return "world";
}

#文件系统路由

文件会自动映射到 h3 路由。定义路由非常简单,只需在 serverDir 的 api/ 或 routes/ 目录中创建文件。

Note

文件系统路由要求在配置中设置 serverDir(或 scanDirs);默认情况下不会扫描任何目录。

每个文件定义一个处理器,你可以在文件名后追加 HTTP 方法,以匹配特定的请求方法。

routes/
  api/
    test.ts      <-- /api/test
  hello.get.ts   <-- /hello (仅 GET)
  hello.post.ts  <-- /hello (仅 POST)
vite.config.ts

你可以通过创建子目录来嵌套路由。

routes/
  api/
    [org]/
      [repo]/
        index.ts   <-- /api/:org/:repo
        issues.ts  <-- /api/:org/:repo/issues
      index.ts     <-- /api/:org
package.json

#路由组

要组织相关的路由文件而不影响最终生成的 URL,可以将它们放在用括号 ( 和 ) 包裹的文件夹中。

例如:

routes/
  api/
    (admin)/
      users.ts   <-- /api/users
      reports.ts <-- /api/reports
    (public)/
      index.ts   <-- /api
package.json

[!NOTE] 路由组不会成为路由路径的一部分,仅用于组织文件。

#静态路由

在 routes/ 或 routes/api/ 目录中创建文件。文件路径会成为路由路径。然后导出一个路由处理器:

routes/api/test.ts
import { defineHandler } from "nitro";

export default defineHandler(() => {
  return { hello: "API" };
});

#动态路由

#单参数

要定义带参数的路由,请使用 [<param>] 语法,其中 <param> 是参数名称。参数可以通过 event.context.params 对象访问。

routes/hello/[name\
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  const { name } = event.context.params;

  return `Hello ${name}!`;
});

调用 /hello/nitro 会返回:

响应
Hello nitro!

#多参数

你可以使用 [<param1>]/[<param2>] 语法在路由中定义多个参数,其中每个参数都是一个文件夹。不能在单个文件名或文件夹中定义多个参数。

routes/hello/[name\
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  const { name, age } = event.context.params;

  return `Hello ${name}! You are ${age} years old.`;
});

#捕获所有参数

你可以使用 [...<param>] 语法捕获 URL 的所有剩余部分。这将把 / 包含在参数中。

routes/hello/[...name\
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  const { name } = event.context.params;

  return `Hello ${name}!`;
});

调用 /hello/nitro/is/hot 会返回:

响应
Hello nitro/is/hot!

#特定请求方法

你可以在文件名后追加 HTTP 方法来强制路由仅匹配特定的 HTTP 请求方法,例如 hello.get.ts 将只匹配 GET 请求。你可以使用任何你想要的 HTTP 方法。

支持的方法:get、post、put、delete、patch、head、options、query、connect、trace。

Note

query 匹配 QUERY 方法,这是一种携带请求体的安全且幂等的 GET 替代方法。在代理、CDN 和部署平台中的支持仍不一致,因此对于公共 API,请保留 GET 或 POST 回退方案。

// routes/users/[id].get.ts
import { defineHandler } from "nitro";

export default defineHandler(async (event) => {
  const { id } = event.context.params;

  // 对 id 进行一些操作

  return `User profile!`;
});

#捕获所有路由

你可以创建一个特殊路由,用于匹配未被其他路由处理的所有请求,适合作为默认路由或回退路由。

要创建捕获所有路由,请创建一个名为 [...].ts 的文件。

routes/[...\
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  return `Hello ${event.url.pathname}!`;
});

Note

对于未命名的捕获所有路由([...].ts),匹配到的通配符片段可以通过 event.context.params._ 访问。

服务器入口和渲染器也会作为捕获所有处理器,并在基于文件的捕获所有路由之后串联执行。

#特定环境的处理器

你可以在文件名中添加 .dev、.prod 或 .prerender 后缀,使路由仅包含在特定构建中,例如:routes/test.get.dev.ts 或 routes/test.get.prod.ts。

该后缀放在方法后缀之后(如果有的话):

routes/
  env/
    index.dev.ts       <-- /env (仅开发环境)
    index.get.prod.ts  <-- /env (GET, 仅生产环境)

Tip

要针对多个环境,或使用预设名称作为环境,请通过 routes 配置以编程方式注册路由。

#忽略文件

你可以使用 ignore 配置选项来排除文件不被路由扫描。它接受相对于服务器目录的 glob 模式数组。

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  ignore: [
    "routes/api/**/_*",   // 忽略 api/ 中以 _ 开头的文件
    "middleware/_*.ts",    // 忽略以 _ 开头的中间件
    "routes/_*.ts",       // 忽略根路由中以 _ 开头的文件
  ],
});

#路由元数据

你可以在路由处理器文件中使用 defineRouteMeta 宏,在构建时定义路由元数据。

Important

此功能目前处于实验阶段。

routes/api/test.ts
import { defineRouteMeta } from "nitro";
import { defineHandler } from "nitro";

defineRouteMeta({
  openAPI: {
    tags: ["test"],
    description: "Test route description",
    parameters: [{ in: "query", name: "test", required: true }],
  },
});

export default defineHandler(() => "OK");
Read more in swagger.io/specification/v3/.

#程序化路由处理器

除了文件系统路由外,你还可以使用 routes 配置选项以程序化方式注册路由处理器。

#routes 配置

routes 选项允许你将路由模式映射到处理器:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routes: {
    "/api/hello": "./server/routes/api/hello.ts",
    "/api/custom": {
      handler: "./server/routes/api/hello.ts",
      method: "POST",
      lazy: true,
    },
    "/virtual": {
      handler: "#virtual-route",
    },
  },
});

每个路由条目可以是一个简单的字符串(处理器路径)或一个具有以下选项的对象:

选项类型描述
handlerstring事件处理器文件或虚拟模块 ID 的路径
methodstring要匹配的 HTTP 方法(get、post 等)
lazyboolean使用懒加载导入处理器
format"web" | "node"处理器类型。"node" 处理器会被转换为兼容 web 的格式
envstring | string[]包含此处理器的环境("dev"、"prod"、"prerender" 或预设名称)

#handlers 配置

handlers 数组适用于注册具有路由匹配控制权的中间件:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  handlers: [
    {
      route: "/api/**",
      handler: "./server/utils/api-auth.ts",
      middleware: true,
    },
  ],
});

每个处理器条目支持以下选项:

选项类型描述
routestringHTTP 路径名模式(例如 /test、/api/:id、/blog/**)
handlerstring事件处理器文件或虚拟模块 ID 的路径
methodstring要匹配的 HTTP 方法(get、post 等)
middlewareboolean在路由处理器之前作为中间件运行处理器
lazyboolean使用懒加载导入处理器
format"web" | "node"处理器类型。"node" 处理器会被转换为兼容 web 的格式
envstring | string[]包含此处理器的环境("dev"、"prod"、"prerender" 或预设名称)

#中间件

中间件在路由处理器之前运行,让你可以检查或扩展传入的请求。有关中间件在请求管道中的位置,请参阅生命周期。

Tip

中间件可以在请求被处理之前修改请求,但不能在请求处理之后修改。

中间件会从 middleware/ 目录自动注册。

middleware/
  auth.ts
  logger.ts
  ...
routes/
  hello.ts

#简单中间件

中间件的定义方式与路由处理器完全相同,但有一个例外:它们不应返回任何内容。

middleware/auth.ts
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  // Extend or modify the event
  event.context.user = { name: "Nitro" };
});

middleware/ 目录中的中间件会自动为所有路由注册。要为特定路由模式注册中间件,请参阅路由作用域中间件。

Warning

从中间件返回值会关闭请求:该值会成为响应,并且不会继续运行后续处理器。请避免从中间件返回内容;应改用路由处理器。

#执行顺序

中间件按目录列表顺序执行。

middleware/
  auth.ts <-- 第一个
  logger.ts <-- 第二个
  ... <-- 第三个

在中间件文件名前添加数字以控制它们的执行顺序。

middleware/
  1.logger.ts <-- 第一个
  2.auth.ts <-- 第二个
  3.... <-- 第三个

Note

文件名会按字符串排序,因此 10.filename.ts 会紧跟在 1.filename.ts 之后,并排在 2.filename.ts 之前。如果同一目录中有超过 10 个中间件,请在 1-9 前加上 0(例如 01.filename.ts)。

#请求过滤

全局中间件会在每个请求上运行,但你可以添加自定义条件来限定其作用范围。例如,检查 URL,以便将中间件应用于特定路由:

middleware/auth.ts
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  // 仅对 /auth 路由执行
  if (event.url.pathname.startsWith('/auth')) {
    event.context.user = { name: "Nitro" };
  }
});

#路由作用域中间件

你可以使用 handlers 配置并设置 middleware 选项和特定的 route 来为特定路由模式注册中间件:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  handlers: [
    {
      route: "/api/**",
      handler: "./server/utils/api-auth.ts",
      middleware: true,
    },
  ],
});

与全局中间件(从 middleware/ 目录自动注册,并匹配 /**)不同,路由作用域中间件只会对匹配指定模式的请求运行。

请将路由作用域处理器文件放在 middleware/ 目录之外(如上例所示),这样目录扫描不会同时将它们注册为全局中间件。

#错误处理

你可以使用 H3 中提供的工具来处理路由处理器和中间件中的错误。

错误发送回客户端的方式取决于环境。在开发环境中,Accept 标头为 text/html 的请求(例如浏览器请求)会收到 HTML 错误页面。在生产环境中,错误始终以 JSON 形式发送。此行为可能会受到请求属性的影响,例如 Accept 或 User-Agent 标头。

要使用 error hook 全局捕获错误,请参阅生命周期。

#代码分割

Nitro 为每个路由处理程序创建单独的代码块。代码块在首次请求时按需加载,因此 /api/users 不会加载 /api/posts 的代码。

查看 inlineDynamicImports 以将所有内容打包到单个文件中。

#路由规则

路由规则允许你直接从配置中将行为(重定向、代理、缓存、身份验证、自定义标头)附加到路由模式,而无需编写处理器代码。

每条规则都会将路由模式(使用 rou3 语法)映射到一组选项,这些选项基于 h3 路由规则。设置 cache 选项后,匹配的处理器会自动使用 defineCachedHandler 包装。请参阅缓存指南了解更多信息。

使用 routeRules 配置选项设置路由规则:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/blog/**': { swr: true },
    '/blog2/**': { swr: 600 },
    '/blog3/**': { static: true },
    '/blog4/**': { cache: { /* 缓存选项 */ } },
    '/assets/**': { headers: { 'cache-control': 's-maxage=0' } },
    '/api/v1/**': { cors: true, headers: { 'access-control-allow-methods': 'GET' } },
    '/old-page': { redirect: '/new-page' },
    '/old-page/**': { redirect: '/new-page/**' },
    '/proxy/example': { proxy: 'https://example.com' },
    '/proxy/**': { proxy: '/api/**' },
  }
});

#规则合并与覆盖

路由规则按照从最不具体到最具体的顺序匹配。当多个规则匹配一个请求时,它们的选项会合并,更具体的规则优先。

你可以使用 false 来禁用由更通用模式设置的规则:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/api/cached/**': { swr: true },
    '/api/cached/no-cache': { cache: false, swr: false },
    '/api/**': { cors: true },
    '/api/internal/**': { cors: false },
  }
});

#按方法限定的规则

在规则键前加上大写的 HTTP 方法(后跟一个空格),即可将规则限定为使用该方法的请求。不带方法前缀的键适用于所有方法。按方法限定的规则会合并到匹配相同路径的、与方法无关的规则之上,因此你可以在共享默认配置的基础上叠加特定于方法的行为:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    // 应用于所有方法
    '/api/**': { headers: { 'x-api': 'true' } },
    // Only POST requests to /api/** get this header
    'POST /api/**': { headers: { 'x-write': 'true' } },
    // Cache GET requests to /feed only
    'GET /feed': { swr: 600 },
  }
});

Note

方法匹配由服务器运行时针对每个请求进行解析。平台原生的静态生成(例如 Netlify/Cloudflare 的 _headers 和 _redirects、Vercel 的 config.json)不会按方法进行拆分,因此对于希望平台写入其静态配置的 headers/redirect/proxy 规则,建议使用与方法无关的键。

#Headers

为匹配的路由设置自定义响应头:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/api/**': { headers: { 'cache-control': 's-maxage=60' } },
    '**': { headers: { 'x-powered-by': 'Nitro' } },
  }
});

#跨域资源共享(CORS)

在运行时使用 cors 规则处理 CORS。cors: true 会应用宽松的默认设置(来源、方法和允许的请求头均为 *):简单请求会获得 access-control-allow-origin: * 和 access-control-expose-headers: *,而 OPTIONS 预检请求会被直接响应(状态码为 204),并返回匹配的 access-control-allow-* 请求头。

Note

CORS 由正在运行的服务器(h3 的 handleCors)应用,而不是由静态/CDN 配置应用。 在可以直接从边缘节点提供预渲染/静态资源的平台上,只有当请求到达服务器处理程序时,才会添加 CORS 请求头。

传入对象可以进行更精细的控制:来源允许列表、credentials、maxAge 等(h3 CorsOptions)。将 credentials: true 与通配符来源结合使用是无效的,并会在构建时抛出错误:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    // 宽松的默认设置
    '/api/public/**': { cors: true },
    // 限制为特定来源并启用凭据
    '/api/v1/**': {
      cors: { origin: ['https://app.example.com'], credentials: true },
    },
  }
});

#重定向

将匹配的路由重定向到另一个 URL。使用字符串进行简单重定向(默认为 307 状态码),或使用对象进行更精细的控制:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    // 简单重定向(307 状态码)
    '/old-page': { redirect: '/new-page' },
    // 自定义状态码的重定向
    '/legacy': { redirect: { to: 'https://example.com/', status: 308 } },
    // Wildcard redirect, preserves the path after the pattern
    '/old-blog/**': { redirect: 'https://blog.example.com/**' },
  }
});

#代理

将请求代理到另一个 URL。支持内部和外部目标:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    // 代理到确切 URL
    '/api/proxy/example': { proxy: 'https://example.com' },
    // 代理到内部路由
    '/api/proxy/**': { proxy: '/api/echo' },
    // Wildcard proxy, preserves the path after the pattern
    '/cdn/**': { proxy: 'https://cdn.jsdelivr.net/**' },
    // 带选项的代理
    '/external/**': {
      proxy: {
        to: 'https://api.example.com/**',
        // 其他 H3 代理选项...
      },
    },
  }
});

#身份验证

没有 auth 路由规则:身份验证需要可执行逻辑,因此应放在中间件中。对于单个路由,请使用 h3 的 basicAuth 中间件:

routes/admin/index.ts
import { defineHandler } from "nitro";
import { basicAuth } from "nitro/h3";

export default defineHandler({
  middleware: [basicAuth({ username: 'admin', password: 'supersecret', realm: 'Admin Area' })],
  handler: (event) => `Hello, ${event.context.basicAuth?.username}!`,
});

对于整个子树,可以将其注册为路由作用域中间件,或注册为 middleware/ 目录中的全局中间件。

#缓存(SWR / 静态)

使用 cache、swr 或 static 选项控制缓存行为:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    // 启用 stale-while-revalidate 缓存
    '/blog/**': { swr: true },
    // 带 maxAge(秒)的 SWR
    '/blog/posts/**': { swr: 600 },
    // 完整缓存选项
    '/api/data/**': {
      cache: {
        maxAge: 60,
        swr: true,
        // ...其他缓存选项
      },
    },
    // 禁用缓存
    '/api/realtime/**': { cache: false },
  }
});

Tip

swr: true 是 cache: { swr: true } 的简写,swr: <number> 是 cache: { swr: true, maxAge: <number> } 的简写。

#预渲染

标记路由以在构建时进行预渲染:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/about': { prerender: true },
    '/dynamic/**': { prerender: false },
  }
});

预渲染路由会在构建时生成一次,并作为静态文件提供。它们不会在运行时重新验证。

Warning

不要在同一路由上同时使用 prerender 和 isr。预渲染具有更高优先级,因此 isr 会被忽略,页面也不会在部署后重新生成。在支持的平台上需要增量再生时,请单独使用 isr。

#ISR(Vercel)

为 Vercel 部署配置增量静态再生:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/isr/**': { isr: true },
    '/isr-ttl/**': { isr: 60 },
    '/isr-custom/**': {
      isr: {
        expiration: 60,
        allowQuery: ['q'],
        group: 1,
      },
    },
  }
});

Note

ISR 会按需生成页面,并根据你设置的过期时间重新验证页面。它是构建时 prerender 的替代方案,而不是可以在同一路由上与其结合使用的功能。

#路由规则参考

选项类型描述
headersRecord<string, string>自定义响应标头
redirectstring | { to: string, status?: number }重定向到另一个 URL(默认状态码:307)
proxystring | { to: string, ...proxyOptions }将请求代理到另一个 URL
corsboolean | CorsOptions通过 h3 的 handleCors 处理 CORS(true = 宽松设置)
cacheobject | false缓存选项(请参阅缓存指南)
swrboolean | numbercache: { swr: true, maxAge: number } 的简写(false 会重置继承的缓存规则)
staticboolean | number静态缓存的简写
prerenderboolean启用/禁用预渲染
isrboolean | number | object增量静态再生(Vercel)

#运行时路由规则

路由规则可以通过 runtimeConfig 提供,允许通过环境变量覆盖而无需重新构建:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  runtimeConfig: {
    nitro: {
      routeRules: {
        '/api/**': { headers: { 'x-env': 'production' } },
      },
    },
  },
});

#配置参考

这些配置选项控制路由行为:

选项类型默认值描述
baseURLstring"/"所有路由的基础 URL
apiBaseURLstring"/api"api/ 目录中路由的基础 URL
apiDirstring"api"API 路由的目录名称
routesDirstring"routes"基于文件的路由的目录名称
serverDirstring | falsefalse用于扫描路由、中间件、插件等的服务器目录
scanDirsstring[][]扫描路由的额外目录
routesRecord<string, string | handler>{}路由到处理程序的映射
handlersNitroEventHandler[][]程序化处理程序注册(主要用于中间件)
routeRulesRecord<string, NitroRouteConfig>{}匹配模式的路由规则
ignorestring[][]文件扫描期间忽略的 glob 模式