之前偶然看到了 Astro 这个框架,据网友所述是“快速且美观”的现代化框架、适合用来做博客。原先我用的是hexo + github.io 的博客方案,最近正好在考虑搬上自己的域名并自己部署。机会难得,刚好来学学 Astro。
准备工作
首先自然是了解一下 Astro:官网。
The web framework for content-driven websites 面向内容驱动型网站的 Web 框架
也就是说,它“擅长静态生成、SEO 优化、Markdown/MDX 支持,让内容作者可以专注于写作,框架自动负责生成页面和性能优化”,适合用来做博客、技术文档、企业官网等等。
既然看起来符合我们的需求,就开始准备它所需的环境吧。实际上需要的就是Node.js - v22.12.0 或更高版本。可以直接前往Node.js 官网安装适合自己机器的版本。
第一个 Astro 项目
1. 安装 Astro
Astro 官方说:
Astro 必须安装在本地,不能全局安装。
也就是说,Astro 不建议将其作为全局可用 CLI 安装,而是作为项目级依赖。
那么,如果要创建一个新的空 Astro 项目,要做的就很简单了:
npm create astro@latest
这会在你的终端里启动一个交互式的安装向导,要做的就是在安装向导里选择项目目录并一路回车。
2. 选择模板
针对不同程度的懒人, Astro 贴心地提供了一种偷懒方式:模板
使用 Blog 模板
在安装过程中,会有这样的环节:
如果想完全自主完成自己的博客,可以选第一个,从零开始写基础页面。但是对于像我一样的懒人,使用模板要轻松多了。这里选择blog template的话,它会提供一个具备文章列表、标签分类、RSS订阅的代码的项目,看上去大概是这样:
如果你觉得它提供的这个模板太丑了,那么还有更进一步的偷懒方案。
使用社区主题
Astro 同样可以用社区主题当作模板来创建一个新项目,在Astro 主题里有大量可供选择的主题,可以在里面选一个自己喜欢的,然后用这个主题创建一个新项目,这里以astro-paper为例:
npm create astro@latest -- --template satnaing/astro-paper
在安装向导里填入新项目的目录,一路回车即可。
3. 启动开发服务器
cd 到你的博客项目的目录,启动开发服务器:
npm run dev
默认会在 localhost:4321 启动,直接在浏览器打开 http://localhost:4321 即可看到你选择的主题和一些示例文档了。
项目结构
现在我们有一个 Astro 项目了,或许还有一个符合审美的主题,那么,该从哪里开始写作呢?
打开这个新项目的目录,其结构大致如下(不同的模板下,结构可能稍有差别,但是主体都是一样的):
文字描述就是:
my-blog/
├── src/
│ ├── components/
│ ├── content/
│ ├── layouts/
│ └── pages/
├── public/
├── astro.config.mjs
└── package.json
这其中,主要几个目录的作用:
src/content/: 实际写文章的地方,将md或mdx文件放在这里src/components/:页面组件,比如 Header、Footer、Card 等src/layouts/:布局模板src/pages/:路由页面,例如首页、posts、tags什么的public/:静态资源,会被原样复制到站点根目录
如果没有太大的自己动手的意愿,那只需要关心src/content/就行,在这里组织好自己的md文档。有些主题可能会要求md文档的开头必须写明某些属性,例如:
---
title: 文章标题 # 页面 H1 与浏览器标题
pubDatetime: 2026-01-01T10:00:00Z # 发布时间(ISO 8601,按 timezone 解释)
description: 文章摘要 # 列表页摘要 / SEO meta
---
这一点要结合实际主题/代码确定。
关于 AstroPaper
下面是一些我在使用AstroPaper这个主题过程中做的一些配置。
Astro的配置集中在根目录下的astro-paper.config.ts,逐一修改即可。
这里只列出了较为重要的三个部分:
1. site
site: {
url: "https://example.com/", // 线上域名,必须改成你真正的域名
// 影响 canonical、OG 图、RSS、sitemap
title: "我的博客", // 站点名称
description: "站点描述", // SEO 描述
author: "你的名字", // 默认作者名
profile: "https://example.com",// 作者主页(结构化数据用,可选)
ogImage: "default-og.jpg", // 默认分享图,放在 public/ 下
lang: "zh-CN", // 页面 lang 属性
timezone: "Asia/Shanghai", // 文章日期时区
dir: "ltr", // 文字方向:ltr(从左到右)
googleVerification: "xxx", // 可选:Google Search Console 验证码,不要将真实验证码提交到公开仓库
}
需要注意的是,只是把lang修改为zh-CN不会自动变更语言,还需要自行完成一些工作,这些我写在最后一节了。
2. posts
posts: {
perPage: 4, // 列表页每页文章数
perIndex: 4, // 首页展示的文章数
scheduledPostMargin: 15*60*1000, // 定时发布容差:发布时刻 15 分钟内的文章也会被当作已发布
}
3. features
features: {
lightAndDarkMode: true, // 明暗主题切换
dynamicOgImage: true, // 为每篇文章动态生成 OG 分享图
showArchives: true, // 归档页
showBackButton: true, // 文章页返回按钮
editPost: { // 文章页的"编辑此页"链接
enabled: true,
url: "https://github.com/<你的账号>/<仓库>/edit/main/",
}, // 公开仓库可用,私有仓库应关闭
search: "pagefind", // 站内搜索(Pagefind);不需要可设 false
}
自部署
假定以上工作都已经完成,打开开发服务器,能够看到足够让人满意的页面,接下来就该折腾自部署了。
以下部分默认前提:一台属于你的、有公网IP的计算机(服务器),一个域名。
大体思路:GitHub Actions 自动构建 + SSH 推送
为什么要自部署
如果问我的话,大概就是喜欢折腾吧。比起直接发在平台或者博客托管,这种方式麻烦些,但是能带来不小的成就感。
功利地讲,部署的过程可以学习到基础的运维知识,从 ssh 连接服务器到反代,基本算是完成了一次最基础的网站上线流程。
配置远程仓库
在项目初始化时,会询问是否要初始化git仓库,如果选yes,当前这个项目应该会有一个git仓库。如果没有,也可以直接在当前目录执行git init。总之,这个目录下应该有.git子目录。
在github上创建一个空仓库,关联本地仓库并推送:
git remote add origin git@github.com:<你的账号>/<仓库名>.git
git push -u origin main
服务器端的准备
1. 准备 rsync
后面通过github action自动推送需要 rsync 同步,需要安装 rsync:
sudo apt install rsync
1. 生成 SSH 密钥
通过 SSH 登录服务器,在服务器端生成 SSH 密钥。
这里我建议干脆新建一个用户,给他最基础的权限即可,免得密钥泄漏出大问题。
# 建低权限部署用户,并让它拥有 /opt/blog
sudo useradd -m -s /bin/bash deploy # -m 自动创建家目录,-s制定Shell为Bash
sudo mkdir -p /opt/blog && sudo chown deploy:deploy /opt/blog # 创建目录并将所有权给deploy
# 生成部署 key,并把公钥授权给 deploy
sudo -u deploy mkdir -p /home/deploy/.ssh && sudo -u deploy chmod 700 /home/deploy/.ssh # 创建~/.ssh目录,并设置权限为700
sudo -u deploy ssh-keygen -t ed25519 -f /home/deploy/.ssh/deploy_key -N "" # 生成一对 Ed25519 算法的 SSH 密钥。放到指定文件中,-N 无密码,适合自动化脚本
接下来,复制 deploy_key 中的内容即可。
2. nginx 反代
- 写站点配置
/etc/nginx/sites-available/blog.conf
server {
listen 80;
server_name 域名;
root /opt/blog/dist;
index index.html;
location / { try_files $uri $uri/ =404; }
location ~* \.(css|js|png|jpg|svg|woff2?)$ { expires 7d; }
}
倒数第二行,try_files按顺序查找物理文件/目录,都不存在就返回 404,适合纯静态站点,不做 SPA 回退。
最后一行的作用是设置缓存,告诉浏览器,访问一次之后,将.css/.js/.png等格式的文件缓存下来,下次访问就不需要重新加载已缓存资源了。而7d表示缓存7天。后续更新同名文件时,浏览器可能因缓存拿不到最新内容,可以考虑给静态资源加 hash 文件名。
- 启用站点:
sudo ln -s把它软链进sites-enabled/ - 校验、生效:
nginx -t(无错) →systemctl reload nginx
截至这一步,其实已经可以通过http访问你的网站了(当前是空页面),如果想用https,则需要申请SSL/TLS证书。
- 补 HTTPS
通过Certbot,可以快捷地申请到 Let’s Encrypt 证书,即便是个人也能轻松启用 HTTPS 加密。
安装 Certbot,以 Ubuntu 为例(这里用的是 apt ,或者你也可以用snap):
sudo apt update
sudo apt install certbot
然后要做的只有一个操作:
certbot --nginx -d 域名 --redirect --non-interactive --agree-tos
参数从前到后的作用是:
- —nginx:使用 Nginx 插件,这会自动修改 Nginx 配置文件来启用 HTTPS
- -d 域名:指定要申请证书的域名
- —redirect:自动修改 Nginx 配置,确保所有访问
http://的请求都永久跳转到https:// - —non-interactive:非交互式,运行过程中不会询问
- —agree-tos:配合非交互模式,提前说好同意 Let’s Encrypt 订阅协议
执行过程中,certbot会尝试确定你是否真的拥有该域名的控制权、自动修改 Nginx 的配置,重载 Nginx 、并且把域名登记进续期队列,到期自动续期。
使用github action实现自动推送
服务器端的准备到此为止,在我们的方案里,服务器不需要Node运行环境,也不需要git。
准备 secret
打开一开始准备的远程仓库,在Settings → Secrets and variables → Actions里,新建三个仓库级 secret:
SSH_PRIVATE_KEY= 私钥全文(上面取的)SSH_HOST= 服务器IPSSH_USER=deploy
创建 github workflow
在仓库中创建.github/workflows/deploy.yml,内容示例:
name: Deploy
on:
push:
branches: [main] # 当有push到main分支上时触发
workflow_dispatch: # 也可以网页端手动点一下
permissions:
contents: read # 对仓库只读
jobs:
deploy:
runs-on: ubuntu-latest # 指定虚拟机环境
steps:
# 使用官方的checkout,将代码签出到虚拟机工作目录
- name: Checkout
uses: actions/checkout@v4
# 设置 Node.js 环境,安装24版本的Node.js
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24"
cache: "npm"
# 安装依赖并构建项目
- name: Install & Build
run: |
npm ci
npm run build
# 部署到远程服务器
- name: Deploy via SSH (rsync)
uses: easingthemes/ssh-deploy@main
# 传递给action的环境变量
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.SSH_HOST }}
REMOTE_USER: ${{ secrets.SSH_USER }}
REMOTE_PORT: "22" # 这里要修改为ssh端口,默认22
SOURCE: "dist/"
TARGET: "/opt/blog/dist"
ARGS: "-rltgoDzvO --delete" # 传递给rsync的额外参数,具体可根据文档核对
在本地commit并push你的更改,打开github直到action变绿:
大功告成!
现在通过https+域名即可访问博客,以后每次更新文章后,git commit+push到远程仓库就会自动触发github action,它会自动构建页面并同步到服务器,实现实时的更新。
踩坑实录
1 本地化
原先把lang修改为zh-CN后,我以为就会自动变更语言,但是实际上并没有。研究了下发现,主题实际上只提供了英文:在src/i18n/lang/目录下只有en.ts,所以想完成本地化,还需要自己写对应的中文:
- 翻译
en.ts这是en.ts中的一小段:
nav: {
home: "Home",
posts: "Posts",
tags: "Tags",
about: "About",
archives: "Archives",
search: "Search",
},
创建一个新的zh-CN.ts,逐一翻译上面的字符串:
nav: {
home: "首页",
posts: "文章",
tags: "标签",
about: "关于",
archives: "归档",
search: "搜索",
},
- 把
astro.config.ts的i18n.locales加上"zh-CN",将astro-paper.config.ts里的lang改为zh-CN即可
2 博客属性
这篇博客一开始设置pubDatetime属性,我习惯性模仿主题示例(2026-09-05T21:38:20Z),写了当前的时间(北京时间 UTC+8),后面本地构建预览时没看到这篇博客,研究半天才知道文章开头的pubDatetime如果最后有Z,用的就是UTC时间,我写的北京时间,UTC要等8个小时才到,等那时候文章才能看到。
解决方案就是自己减8小时写上去或者别加Z(这样就会按配置的时区来),不是支援未来就行。