博客框架发展简史
静态站点生成器(SSG)的发展历程非常清晰:
- 2008 年,Jekyll(Ruby)率先开创了「Markdown 写作 + 静态 HTML 生成」的博客时代,GitHub Pages 随后开始流行。
- 随后,Hexo(2013-2014,Node.js)凭借丰富主题和中文友好特性受到欢迎。
- Hugo(Go 语言)以极致的构建速度脱颖而出,成为大型博客的首选。
- 2021 年起,Astro 代表新一代方向,采用岛屿架构(部分水合)、内容优先策略,并支持 React、Vue 等多框架组件,追求更低的 JS 开销和现代开发体验。
整体趋势从「简单快速生成」逐步演进为「高性能 + 灵活组件化」。
Astro 是什么
Astro 是一个以内容为中心的 Web 框架。页面默认生成纯静态 HTML,JavaScript 只在真正需要交互的地方加载(官方叫”Islands 架构”)。
Astro 用 .astro 文件写组件(语法类似 Vue 单文件组件),也可以在组件里混用 React/Vue/Svelte。写文章用原生 Markdown 就行,也支持 MDX。
→ 官方文档:docs.astro.build
AstroPaper 简介
AstroPaper 是一个基于 Astro 的博客主题,设计简洁,SEO 友好,响应式,支持深色模式。原作者 satnaing 在持续维护。
快速开始
前置条件:
- Node.js 24+(Astro 6 硬性要求,也是 GitHub Actions 的新基准)
- pnpm(项目用的包管理器)
# fork 后克隆到本地
git clone https://github.com/<你的用户名>/astro-paper.git
cd astro-paper
# 安装依赖并启动
pnpm install
pnpm dev
pnpm dev 默认跑在 http://localhost:4321,修改文件会自动热更新。
博客的主要配置在 src/config.ts,打开改掉标题、描述、作者信息就行。src/content.config.ts 定义了文章的 frontmatter schema,要加自定义字段在那里改。
我进行的一些改造
修改源码可查看 Astro-paper Fork。
分类系统
原版只有标签,没有分类。标签太扁了,按主题分组文章更好找。
做法:用文件夹做分类。在 posts/ 下建子目录,数字前缀控制排序:
posts/
├── 01-tech/ 技术
├── 02-life/ 生活
├── 03-sports/ 运动
├── 04-entertainment/ 影音游
└── 05-thoughts/ 随想
文章的 slug 会自动去掉数字前缀(01-tech/xxx.md → /posts/tech/xxx)。
分类的显示名称在 src/constants.ts 的 CATEGORIES 数组里配置,支持中/英/日三语:
export const CATEGORIES: Category[] = [
{ slug: "tech", label: { zh: "技术", en: "Tech", ja: "技術" } },
{ slug: "life", label: { zh: "生活", en: "Life", ja: "生活" } },
// ...
];
对应生成了 /categories 列表页和 /categories/[category] 详情页。
浮动目录(TOC)
原版目录在文章开头,长文章阅读体验差。重写了一个 TableOfContents.astro 组件:
- 桌面端(
xl以上,≥1280px)固定在文章右侧空白处,默认半透明,hover 时全亮 - 小屏幕右侧边缘有一个触发按钮,点击或悬浮弹出面板(带毛玻璃效果)
- Scroll Spy 自动高亮当前阅读位置的标题
- 只收录
h2和h3
阅读进度条
文章详情页有两个进度指示:
- 页面顶部 1px 高的线性进度条,跟随滚动变化
- 右下角”回到顶部”按钮外圈是圆形进度(
conic-gradient),超过 10% 滚动时才显示
i18n 多语言
原版 UI 文本是硬编码英文,散落在各组件里。做了客户端动态方案:
- 所有可翻译元素用
data-i18n="key"标记 src/scripts/i18n.ts在页面加载时读取localStorage("i18n-lang"),替换文本src/i18n/ui.ts导出完整的LOCALES字典(中/英/日,约 40 个翻译键)- 分类标签用
data-i18n-cat="slug"单独处理
选客户端方案而不是 Astro 官方的 i18n 路由方案,是因为博客只有中文内容,没必要生成三套页面。UI 文本切换就够了。
文章列表懒加载
文章多了之后一次性渲染全部 DOM 不太合适。用 IntersectionObserver 做懒加载:
- 初始只显示前 10 篇,超出部分
hidden - 页面底部放一个哨兵元素,进入视口(提前 300px)时批量显示下一批 10 篇
- 加了 CSS 淡入动画(
post-reveal),配合content-visibility: auto延迟渲染离屏内容 - 兼容 ViewTransitions,用
AbortController在页面切换时清理监听器
代码块增强
引入了几个 Shiki transformer:
// [!code highlight]高亮指定行// [!code ++]/// [!code --]标记增删- 代码块顶部自动显示文件名(支持
file="filename"元属性,Badge 风格)
代码主题用的是 min-light / night-owl。
外链自动新标签页
写了一个 remark 插件 remarkExternalLinks,自动给所有 http(s) 链接加 target="_blank" rel="noopener noreferrer"。在 astro.config.ts 的 remarkPlugins 里注册就行。
中英文阅读统计
原版只支持英文的阅读时间估算。自己写了一个 readingTime.ts:
- 中文 400 字/分钟,英文 200 词/分钟
- 先剥离 Markdown 语法(代码块、链接、图片等),再分别统计 CJK 字符数和英文单词数
- 文章详情页显示字数和预计阅读时间
图片展示
接入 PhotoSuite 实现:
- 灯箱效果(点击图片放大查看)
- 网格布局(多张图片自动排成网格)
- EXIF 信息显示(拍摄参数)
在 astro.config.ts 配置 scope: "#article",只处理文章内容区域。我的图片都存在阿里云 OSS,Markdown 里直接用完整 URL 引用。
RSS 美化
浏览器直接访问 /rss.xml 默认显示 XML 源码,不好看。加了一个 public/rss-style.xsl 样式表:
- 支持亮色/暗色模式(
prefers-color-scheme: dark) - 卡片式布局,文章列表带日期和描述(2 行截断)
- 提示用户添加到 Feedly / NetNewsWire / Inoreader 等阅读器
字体
没有引入 Google Fonts。直接用 CSS 系统字体栈,中文走了 PingFang SC / Hiragino Sans GB / Microsoft YaHei,覆盖 macOS 和 Windows。国内访问不受影响。
构建与部署
一键部署
pnpm build
产物在 dist/ 目录,全是静态文件。
一键部署到 Cloudflare / Vercel / Netlify,Astro 官方文档都有现成指南:
如果用自己的服务器,下面的流程可以照着做。
GitHub Actions 自部署
思路:push 代码 → GitHub Actions 自动 build → 通过 SSH 把 dist/ 同步到服务器 → nginx 托管。
有两个版本相关的坑要先知道:
- Astro 6 要求 Node.js 24+,本地和 CI 都要满足
- GitHub Actions 从 2025 年底开始强制使用 Node.js 24 运行环境,workflow 里需要设置
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true,所有 Action 插件也要用 Node 24 兼容的版本
服务器端生成 SSH 密钥
# 删除旧密钥(防止冲突)
sudo rm -f /root/.ssh/github-actions*
# 生成 RSA 4096 PEM 格式密钥(无密码)
sudo ssh-keygen -t rsa -b 4096 -C "github-actions-root-deploy" -m PEM -f /root/.ssh/github-actions -N ""
# 把公钥加入 authorized_keys
cat /root/.ssh/github-actions.pub >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
然后用 cat /root/.ssh/github-actions 查看私钥,复制下来。
配置 GitHub Secrets
进 GitHub 仓库 → Settings → Secrets and variables → Actions,添加:
| 名称 | 值 |
|---|---|
SSH_PRIVATE_KEY | 上一步的私钥,完整粘贴 |
REMOTE_HOST | 服务器 IP 或域名 |
REMOTE_USER | SSH 用户名(如 root) |
REMOTE_TARGET | 部署目录,如 /var/www/blog |
创建 workflow 文件
在项目根目录建 .github/workflows/deploy.yml:
name: Deploy Astro to Server
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
on:
push:
branches: [main]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v5
with:
version: latest
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install
- run: pnpm run build
- uses: easingthemes/ssh-deploy@v5
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
SOURCE: dist/
TARGET: ${{ secrets.REMOTE_TARGET }}
ARGS: "-rltgoDzvO --delete"
几个要点:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true必须设,不设的话新版插件会报错node-version: 24写死了,Astro 6 硬性要求,等 Node 25 LTS 了再改- Action 插件都用大版本号(当前最新的),老版本可能不兼容 Node 24 运行环境
ssh-deploy底层用 rsync,--delete会删掉服务器上多余的旧文件
域名解析、nginx 配置、SSL 证书这些属于服务器运维范畴,后面单独写一篇。
→ GitHub Actions 文档:docs.github.com/actions
写作软件
写 Markdown 博客,挑顺手的就行。说几个常见的:
VS Code:写代码的人基本都装了,Markdown 预览、语法高亮、拼写检查都能通过插件搞定。我自己的做法是用 VS Code 写,配合项目里的 TypeScript 类型检查,frontmatter 字段写错了能有提示。
Obsidian:如果同时用它管理笔记,直接拿来写博客也行。实时预览、双向链接、图片拖拽这些对写作很友好,写完使用 Git 插件 push 即可。
其他:Typora(所见即所得,付费)、Neovim/Vim 等就不多说了。