路由
NocoBase 3 的路由分为服务端路由和客户端路由:
- 服务端路由处理 HTTP 请求,使用 Hono 编写。
- 客户端路由加载 React 页面,并决定页面是否出现在导航中。
路由声明只写应用内部路径。部署前缀由运行时统一处理,不要写进 path。
五种路由
服务端路由的路径由声明函数决定:defineRootRoutes() 保留你在 router 中写的路径,defineApiRoutes() 会自动在前面加上 /api。例如,defineRootRoutes() 中的 /callbacks/payment 最终是 /callbacks/payment,defineApiRoutes() 中的 /orders 最终是 /api/orders。客户端设置页和开发页分别自动加上 /settings 和 /dev 前缀,普通客户端页面不增加额外前缀。
例如,应用部署在 /main 下时:
服务端路由
服务端路由文件放在 server/routes/,并从 server/routes/index.ts 导出。每个路由工厂都要创建并返回自己的 Hono router:
每个 /api 路由都用 describeRoute() 声明自己,进入应用的 OpenAPI 文档;有输入的路由用 apiValidator() 校验并声明输入。两者都从 @nocobase/app-server/router 导入。运行中的应用在 <APP_BASE_PATH>/api/swagger/docs 提供 Swagger UI,在 <APP_BASE_PATH>/api/swagger 提供 JSON,登录用户或带 API Key 的请求才能读取。
在 server/routes/index.ts 中导出路由贡献:
defineRootRoutes() 的写法相同,只是路由会挂在应用根路径。例如,Webhook 可以声明为 router.post('/callbacks/payment', ...)。
需要登录的接口在自己的 Hono router 中使用 auth.required();允许匿名访问但需要读取会话时使用 auth.optional()。详细用法见 服务端路由。
客户端路由
客户端路由文件是 client/routes.ts。普通页面使用 defineAppRoutes():
componentLoader 必须返回一个包含 default export 的页面模块:
保持 componentLoader 惰性加载。路由元数据会同步注册,页面组件只在用户访问时加载。即使源文件是 .tsx,import 路径也要写 .js。
路径和子路由
页面的 path 必须填写,子路由的路径会拼接到父路由后面:
上例中的详情页路径是 /orders/:orderId。动态参数页不能配置 navigation,因为它没有一个固定的菜单链接。页面组件需要在希望显示子页面的位置显式渲染 <Outlet />。
路由分组没有 componentLoader,但必须有 navigation 和 children。分组可以设置 path 作为子页面的路径前缀;不设置时只负责组织菜单:
上例中的页面路径是 /business/orders。分组本身不渲染页面组件。
面包屑声明
页面和分组都可以声明 breadcrumb: { title: 'orders.title' }。title 是非空字符串,在路由所属包的语言命名空间中解析。
breadcrumb 与 navigation 独立,不会互相回退。动态参数路径可以声明 breadcrumb,但不能声明需要固定链接的 navigation。省略 breadcrumb 的路由不会出现在面包屑中。
声明本身不渲染界面;页面需要放置 <Breadcrumbs />。显示条件、子页面展示和完整示例见页面和菜单。
页面、菜单和访问控制
客户端页面的 navigation、breadcrumb、auth、authz、路由分组和子路由用法见 页面和菜单。
设置页
设置页使用 defineSettingsRoutes(),声明的 path 不要重复写 /settings:
authz 的值是 'skip'、'unrestricted' 或一个 { resource: { type, id }, action } 对象,用来指定要检查的资源和操作;系统不会根据路由名称推断。在每条路径的第一个页面上声明它:子页面省略时继承最近的上级页面的值(可以跨越分组和多个层级),子页面自己声明的值会覆盖继承值。第一个页面省略 authz 时仍会注册,并在开发环境输出一条警告,说明路由、路径和采用的默认值:需要登录的应用页面(auth: 'required')和设置页默认为 'unrestricted',只有 root 等拥有无限制权限的身份可以打开;guest、optional 应用页面和开发页默认为 'skip'。'unrestricted' 也可以显式声明,用于只允许 root 打开的页面,它不会出现在权限配置中。例如,{ resource: { type: 'settings', id: 'orders' }, action: 'read' } 表示检查当前用户是否有读取订单设置页的权限。客户端会在加载页面组件前执行这项检查;authz: 'skip' 仅跳过当前页面的权限检查,不跳过登录和父级检查。
检查被拒绝时,页面不会出现在设置导航中,直接访问它的 URL 也不会加载页面组件。请为设置页声明它所属的设置项,或者对所有登录用户都可打开的页面显式声明 'skip';省略时设置页只有 root 可以打开。authz 只控制客户端页面,页面调用的服务端接口仍需自行完成认证和权限校验。
开发页
开发页使用 defineDevRoutes(),声明的 path 不要重复写 /dev:
开发页在生产环境中会被移除,也不会被包含进构建产物。
开发页同样应当声明 authz,可以是 'skip' 或 { resource: { type, id }, action };省略时默认为 'skip'。声明权限请求时,客户端会在开发环境中加载页面前检查这项权限;没有权限时,页面不会出现在开发导航中,直接访问 URL 也不会加载组件。
路由之间不会自动配对
客户端和服务端路由不会因为名称或路径相似而自动配对:
- 客户端页面调用
/api/orders时,需要服务端通过defineApiRoutes()提供对应接口 - 客户端的
auth不会自动为服务端接口添加认证 - 服务端接口必须在自己的 Hono router 中声明 authentication 和 authorization

