跳转到内容
l-library's blog
返回

自给自足:使用 Astro 搭建个人博客并自部署

之前偶然看到了 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文档。有些主题可能会要求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 反代

  1. 写站点配置 /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 文件名。

  1. 启用站点:sudo ln -s 把它软链进 sites-enabled/
  2. 校验、生效:nginx -t(无错) → systemctl reload nginx

截至这一步,其实已经可以通过http访问你的网站了(当前是空页面),如果想用https,则需要申请SSL/TLS证书。

  1. 补 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

参数从前到后的作用是:

执行过程中,certbot会尝试确定你是否真的拥有该域名的控制权、自动修改 Nginx 的配置,重载 Nginx 、并且把域名登记进续期队列,到期自动续期。

使用github action实现自动推送

服务器端的准备到此为止,在我们的方案里,服务器不需要Node运行环境,也不需要git。

准备 secret

打开一开始准备的远程仓库,在Settings → Secrets and variables → Actions里,新建三个仓库级 secret:

创建 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,所以想完成本地化,还需要自己写对应的中文:

  1. 翻译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: "搜索",
},
  1. 把 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(这样就会按配置的时区来),不是支援未来就行。



上一篇
“赛博做旧”踩坑记录
下一篇
从零开始的 aget 开发:核心循环