Tanplate Docs

Pages

TanStack Start 页面路由、Legal/Changelog Git Content、布局组和自定义页面。

Tanplate 的页面系统建立在 TanStack Router 文件路由之上,并按职责拆分 UI、Server Route 与 Git Content。src/pages/_app 是不占 URL 段的公共布局组;它下面的页面共享 Header、Footer、Consent 与全局路由守卫。

核心能力

  • 创建带公共应用壳的组件页面。
  • 用 Git 管理 Blog、Changelog 和 Legal Markdown/MDX。
  • 在构建期校验 frontmatter、内部链接、发布状态和安全渲染边界。
  • 按 Manifest Capability Profile 将可选页面真正移出产物。
  • 为公开页面统一生成 canonical、Open Graph、Twitter 与 JSON-LD。

页面结构

apps/web/
├── content/
│   ├── blog/                 Blog Markdown/MDX
│   ├── changelog/            版本更新 Markdown/MDX
│   └── legal/                法律与隐私 Markdown/MDX
└── src/
    ├── pages/
    │   ├── _app/             带 Header/Footer 的页面
    │   └── _fullscreen/      不带公共壳的全屏页面
    └── api/
        └── -internal/        Server Route 背后的内部实现

_app 是 pathless layout:它参与组件树,但不会出现在公开 URL 中。例如 src/pages/_app/legal/index.tsx 对应 /legal,而不是 /_app/legal

页面私有渲染组件放在相邻 -components/。跨页面稳定复用的 UI 原语才进入 src/components;数据库、Provider 和 Worker binding 留在 src/api-internal 与共享领域包中。

法律内容存储在 apps/web/content/legal,由一组列表/动态详情路由统一渲染:

文件URL职责
src/pages/_app/legal/index.tsx/legal已发布法律内容列表
src/pages/_app/legal/$slug.tsx/legal/$slug按 slug 渲染详情,未知或未发布内容返回 404

与“Cookie、Privacy、Terms 各写一个 route”的结构不同,本项目只维护一个 $slug.tsx 动态路由。要提供三类法律页面,只需创建三个内容文件:

apps/web/content/legal/
├── cookie.md
├── privacy.md
└── terms.md

当前仓库已经提供 privacy.md 作为可替换的起点。文件名必须与 slug 一致,目录必须与 kind 一致。

自定义法律内容

apps/web/content/legal/privacy.md
---
kind: legal
slug: privacy
title: Privacy policy
description: How we collect, use, retain, and protect personal data.
status: published
publishedAt: 2026-07-30
effectiveAt: 2026-07-30
---

<!--
[INPUT]: 当前产品的数据处理、Provider 与用户权利。
[OUTPUT]: `/legal/privacy` 的已发布隐私政策。
[POS]: 位于 Legal 内容目录,作为项目隐私政策。

[PROTOCOL]:
1. 一旦正文或 frontmatter 变化,必须同步更新本 Header。
2. 更新后必须检查所属 `.folder.md` 与引用本页的内部链接。
-->

## Introduction

Describe the data practices that apply to this deployment.

法律 frontmatter 使用以下固定字段:

  • kind:必须是 legal
  • slug:小写 kebab-case,且必须等于文件名。
  • title / description:列表、SEO 与详情页使用的公开文案。
  • statusdraftpublished
  • publishedAt:进入发布索引的日期。
  • effectiveAt:页面展示的生效日期。

新增 Cookie 政策或服务条款时复制同一 Schema,分别使用 slug: cookieslug: terms;不需要再创建 React route。

Changelog 更新日志

发布说明存储在 apps/web/content/changelog,同样由列表和动态详情路由渲染:

文件URL职责
src/pages/_app/changelog/index.tsx/changelog按发布日期倒序展示已发布版本
src/pages/_app/changelog/$slug.tsx/changelog/$slug渲染单个版本,未知或未发布内容返回 404

当前仓库包含 v0-4-content-foundation.md 示例。新增版本时只需添加新的 Markdown/MDX 文件:

apps/web/content/changelog/v1-1-0.md
---
kind: changelog
slug: v1-1-0
title: Product workflows
description: This release adds the next set of product workflows.
status: published
publishedAt: 2026-07-30
version: 1.1.0
---

<!--
[INPUT]: 1.1.0 已完成且已验证的产品变化。
[OUTPUT]: `/changelog/v1-1-0` 的已发布版本说明。
[POS]: 位于 Changelog 内容目录,作为 1.1.0 发布记录。

