项目概览
这个网站既是个人技术博客,也是工程案例的长期归档入口。它面向需要持续整理技术文章、项目复盘和基础设施笔记的个人开发者,核心目标不是堆叠功能,而是建立一条简单、可验证、容易恢复的内容发布链路。
站点当前已经实现首页、项目、博客、关于、基础设施、404、RSS、robots 和 Sitemap,并提供文章与项目详情页。内容使用 Markdown 或 MDX 编写,Astro 在构建阶段把它们转换为静态 HTML;生产环境不需要数据库,也不需要常驻 Node.js 服务。
当前仓库可以完整执行类型检查和生产构建,但仍使用 https://example.com、示例邮箱和 GitHub 地址。Rocky Linux、Caddy 与 GitHub Actions 是后期目标运维架构,仓库中暂时没有对应的服务器配置和部署工作流。本文会明确区分“已经实现的站点源码”和“准备实施的生产部署”。
目标与设计原则
项目围绕以下目标展开:
- 内容优先:文章和案例应当是普通文本文件,可以审查、比较和长期迁移;
- 静态优先:能在构建阶段完成的工作不留到请求阶段,减少线上故障面;
- 渐进增强:没有 JavaScript 时正文和导航仍可阅读,脚本只承担主题切换等必要交互;
- 集中配置:个人信息、站点 URL、导航和技术栈从单一配置入口读取;
- 类型安全:内容字段、页面属性和组件接口在构建前完成校验;
- 可访问性:键盘焦点、跳过链接、语义化导航、颜色对比度和减少动画偏好属于基础能力;
- 可恢复运维:Git 仓库是源数据,服务器只保存可替换的静态产物。
为了守住这些原则,第一阶段没有加入 React Islands、搜索、评论、Analytics、CMS、登录、数据库或服务端渲染。这些能力并非永远不能增加,而是只有在需求和维护成本都明确时才进入项目。
快速开始
环境要求
建议使用 Node.js 22.19 或更高的受支持偶数版本以及 npm。Astro 本身要求受支持的 Node.js 版本,当前依赖树中的 undici 在较新的 Node.js 22 上也不会产生 engine 提示。
仓库根目录包含实施文档,真正的 Astro 项目位于外层 src/ 目录。因此从仓库根目录开始时先进入该目录:
cd src
npm ci
npm run dev
开发服务器固定监听:
http://127.0.0.1:4321/
http://localhost:4321/ 通常会回退到同一个 IPv4 回环地址。固定使用 IPv4 是为了兼容本机启用 v2rayN TUN 且未启用 TUN IPv6 的场景,不会把开发服务器暴露给局域网。
常用命令
| 命令 | 用途 | 主要输出 |
|---|---|---|
npm run dev |
启动本地开发服务器和内容热更新 | 127.0.0.1:4321 |
npm run check |
检查 Astro、TypeScript 和内容 schema | 诊断结果 |
npm run build |
生成生产静态站点 | dist/ |
npm run preview |
本地预览生产构建结果 | 127.0.0.1:4321 |
修改内容后至少执行一次 npm run check 和 npm run build。开发服务器能打开页面,不代表内容 schema、所有静态路由和生产过滤逻辑一定正确。
技术选型
| 关注点 | 选择 | 原因与取舍 |
|---|---|---|
| 站点框架 | Astro 7 | 以 HTML 为默认输出,适合内容站和静态路由,不必为了组件复用引入完整 SPA |
| 类型系统 | TypeScript Strict | 组件属性、集合条目和工具函数在构建前暴露类型问题 |
| 样式系统 | Tailwind CSS 4 Vite 插件 | 直接接入 Astro 的 Vite 管线,同时保留一份集中维护的全局设计系统 |
| 内容格式 | Markdown / MDX | 正文便于版本控制,必要时仍可嵌入结构化组件 |
| 内容索引 | Astro Content Collections | frontmatter 经过 Zod schema 校验,列表与详情页共享同一数据来源 |
| 输出方式 | Static | 线上只托管文件,不需要应用服务器、会话或数据库 |
| 客户端交互 | 原生 JavaScript | 只实现三态主题和持久化,避免为少量交互引入客户端框架 |
Astro 项目使用 astro/tsconfigs/strict,并通过 @/* 映射引用应用源码。Tailwind 4 以 @tailwindcss/vite 插件接入,没有额外的客户端运行时。MDX、RSS 和 Sitemap 分别由 Astro 官方集成提供。
整体架构
构建期数据流
site.config.mjs Markdown / MDX
│ │
│ glob() content loaders
│ │
└──────────────┬───────────────┘
▼
Astro Content Collections
│
getCollection() / sort / filter
│
getStaticPaths() / render()
│
▼
Pages + Layouts + Components + CSS
│
astro build
│
▼
dist/
HTML / CSS / RSS / robots / Sitemap
内容加载、schema 校验、草稿过滤、排序和 Markdown 渲染全部发生在构建阶段。详情路由通过内容文件 ID 生成稳定 URL,例如:
src/content/projects/personal-tech-blog.md
→ /projects/personal-tech-blog/
访问链路
本地开发时,请求直接进入 Astro 开发服务器;目标生产环境则只读取静态文件:
开发环境
Browser → 127.0.0.1:4321 → Astro Dev Server
目标生产环境
Browser → DNS → Caddy / HTTPS → /var/www/blog → Static Files
生产请求不会执行 Markdown 解析、数据库查询或 Node.js 业务逻辑。Caddy 负责 TLS、压缩、安全响应头和文件服务,Astro 的职责在构建完成时已经结束。
目录结构
下面是与运行和内容维护直接相关的实际结构:
Blog/
├── .gitignore
├── docs/
│ └── 个人技术博客_Astro_Rocky_实施方案.md
└── src/ # Astro 项目根目录
├── astro.config.mjs # 静态输出、尾斜杠、集成与本地监听地址
├── site.config.mjs # 个人信息、站点 URL、导航与技术栈
├── package.json # 依赖和 dev/check/build/preview 脚本
├── package-lock.json # 可复现安装
├── tsconfig.json # Strict 模式和 @/* 路径别名
├── public/
│ └── favicon.svg
└── src/
├── components/ # 页头、页脚、卡片、标签、主题与目录
├── layouts/ # 通用 SEO 框架、博客与项目正文布局
├── pages/ # 文件路由、RSS、robots 和 404
├── content/
│ ├── blog/ # 技术文章
│ └── projects/ # 项目案例
├── content.config.ts # Blog 与 Project schema
├── styles/global.css # 设计令牌、响应式布局与 Markdown 样式
└── utils/ # 内容筛选、排序和日期格式化
node_modules/、.astro/ 和 dist/ 都是可重新生成的目录,已经由仓库根级 .gitignore 排除,不应进入版本控制或备份。
内容模型与路由
Blog 内容接口
博客 frontmatter 包含:
| 字段 | 必填 | 作用 |
|---|---|---|
title |
是 | 页面标题、卡片标题与 RSS 标题 |
description |
是 | 摘要、SEO description 与 RSS 描述 |
pubDate |
是 | 发布时间和倒序排序依据 |
updatedDate |
否 | 更新日期和文章 Open Graph 元数据 |
tags |
否 | 标签列表与 RSS categories |
draft |
否 | 开发可见、生产过滤,默认 false |
Project 内容接口
项目 frontmatter 包含标题、描述、分类和技术栈,并用以下字段控制展示:
featured:是否进入首页精选候选;order:项目列表的升序位置;draft:生产构建是否过滤;placeholder:是否显示匿名化示例标识和发布前替换提示。
开发环境会加载草稿并显示 Draft 标识,生产构建会过滤 draft: true。博客按发布日期倒序,项目按 order 升序;首页再从精选项目中取前三项。
静态路由生成
列表页调用 getCollection() 获取可见内容。详情页通过 getStaticPaths() 为每个内容 ID 创建路径,并用 render() 得到正文组件和标题列表。二级、三级标题会进入右侧目录,不需要作者手工维护锚点。
站点统一使用尾斜杠,例如 /blog/linux-socket/。404 是独立静态页面,未知路径由生产 Web Server 按其静态站规则返回。
页面与组件设计
BaseLayout 统一输出语言、标题、description、canonical、Open Graph、favicon、RSS 和 Sitemap 链接,并装配跳过链接、页头和页脚。Blog 与 Project 布局只负责各自的元信息、标签、正文和目录,避免每个页面重复定义 SEO 和外壳。
响应式导航在桌面使用普通 <nav>,移动端使用原生 <details>,即使没有客户端框架也支持键盘操作。项目卡片和文章卡片只接收内容集合条目,链接始终由条目 ID 推导。
全局样式使用暖白浅色、石墨深色和冷青强调色,并以 CSS 变量维护主题。正文针对标题、列表、引用、表格和代码块提供统一排版;表格与代码块允许横向滚动,避免长命令或目录树撑破移动布局。
主题与可访问性
主题切换包含“系统、浅色、深色”三种状态:
- 首次访问遵循
prefers-color-scheme; - 用户选择浅色或深色后写入
localStorage; - 内联脚本在正文绘制前应用已保存主题,减少首屏闪烁;
- 切回系统模式时删除持久化覆盖,继续跟随操作系统。
站点还提供跳到主要内容的链接、清晰的焦点状态、导航当前页标识、语义化目录和 prefers-reduced-motion 适配。图形装饰不承担唯一信息来源,主题按钮同时具有文本与可访问名称。
SEO 与内容分发
SEO 数据从页面属性与 site.config.mjs 合成:
- 每页独立的标题和 description;
- 基于
siteConfig.url与当前路径生成 canonical; - Open Graph 标题、描述、类型和 URL;
- 博客文章的发布时间、更新时间和标签元数据;
/rss.xml输出所有非草稿博客文章;/robots.txt指向 Sitemap;@astrojs/sitemap在构建时生成 Sitemap;- SVG favicon 不依赖外部图片服务。
正式发布前必须替换 site.config.mjs 中的 https://example.com、邮箱和 GitHub 占位值。否则页面虽然可以构建,canonical、robots、RSS 和 Sitemap 却会指向错误域名。
本地网络与 TUN 兼容
开发阶段曾出现浏览器无法打开本地站点的问题。Astro 当时只监听 IPv6 回环地址 ::1:4321,而 v2rayN TUN 配置未启用 IPv6 地址并使用严格路由,因此请求没有到达开发服务器。关闭代理并不是可靠修复,也会改变正在使用的网络环境。
最终在 astro.config.mjs 中固定 IPv4 回环地址:
server: {
host: '127.0.0.1',
port: 4321,
}
这一配置同时作用于 dev 和 preview,只允许本机访问,也与现有 localhost、127.* 和私有地址直连规则兼容。排查同类问题时可以先验证:
curl.exe -I http://127.0.0.1:4321/
netstat -ano | findstr :4321
如果 127.0.0.1 可访问而 localhost 不可访问,再检查本机名称解析;如果端口没有监听,则检查开发进程和端口占用。没有必要为了本站修改全局 IPv6、Windows hosts 或关闭 TUN。
内容开发与发布流程
一次内容变更建议遵循下面的最小闭环:
- 在
src/content/blog/或src/content/projects/新建 Markdown; - 填写符合 schema 的 frontmatter;
- 使用
npm run dev检查正文、目录、代码块和移动端布局; - 执行
npm run check,处理类型和内容错误; - 执行
npm run build,确认所有静态路由都能生成; - 检查 Git 差异,避免提交
dist/、.astro/、环境文件或敏感信息; - 提交源码,由手工流程或未来 CI 生成生产产物。
项目案例涉及客户或设备时,必须删除客户名称、真实 IP、访问凭据、工艺参数和未公开指标。无法验证的结果应明确标为示例,不把假设数据写成真实成果。
目标部署架构
本节描述后期运维目标,当前仓库尚未包含 Caddyfile、服务器初始化脚本或 GitHub Actions 工作流。
Git Repository
│ push production branch
▼
CI or trusted build host
│ npm ci
│ npm run check
│ npm run build
▼
dist/
│ rsync over SSH
▼
Rocky Linux /var/www/blog
│
▼
Caddy → HTTPS → Browser
服务器只接收 dist/,不保存项目源码、.git/、node_modules/ 或 .env。这样可以降低低配 VPS 的内存占用,也能把构建失败与线上请求处理隔离开。
第一阶段:手工发布
先手工跑通完整链路,再自动化同一套步骤:
npm ci
npm run check
npm run build
rsync -avz --delete dist/ deploy@example.com:/var/www/blog/
这里的用户、域名和路径必须按真实服务器替换。上传后恢复 SELinux 标签,并分别验证 HTTP 重定向、HTTPS、首页、详情页、CSS、RSS 和 Sitemap。
目标 Caddy 配置可以从最小静态站开始:
example.com {
root * /var/www/blog
encode zstd gzip
file_server
header {
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy "camera=(), microphone=(), geolocation=()"
}
}
在启用前先检查 TCP/80、TCP/443 和可能被现有代理占用的 UDP/443。若其他服务已经占用 TCP/443,需要设计统一入口,不能让两个进程竞争同一监听地址。
第二阶段:CI/CD
手工部署稳定后,CI 只自动化已验证的过程:检出代码、安装锁定依赖、类型检查、静态构建、上传 dist/、恢复标签并做 HTTP 健康检查。部署使用独立普通用户和受限 SSH Key,不使用 root,也不把服务器密钥写进仓库。
任何一步失败都应停止发布;构建失败时不得覆盖线上目录,上传完成但健康检查失败时应保留上一版本用于恢复。
后期运维
系统与权限
- Rocky Linux 保持 SELinux Enforcing,不通过关闭安全机制解决文件访问问题;
- 使用持久化文件上下文标记
/var/www/blog,部署后执行restorecon; - firewalld 公网只开放实际需要的 TCP/80 和 TCP/443;
- SSH、监控面板和其他管理入口优先通过 Tailscale 或限制来源地址;
- Caddy 和部署用户只获得完成各自职责所需的最小权限。
静态目录的 SELinux 配置示例:
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/blog(/.*)?"
sudo restorecon -Rv /var/www/blog
发布验收
每次发布至少检查:
sudo caddy validate --config /etc/caddy/Caddyfile
curl -I http://example.com
curl -I https://example.com
curl -I https://example.com/rss.xml
同时确认 HTTPS 证书正常、HTTP 自动跳转 HTTPS、静态资源返回成功、canonical 使用生产域名、手机宽度无横向溢出。Caddy 配置修改后优先使用平滑 reload,不为普通内容更新重启整台服务器。
日志与基础监控
个人静态站第一阶段不需要复杂监控平台,先保证关键层级可观察:
systemctl --failed
systemctl status caddy
journalctl -u caddy --since today
ss -lntup
df -h
free -h
重点关注证书续期、HTTP 5xx、磁盘空间、Caddy 服务状态、端口冲突和最近一次发布结果。外部可用性检查可以定时请求首页和一个静态资源,但不应把监控复杂度做得高于站点本身。
备份与恢复
Git 仓库是文章、项目、配置和构建逻辑的源数据;/var/www/blog 只是可重建产物。需要备份的是 Git 远端、/etc/caddy/Caddyfile、DNS 信息和必要的服务器安全配置,而不是 node_modules/ 或 dist/。
灾难恢复流程应能够在新主机上完成:安装 Caddy、恢复配置和权限、重新构建源码、上传 dist/、恢复 SELinux 标签、验证 DNS 与 HTTPS。定期验证恢复步骤比单纯确认“存在备份文件”更重要。
回滚策略
MVP 阶段可以对错误提交执行 git revert,重新构建并发布。需要更快回滚时,再升级为版本化目录:
/var/www/blog/
├── releases/
│ ├── commit-a/
│ ├── commit-b/
│ └── commit-c/
└── current -> releases/commit-c/
新版本先上传到独立目录并完成检查,最后原子切换 current 软链接;失败时切回上一版本。该方案属于后期优化,不是当前仓库已经实现的能力。
依赖维护
定期检查 Node.js、Astro、Tailwind 和集成包的受支持版本。依赖升级应在独立提交中进行,先阅读变更说明,再执行:
npm outdated
npm audit
npm run check
npm run build
不要在没有验证的情况下批量升级所有主版本。package-lock.json 必须与 package.json 一起提交,生产构建使用 npm ci 保证依赖树可复现。
常见问题排查
| 现象 | 优先检查 | 处理方向 |
|---|---|---|
| 本地页面打不开 | 127.0.0.1:4321 是否监听 |
检查开发进程、端口占用和 IPv4 回环,不关闭 TUN |
| 内容未出现在生产站 | draft、文件位置和构建日志 |
将可发布内容设为非草稿并重新构建 |
| 构建报告 schema 错误 | frontmatter 字段与类型 | 按内容集合接口补齐字段,不绕过校验 |
| canonical 或 Sitemap 域名错误 | site.config.mjs 的 url |
替换占位域名后重新构建全部静态文件 |
| 详情页 404 | 内容 ID、尾斜杠和上传结果 | 检查对应 dist/ 目录是否完整发布 |
| 样式或脚本 404 | dist/_astro/ 是否同步 |
确保部署整个 dist/,不要只复制 HTML |
| Caddy 返回 403 | 文件权限与 SELinux 标签 | 检查目录读取权限并执行 restorecon |
| HTTPS 申请失败 | DNS、TCP/80、TCP/443 | 确认解析生效、防火墙开放且端口未被占用 |
排查时应从浏览器、DNS、端口、Caddy、文件系统到构建产物逐层验证,避免同时修改代理、防火墙和应用配置后失去证据链。
当前边界与后续路线
当前版本没有搜索、评论、Analytics、CMS、登录、React Islands、远程部署配置和自动回滚。近期路线按风险从低到高排列:
- 替换个人信息、域名和 GitHub 占位值;
- 在真实域名上完成一次手工 Caddy 部署与验收;
- 固化 Caddy、SELinux、firewalld 和备份配置;
- 将已验证流程迁移到 GitHub Actions;
- 根据真实使用需求评估搜索、统计与版本化发布目录。
任何新功能都应回答三个问题:它解决了什么已发生的问题、是否必须增加客户端或服务端状态、故障时如何降级和恢复。
项目复盘
这个项目的主要价值不是“用 Astro 做了一个博客”,而是把内容、构建、展示和运维拆成边界清晰的层次。Markdown 是源数据,内容集合负责契约,页面与组件负责表达,Astro 负责生成,Web Server 只负责交付。
静态架构不会自动带来高质量,但它让问题更容易定位:内容错误在构建期暴露,页面错误可以直接检查生成文件,服务器故障集中在 DNS、TLS、端口、权限和文件服务几个层面。对个人技术站而言,这种可解释性比引入更多平台能力更重要。
当前源码已经具备可重复检查和静态构建能力;生产域名、真实服务器和自动部署仍需按本文的目标运维步骤实施。本文不使用虚构的性能分数、访问量或线上稳定性数据,后续只有在真实部署和持续观测后才补充可验证结果。