第 08 / 11 章

页面与路由:app/

根布局、首页、散篇与标签页,以及系列三层路由、章节页三栏阅读台与 sticky 侧栏约定。

⏱ 3 分钟902 字

layout.tsx§

  • 用 next/font/local 加载 JetBrains Mono,挂到 CSS 变量 --font-jetbrains
  • 导出全站 metadata(默认标题模板 "%s · 墨栈")与 viewport(浅色/深色地址栏颜色)
  • 结构:<Providers> → SiteHeader → {children} → SiteFooter
  • <html> 上 suppressHydrationWarning:主题脚本可能在水合前改 data-theme,避免 React 报属性不一致

页眉导航含「文章 / 系列 / 摄影 / 标签」;Logo 回首页,文章 指向 /posts。小屏为抽屉菜单(焦点陷阱 + 打开时 main/footer inert),大屏为横向导航。usePathname 用路径前缀决定高亮。

providers.tsx§

客户端组件,仅包一层 ThemeProvider:attribute="data-theme"、defaultTheme="system"、本地键名 inkstack-theme。真正的颜色切换靠 app/styles/tokens.css 里 :root 与 :root[data-theme="light"] 两套变量。

page.tsx(首页)§

服务端组件,首页只突出最新文章与摄影:

  1. 全幅 Hero:最新摄影作品作背景;品牌、标语、CTA;右下角署名链到该作品
  2. HomeReadingStrip 叠在 Hero 顶部(本地进度最高的一篇散篇或系列章;无进度则不渲染)
  3. getAllPosts() → toMeta,取最近若干篇:第一篇 PostCard featured,其余列表;「全部文章 →」链到 /posts
  4. 摄影马赛克:排除 Hero 已用作品后,精选优先再按日期取若干张;「发现全部 →」链到 /photos
  5. 系列与标签不在首页堆砌,从导航进入

posts/page.tsx(文章归档)§

散篇时间线:全部 PostCard 双列展示,供首页「全部文章」与导航「文章」使用。

posts/[slug]/page.tsx(散篇文章页)§

关键配置:

export const dynamicParams = false;

export function generateStaticParams() {
  return getAllPosts().map((p) => ({ slug: p.slug }));
}

dynamicParams = false 表示:未出现在静态列表里的路径直接 404,不会在运行时再去读盘。

页面骨架(两栏):

  • 主栏:返回、刊号与标题、<MdxContent />、上一篇/下一篇
  • 右侧大屏:ReadingPathRail;小屏:ReadingPathFab
  • ProgressBar + ArticleShell(进度键 = 文章路径名)

tags/page.tsx 与 tags/[tag]/page.tsx§

标签总览用条形长度表示相对篇数;单标签页同样 generateStaticParams,无文章则 notFound()。列表复用 PostCard。标签目前聚合自散篇的 tags 字段。

系列版块:三层路由§

路径文件作用
/seriesapp/series/page.tsx全部小册列表(SeriesCard)
/series/[series]app/series/[series]/page.tsx小册封面、章目录(SeriesToc)、从第一章开始
/series/[series]/[chapter]…/[chapter]/page.tsx章节正文:三栏阅读台

章节静态参数展开「每一册 × 每一章」:

export function generateStaticParams() {
  return getAllSeries().flatMap((s) =>
    s.chapters.map((c) => ({ series: s.slug, chapter: c.slug }))
  );
}

章节页布局([chapter]/page.tsx)§

大屏(约 ≥1080px)三栏:

  1. 左 SeriesChapterRail——本册目录、册内进度、回小册首页 / 全部系列
  2. 中——面包屑、章标题、MdxContent、ChapterPager(上一章 / 下一章)
  3. 右 ReadingPathRail——本章标题目录(与散篇同组件)

窄屏隐藏左右栏:顶部 SeriesChapterStrip 横向切章;本章目录仍用 ReadingPathFab。

ArticleShell 传入的是 chapter.progressKey(形如 series/inkstack-source/01-orientation),与散篇进度隔离。

sticky 实现要点§

  • Grid align-items: stretch(默认),左右 aside 与正文同高
  • position: sticky 写在 aside 内部的 .series-chapter-sticky
  • 切勿在拉伸侧栏上再设 align-self: start 后把 sticky 只放在塌缩后的盒子里——否则没有吸附行程

样式见 app/styles/site.css 中 .series-chapter-frame、.series-chapter-sticky、.series-rail*、.series-strip*。

内容模型与 lib/series API 的完整说明见专章「系列小册:内容模型与版块设计」;本章只固定页面层怎么挂。

not-found.tsx§

站点级 404 页,文案与视觉跟刊物气质一致即可,逻辑很薄。

下一章进入客户端最重的一块:MDX 标签映射与代码块组件(散篇与系列章节共用)。