[PROTOCOL]:
1. 一旦正文或 frontmatter 变化,必须同步更新本 Header。
2. 更新后必须检查所属 `.folder.md`、Search、Feed 与 Sitemap。
-->

## Highlights

- Added the first workflow.
- Improved the second workflow.

version 接受 1.11.1.0v1.1.0,以及由小写字母、数字、点和连字符组成的可选后缀(例如 1.1.0-beta.1)。这是仓库的版本标签格式,不是完整 SemVer 校验。slug 仍需使用 kebab-case,因此版本文件建议使用 v1-1-0.md

发布与 Capability Profile

Legal 和 Changelog 是 Content 的独立子能力。当前默认 Manifest 开启 Content/Blog,但关闭 Legal 与 Changelog:

{
  "features": {
    "content": {
      "blog": true,
      "changelog": false,
      "enabled": true,
      "legal": false
    }
  }
}

要发布它们,必须同时保持 features.content.enabled: true,并将对应 legal / changelog 子开关设为 true,然后运行:

pnpm init:template
pnpm check:template
pnpm --filter web typecheck
pnpm --filter web build

关闭子能力时,相关 route、Header/Footer 链接、预渲染路径和内容正文不会进入产物。只添加 Markdown、但不启用 Manifest,不会生成公开页面。

构建期内容合同

Vite 在启动或构建时读取 apps/web/content,再由 src/lib/markdown.ts 完成确定性校验:

  1. frontmatter 必须匹配严格 Schema,未知字段会失败。
  2. status: draft 或未来 publishedAt 不进入公开索引。
  3. 已发布条目的 URL 不能重复。
  4. 内部链接必须指向当前 Profile 中真实发布的页面。
  5. MDX 禁止 import、export 和表达式,只允许固定的 Callout

通过校验的条目注入 src/lib/pages.ts,供 Legal/Changelog 列表、详情、Search、Feed、Sitemap 和预渲染共用。运行时不读取 Git 文件,也不会把它们变成 D1 CMS。

创建组件页面

不适合 Markdown 的交互页面直接放在 src/pages/_app。例如:

apps/web/src/pages/_app/about.tsx
/*
[INPUT]: APP_BRAND、站点 URL 与公开 SEO policy。
[OUTPUT]: `/about` 公开介绍页面。
[POS]: 位于 `_app`,作为带公共布局的介绍页。

[PROTOCOL]:
1. 一旦页面职责变化,必须同步更新 Header。
2. 更新后必须检查所属 `.folder.md`、路由树与 Sitemap。
*/

import { createFileRoute } from '@tanstack/react-router'
import { buildPublicSeo } from '@tanplate/ui'
import { APP_BRAND } from '@/config/app'
import { getSiteUrl } from '@/lib/urls'

export const Route = createFileRoute('/_app/about')({
  component: AboutPage,
  head: () =>
    buildPublicSeo({
      description: `About ${APP_BRAND.name}.`,
      path: '/about',
      siteName: APP_BRAND.name,
      siteUrl: getSiteUrl(),
      title: `About | ${APP_BRAND.name}`,
    }),
})

function AboutPage() {
  return (
    <main className="mx-auto w-full max-w-4xl px-5 py-12 lg:px-8">
      <h1 className="text-3xl font-semibold">About</h1>
    </main>
  )
}

新增页面时:

  1. 选择 _app_fullscreen,创建 file route 并补齐 Header。
  2. 公开页复用 buildPublicSeo;私有工具页使用 noindex policy。
  3. 页面私有组件放入相邻 -components/
  4. 浏览器请求通过 src/services/api,HTTP transport 复用 src/services/request
  5. 服务端能力放在 src/api,内部实现放在 src/api/-internal
  6. 更新所属 .folder.md,生成 Route Tree,并运行 typecheck/build。

routeTree.gen.ts 是生成物,不能手工维护。

受保护页面

src/pages/__root.tsx 在每次页面进入前执行全局路由守卫。登录态页面应复用当前 src/routers 的 Session/guard 模块和既有 Capability Profile,不能在单个页面复制 Session fetch 或维护第二套中间件。

Projects 可以匿名公开只读;Favorites 只有 Projects 与 Authentication 同时开启时存在。Admin、User Assets、Developer API 和 AI 依赖 Authentication。

完整依赖方向见 项目结构,页面文案见 多语言

On this page