默认情况下,Modern.js 推荐使用 约定式路由 作为路由定义的方式。同时,Modern.js 也提供了一个配置式路由的能力,其可以和约定式路由一起使用,或两者分别单独使用。
配置式路由在以下场景中特别有用:
在 src 或每个入口目录下,你可以定义一个 modern.routes.[tj]s 文件,通过该文件,你可以对路由进行配置:
import { defineRoutes } from '@modern-js/runtime/config-routes'
export default defineRoutes(({ route, layout, page, $ }, fileRoutes) => {
return [
route("home.tsx", "/"),
]
})defineRoutes 接受一个回调函数,该回调函数有两个参数:
routeFunctions: 包含 route、layout、page、$ 函数的对象fileRoutes: 约定式路由生成的路由配置数组路由函数的基本签名:
modern.routes.[tj]s 的文件路径Modern.js 提供了四个主要的路由配置函数:
所有路由函数的第一个参数(路径)都是相对路径,会与父级路径拼接生成最终的路由路径:
"/" 或 "" 表示当前层级的根路径:param 语法表示动态参数"*" 语法匹配所有路径route、layout、page、$ 等函数的第一个参数(组件文件路径)必须指向当前项目中的真实文件,暂不支持 node_modules 及 Monorepo 下其他仓库中的文件@/pages/...、~/pages/... 等);请使用相对于 modern.routes.[tj]s 的相对路径。route 函数route 函数是通用的路由配置函数,会根据是否有子路由自动决定生成页面路由还是布局路由,可以代替 layout, page, $等函数。
export default defineRoutes(({ route }, fileRoutes) => {
return [
// 无子路由时,自动生成页面路由
route("home.tsx", "/"),
route("about.tsx", "about"),
// 有子路由时,自动生成布局路由
// dashboard/layout.tsx 需要包含 <Outlet> 来渲染子路由
route("dashboard/layout.tsx", "dashboard", [
route("dashboard/overview.tsx", "overview"), // 生成路径:/dashboard/overview
route("dashboard/settings.tsx", "settings"), // 生成路径:/dashboard/settings
]),
// 动态路由
route("products/detail.tsx", "products/:id"),
// 多路径指向同一组件
route("user/profile.tsx", "user"),
route("user/profile.tsx", "profile"),
]
})使用场景:
layout 函数layout 函数专门用于配置布局路由,始终生成布局组件,必须包含子路由:
export default defineRoutes(({ layout, page }, fileRoutes) => {
return [
// 生成布局路由,路径为 "/auth",必须包含子路由
layout("auth/layout.tsx", "auth", [
page("auth/login/page.tsx", "login"), // 生成路径:/auth/login
page("auth/register/page.tsx", "register"), // 生成路径:/auth/register
]),
]
})使用场景:
page 函数page 函数专门用于配置页面路由,始终生成页面组件:
export default defineRoutes(({ layout, page }, fileRoutes) => {
return [
layout("dashboard/layout.tsx", "dashboard", [
page("dashboard/overview.tsx", "overview"), // 生成路径:/dashboard/overview
page("dashboard/settings.tsx", "settings"), // 生成路径:/dashboard/settings
]),
]
})使用场景:
<Outlet> 子组件渲染$ 函数$ 函数专门用于配置通配路由,用于处理未匹配的路由:
export default defineRoutes(({ layout, page, $ }, fileRoutes) => {
return [
layout("blog/layout.tsx", "blog", [
page("blog/page.tsx", ""), // 生成路径:/blog
page("blog/[id]/page.tsx", ":id"), // 生成路径:/blog/:id
$("blog/$.tsx", "*"), // 通配路由,匹配 /blog 下的所有未匹配路径
]),
]
})使用场景:
$ 函数与约定式路由中的 $.tsx 文件功能相同,用于捕获未匹配的路由请求。
export default defineRoutes(({ page }, fileRoutes) => {
return [
// 使用 page 函数明确指定页面路由
page("home.tsx", "/"),
page("about.tsx", "about"),
page("contact.tsx", "contact"),
]
})当不指定路径时,路由会继承父级路径:
export default defineRoutes(({ layout, page }, fileRoutes) => {
return [
// 使用 layout 函数明确指定布局路由
// auth/layout.tsx 需要包含 <Outlet> 来渲染子路由
layout("auth/layout.tsx", [
page("login/page.tsx", "login"),
page("register/page.tsx", "register"),
]),
]
})上述配置会生成:
/login → auth/layout.tsx + login/page.tsx/register → auth/layout.tsx + register/page.tsxexport default defineRoutes(({ page }, fileRoutes) => {
return [
page("user.tsx", "user"),
page("user.tsx", "profile"),
page("user.tsx", "account"),
]
})配置式路由支持动态路由参数:
export default defineRoutes(({ page }, fileRoutes) => {
return [
// 必需参数
page("blog/detail.tsx", "blog/:id"),
// 可选参数
page("blog/index.tsx", "blog/:slug?"),
]
})配置式路由会自动查找组件的配套文件,无需手动配置。对于 modern.routes.[tj]s 中指定的任意组件文件,Modern.js 会自动查找以下配套文件:
// modern.routes.[tj]s
export default defineRoutes(({ route }, fileRoutes) => {
return [
route("pages/profile.tsx", "profile"),
route("pages/user/detail.tsx", "user/:id"),
]
})Modern.js 会自动查找并加载:
pages/profile.data.ts → 数据加载器pages/profile.config.ts → 路由配置pages/profile.error.tsx → 错误边界pages/profile.loading.tsx → Loading 组件更多关于数据获取的详细信息,请参考 数据获取 文档。关于 Loading 组件的使用,请参考 约定式路由 - Loading。
配置式路由可以与约定式路由一起使用,你可以通过修改 fileRoutes 参数来实现路由的合并:
当前路由结构可以通过 modern routes 命令查看
以下示例展示了配置式路由与约定式路由的合并方式:
// modern.routes.ts
import { defineRoutes } from '@modern-js/runtime/config-routes';
export default defineRoutes(({ page }, fileRoutes) => {
// 场景1:覆盖约定式路由
// 移除原有的 shop 路由,替换为自定义组件
const shopPageIndex = fileRoutes[0].children?.findIndex(
route => route.id === 'three_shop/page',
);
fileRoutes[0].children?.splice(shopPageIndex!, 1);
fileRoutes[0].children?.push(page('routes/CustomShop.tsx', 'shop'));
// 场景2:补充约定式路由
// 添加配置式路由,无约定式路由对应
fileRoutes[0].children?.push(page('routes/Settings.tsx', 'settings'));
// 场景3:混合嵌套路由
// 在约定式布局路由下添加配置式子路由
const userRoute = fileRoutes[0].children?.find(
(route: any) => route.path === 'user',
);
if (userRoute?.children) {
userRoute.children.push(page('routes/user/CustomTab.tsx', 'custom-tab'));
}
// 场景4:自动发现配套文件
// Product.tsx 会自动发现 Product.data.ts 和 Product.error.tsx
fileRoutes[0].children?.push(page('routes/Product.tsx', 'product/:id'));
return fileRoutes;
});由于路由合并后的最终结构可能不够直观,Modern.js 提供了调试命令来帮助你查看完整路由信息:
# 生成路由结构分析报告
npx modern routes该命令会在 dist/routes-inspect.json 文件中生成最终的路由结构,帮助你了解合并后的完整路由信息。
{
"routes": [
{
"path": "/",
"component": "@_modern_js_src/routes/page",
"data": "@_modern_js_src/routes/page.data",
"clientData": "@_modern_js_src/routes/page.data.client",
"error": "@_modern_js_src/routes/page.error",
"loading": "@_modern_js_src/routes/page.loading"
},
{
"path": "/dashboard",
"component": "pages/admin",
"config": "pages/admin.config",
"error": "pages/admin.error"
},
{
"path": "/user",
"component": "layouts/user",
"children": [
{
"path": "/user/profile",
"component": "@_modern_js_src/routes/user/profile",
"data": "@_modern_js_src/routes/user/profile.data"
}
]
},
{
"path": "@_modern_js_src/routes/blog/:id",
"component": "blog/detail",
"params": ["id"],
"data": "blog/detail.data",
"loading": "blog/detail.loading"
},
{
"path": "/files/*",
"component": "@_modern_js_src/routes/files/list"
}
]
}在多入口项目中,报告文件会按照入口名称进行分组,其中 key 为 entryName:
{
"main": {
"routes": [
{
"path": "/",
"component": "@_modern_js_src/routes/page",
"data": "@_modern_js_src/routes/page.data",
"error": "@_modern_js_src/routes/page.error",
"loading": "@_modern_js_src/routes/page.loading"
},
{
"path": "/dashboard",
"component": "@_modern_js_src/routes/dashboard",
"config": "@_modern_js_src/routes/dashboard.config"
}
]
},
"admin": {
"routes": [
{
"path": "/",
"component": "@_modern_js_src/routes/dashboard",
"data": "@_modern_js_src/routes/dashboard.data",
"clientData": "@_modern_js_src/routes/dashboard.data.client",
"config": "@_modern_js_src/routes/dashboard.config"
},
{
"path": "/users",
"component": "@_modern_js_src/routes/users",
"data": "@_modern_js_src/routes/users.data",
"error": "@_modern_js_src/routes/users.error",
"loading": "@_modern_js_src/routes/users.loading"
}
]
}
}报告中除了基本的路由信息外,还会显示每个路由找到的相关文件:
data: 服务端数据加载文件(.data.ts),用于在服务端获取数据clientData: 客户端数据加载文件(.data.client.ts),用于在客户端重新获取数据error: 错误边界文件(.error.tsx),用于处理路由渲染错误loading: 加载状态组件文件(.loading.tsx),用于显示数据加载中的状态config: 路由配置文件(.config.ts),用于配置路由元数据这些字段都是可选的,只有在找到对应文件时才会在报告中显示。通过查看这些字段,你可以快速了解每个路由的完整文件结构。