Repository navigation
Frontend Pages
页面路由(pages)是前端包的核心能力,声明主题或插件提供的用户可访问页面。
interface FrontendPageDefinition {
path: string; // 路由路径
component: string; // 组件名
data?: FrontendPageDataRequirement[]; // 可选,Finder 数据绑定
layout?: string; // 可选,Layout 名
}
interface FrontendPageDataRequirement {
finder: string; // Finder 名称
as: string; // 注入到组件 props 的键名
input?: Record<string, unknown>; // 传给 Finder 的额外参数
permission?: string; // Finder 执行前检查的权限
}页面组件从 props 接收平台注入的数据。完整形状见 FrontendPageProps(packages/sdk/src/frontend.ts):
interface FrontendPageProps<P extends Record<string, unknown> = Record<string, unknown>> {
data: P; // data 绑定注入的数据,键为 as
params: Record<string, string>; // 路径参数,如 :slug
settings: FrontendSettings; // 前端设置(见设置 Schema)
actions: FrontendActionExecutors; // 声明式操作,见 admin-extensions
assetsBaseUrl?: string; // 解析主题/插件资源 URL 的同源 BFF 基址
}渲染来源(theme/plugin/builtin)由平台内部的
PageResolution.source决定,不会作为 prop 传给组件。
组件以 pageComponents[component] 的名称为键导出:
export function BlogPostPage({ data, params }: FrontendPageProps) {
const post = data.post as Post;
return <article><h1>{post.title}</h1></article>;
}别忘了
export default frontend。 缺少 default 导出会让整份前端包解析失败,所有页面静默 not-found。详见前端包介绍:三条硬约束。
{ "path": "/", "component": "HomePage" }
{ "path": "/about", "component": "AboutPage" }使用 :param 声明路径参数:
{ "path": "/blog/:slug", "component": "BlogPostPage" }参数通过 captureFrontendPathParams 注入 Finder,可在组件中通过 params.slug 访问。
通配符 * 和 ** 只能作为路径的最后一段。有两种用法:
全局 fallback(* 或 /**):
{ "path": "*", "component": "NotFoundPage" }* 和 /** 匹配所有路径,优先级最低(specificity = -1)。在跨包覆盖时,显式匹配(非 fallback)优先于 fallback 匹配。
fallback 模板不会被当作正常页面直接返回:
FrontendRoute命中 fallback 时调用notFound(),由(user)/not-found.tsx边界渲染主题声明的*模板(无则回退平台默认 404 面板),HTTP 状态码保持真实 404。
路径内通配(如 /docs/*):
{ "path": "/docs/*", "component": "DocPage" }匹配 /docs/ 下的所有路径。路径内 * 的 specificity 为 0(低于字面段和参数段)。
selectFrontendPageDefinition 按 specificity 评分选择最佳匹配:
| 段类型 | 评分 | 示例 |
|---|---|---|
| 字面段 | 2 |
/about 中的 about
|
参数段 :param
|
1 |
/:slug 中的 :slug
|
通配段 */**(路径内) |
0 |
/docs/* 中的 *
|
全局 fallback *//**
|
-1 | * |
评分总和最高的路由胜出。例如 /about(2 分)优先于 /:slug(1 分)优先于 *(-1 分)。
resolveFrontendPageSource 按以下顺序选择页面来源:
- 主题显式匹配(非 fallback 路径)
- 插件显式匹配(非 fallback 路径)
-
主题 fallback(
*或/**) -
插件 fallback(
*或/**) - 内置
页面可以通过 data 字段绑定一个或多个 Finder,每个绑定声明 Finder 名称、注入键名和可选参数:
{
"path": "/blog/:slug",
"component": "BlogPostPage",
"data": [
{ "finder": "blogPost", "as": "post" },
{ "finder": "comments", "as": "comments", "input": { "limit": 10 } }
]
}渲染时,平台并行调用每个 Finder(Promise.all),传入路径参数、页面设置和 input。返回值以 as 声明的键名注入组件 props(如 post、comments)。如果声明了 permission,Finder 执行前会检查当前会话权限。详见 Finder 数据源。
页面可以通过 layout 字段使用命名 Layout:
{
"path": "/blog/:slug",
"component": "BlogPostPage",
"layout": "DefaultLayout"
}Layout 必须在前端包的 layouts 列表中声明。渲染时,平台用 Layout 组件包装页面组件。详见 命名 Layout。
{
"pages": [
{ "path": "/", "component": "HomePage", "layout": "DefaultLayout" },
{ "path": "/about", "component": "AboutPage", "layout": "DefaultLayout" },
{
"path": "/blog",
"component": "BlogListPage",
"data": [{ "finder": "blogList", "as": "posts" }],
"layout": "DefaultLayout"
},
{
"path": "/blog/:slug",
"component": "BlogPostPage",
"data": [{ "finder": "blogPost", "as": "post" }],
"layout": "DefaultLayout"
},
{ "path": "*", "component": "NotFoundPage" }
],
"layouts": ["DefaultLayout"],
"finders": ["blogList", "blogPost"]
}启用/安装插件时,assertNoConflictingFrontendPages 检查该插件的页面路径不与已激活插件的页面路径重叠(主题页面不参与此检测,通过覆盖优先级共存)。路径模式匹配包括 :param 和 *。
如果两个插件声明了重叠的路径(如 /blog/:slug 和 /blog/:id),启用后者时会返回 409 错误。
用户请求 /blog/hello-world
→ (user)/[...slug] catch-all
→ resolveFrontendPageSource(主题 > 插件 > 内置)
→ selectFrontendPageDefinition(包内具体 > 参数 > 通配)
→ captureFrontendPathParams(提取 :slug = hello-world)
→ 执行 Finder(如有 data 字段)
→ 用 Layout 包装(如有 layout 字段)
→ 渲染组件
开始
- 核心开发
构建内核扩展
- 插件
- 主题与前端包
AI 与自动化
- 内核 Agent
- MCP
对接平台
- Open Platform API
- Partner API