更新说明(2026-04-16):站点已经移除了这套自定义 ScrollRestore。它会在真实刷新里制造明显的重影和跳帧;当前实现改成了更简单的策略:刷新时一律回到页首,不再尝试恢复中段阅读位置。
站点只有首页和文章页时,把所有东西都塞进 BaseLayout.astro 还能忍。等页面开始变多,问题就出来了:
- 页面骨架、全局样式、头部结构、主题脚本、滚动脚本挤在一个文件里
- 中英切换和站点级文案需要在很多页面重复传递
- 以后再加归档、系列、移动端导航时,很难判断该改哪里
当前仓库的做法,是先把布局层拆出清楚的装配边界。
最终分层
和布局直接相关的文件现在是这些:
src/layouts/BaseLayout.astrosrc/components/layout/SitePage.astrosrc/components/layout/LayoutStyles.astrosrc/components/layout/SiteHeader.astrosrc/components/layout/ThemeController.astrosrc/components/layout/ScrollRestore.astro
其中真正负责“骨架”的只有前 3 个,另外 3 个是被挂进去的行为或结构组件。
BaseLayout 只负责装配
当前版本的 BaseLayout.astro 不再承载具体交互,只做三件事:
- 输出
<html> / <head> / <body>的页面骨架 - 接收站点级 props
- 把主题、滚动恢复、全局样式和 Header 挂进去
核心结构如下:
---
import LayoutStyles from "../components/layout/LayoutStyles.astro";
import ScrollRestore from "../components/layout/ScrollRestore.astro";
import SiteHeader from "../components/layout/SiteHeader.astro";
import ThemeController from "../components/layout/ThemeController.astro";
---
<html lang={lang} data-theme="light">
<head>
<ThemeController />
{scrollRestore ? <ScrollRestore /> : null}
<LayoutStyles />
</head>
<body>
<SiteHeader ... />
<main>
<slot />
</main>
</body>
</html>
这样拆完以后,布局文件就变成一个稳定的装配点,而不是一份越来越长的杂糅脚本。
SitePage 负责站点级上下文
页面层再往上一层,当前项目又做了一个 SitePage.astro。它的作用不是新增视觉层,而是把每个页面都会重复做的那套事情收起来:
- 根据
locale读取site.ts里的文案 - 计算当前页面的跨语言跳转地址
- 把
siteName、lang、导航、切换按钮文案一起传给BaseLayout
这个组件让 src/pages/*.astro 可以保持很薄。页面文件只需要决定当前是哪一种 page view,不需要再手工拼一遍头部配置。
LayoutStyles 独立承担全局样式
LayoutStyles.astro 是当前站点的全局视觉底板,主要包含:
- 颜色变量和主题变量
body版心main正文排版- 表格、代码块、图片、引用块等通用内容样式
- Header 的响应式样式
几个关键点都放在这里:
html {
background: var(--page-bg);
scrollbar-gutter: stable;
}
body {
max-width: 800px;
margin: 0 auto;
padding: 40px 20px;
}
main {
display: grid;
gap: 1.5rem;
overflow-wrap: break-word;
}
这层抽出来以后,页面组件就不需要各自维护一份排版规则,新增页面也会自动落到统一视觉体系里。
为什么先做这一步
因为后面的几个功能都依赖它:
ThemeController需要一个稳定的<html data-theme>挂点ScrollRestore需要在<head>提前执行SiteHeader需要统一被所有页面复用- 首页、文章页、系列页、归档页都需要共享相同的正文排版和版心
如果不先把布局层分清,后面每加一个功能,都会重新把 BaseLayout.astro 拉回到“大文件加内联逻辑”的状态。
当前这一步解决的其实不是视觉,而是维护性
拆完之后,职责边界就很明确:
BaseLayout是页面骨架SitePage是站点级页面包装器LayoutStyles是全局样式层
至于 Header、主题切换和滚动恢复,则各自进入独立组件和独立页面。这样站点从最初的“能跑”继续往上长时,不会再因为布局文件过重而卡住。