Astro 是一个面向内容网站的构建框架。它把 Markdown / MDX 当成一等公民,构建时把页面预渲染成纯 HTML,只在确实需要交互的组件上加载 JavaScript——官方把这种模式叫「群岛架构」(Islands)。
和 Hugo 一样,产物是静态文件,可以直接丢给 Nginx 或 GitHub Pages;不同的是 Astro 用 JS/TS 生态,页面是 .astro 组件,可以按需嵌入 React、Vue、Svelte 组件。
核心特点与优势:
内容集合(Content Collections):在 src/content.config.ts 里用 zod 给 front matter 定义 schema,字段写错、日期格式不对会在构建时直接报错,而不是上线后才发现。
默认零 JS:博客正文、列表页、RSS 都是构建时生成的静态 HTML,首屏没有框架运行时,加载快、SEO 友好。
组件化:导航、卡片、评论这些可以写成组件复用;需要交互时再单独挂一个 React/Vue 岛屿,不必全站背框架。
官方集成齐全:RSS、sitemap、图片优化、MDX、站点地图都是一条命令(npx astro add xxx)接入。
模板生态成熟:npm create astro@latest -- --template blog 直接得到一份可用的博客(含文章集合、RSS、暗色模式),改内容即可上线。
Markdown 能力完整:支持 front matter、代码高亮、GFM 表格和任务列表,需要时可以写 MDX 混入组件。
和 Hugo 的取舍:
| 维度 | Astro | Hugo |
|---|---|---|
| 构建速度 | 秒级,文章上千时差距明显 | 毫秒级到亚秒级,最快 |
| 技术栈 | Node + JS/TS,页面是组件 | Go 模板,纯配置文件驱动 |
| 可扩展性 | 高:可嵌 React/Vue/Svelte,能写任意逻辑 | 中:靠 shortcode、partial 和外部脚本 |
| 主题数量 | 少但质量高,官方 blog 模板够用 | 非常多,中文主题也多 |
| 学习成本 | 需要会一点 JS/TS | 会 Markdown 就能写,改模板要学 Go 模板 |
| 部署 | 静态产物,任意托管 | 静态产物,任意托管 |
适合「想要现代前端体验、以后可能往站点里加交互组件、不介意为此装 Node 工具链」的人;如果只想最快出一篇博客,Hugo 更省事。
Astro 的部署只有一条链路:本地或 CI 执行 npm run build,把 dist/ 里的静态文件放到 Web 服务器上。没有服务端进程,也不需要 Node 运行时。
| 维度 | 服务器 + 域名 | GitHub Pages |
|---|---|---|
| 费用 | 服务器 + 域名 | 公开仓库免费 |
| 国内访问 | 备案后稳定 | *.github.io 不稳定 |
| 构建位置 | 本地或服务器 | GitHub Actions |
| 可控性 | 高:任意 Nginx 规则 | 低 |
| 适合场景 | 正式博客、需要国内速度 | 试水、读者在海外 |
node -v、npm -v 有输出。dist/。用官方 blog 模板起步:
bashnpm create astro@latest myblog -- --template blog
cd myblog
npm install
生成的目录(模板结构,略有版本差异):
myblog/ ├── astro.config.mjs ├── package.json ├── public/ # 原样拷贝到 dist/ 的静态文件 ├── src/ │ ├── assets/ # 会被优化的图片 │ ├── components/ │ ├── content/ │ │ └── blog/ # 文章,一篇一个 .md / .mdx │ ├── content.config.ts # 内容集合的 schema │ ├── layouts/ │ └── pages/ # 路由,文件路径即 URL └── dist/ # 构建产物,不要提交到 Git
astro.config.mjs:
jsimport { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://example.com', // 必须和最终域名一致,RSS / sitemap 靠它生成绝对链接
// base: '/blog', // 只有部署在子路径(GitHub 项目站)时才需要
});
site 不做末尾斜杠;base 只在站点挂载在子目录时使用,写了之后所有内部链接都会带这个前缀。
模板的文章放在 src/content/blog/,front matter 由 src/content.config.ts 校验:
tsimport { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string().optional(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
一篇文章:
markdown---
title: 'Astro 博客部署笔记'
description: '从建站到上线的一条完整路径'
pubDate: '2026-10-25'
tags: ['部署']
draft: false
---
正文从这里开始。
pubDate 写错格式、漏了 title,npm run build 会直接失败——这是 Astro 相对 Hugo 最舒服的一点:错误提前暴露。
bashnpm run dev # http://localhost:4321,热更新
npm run build # 产物在 dist/
npm run preview # 本地预览 dist/ 的构建结果
发布前至少跑一次 npm run build && npm run preview,确认构建产物没问题,而不是只看 dev 的效果。
bashssh ubuntu@1.2.3.4
sudo apt update && sudo apt install -y nginx
sudo mkdir -p /var/www/myblog
sudo chown -R $USER:$USER /var/www/myblog
sudo ufw allow 'Nginx Full'
/etc/nginx/sites-available/myblog.conf:
nginxserver { listen 80; listen [::]:80; server_name example.com www.example.com; root /var/www/myblog; index index.html; location / { try_files $uri $uri/ =404; } location ~* \.html$ { add_header Cache-Control "no-cache"; } # Astro 的 /_astro/ 资源带内容哈希,可以放心长期缓存 location /_astro/ { expires 30d; add_header Cache-Control "public, max-age=2592000, immutable"; } error_page 404 /404.html; gzip on; gzip_types text/css application/javascript application/json image/svg+xml; gzip_min_length 1024; }
bashsudo ln -s /etc/nginx/sites-available/myblog.conf /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
bashsudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d example.com -d www.example.com sudo certbot renew --dry-run
服务器 IP 假设 1.2.3.4:
| DNS 服务商 | 配置入口 | 记录 | 注意点 |
|---|---|---|---|
| Cloudflare | NS 改到 Cloudflare → DNS → Records | A @ → 1.2.3.4、A www → 1.2.3.4 | 橙云走代理时 SSL/TLS 必须是 Full (strict);签证书期间先灰云 |
| 阿里云 / 腾讯云 | 云解析 DNS → 解析设置 → 添加记录 | A @ / www → 1.2.3.4,TTL 600 | 国内服务器先备案 |
| DNSPod | 域名解析 → 添加记录,主机记录 @ / www | 同上,TTL 600 | 改完等 TTL 过期再验证 |
bashdig +short example.com curl -I https://example.com
bashnpm run build rsync -avz --delete dist/ deploy@1.2.3.4:/var/www/myblog/
Windows Git Bash 没 rsync 时:scoop install rsync,或用 tar 管道:
bashtar -czf - -C dist . | ssh deploy@1.2.3.4 'find /var/www/myblog -mindepth 1 -delete && tar -xzf - -C /var/www/myblog'
astro.config.mjs 的 site 与最终域名一致npm run build 无报错,dist/index.html 存在dist/ 在 .gitignore 里,没有提交进仓库/_astro/ 资源返回 200,控制台无 404certbot renew --dry-run 通过--delete,删掉的旧文章不残留<用户名>.github.io,访问根路径。https://<用户名>.github.io/<仓库名>/,此时必须设置 base。推代码前确认 .gitignore 里有 dist/、node_modules/、.astro/。
仓库 Settings → Pages → Build and deployment → Source 选 GitHub Actions。
.github/workflows/deploy.yml(与 Astro 官方文档一致):
yamlname: Deploy to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install, build, and upload your site
uses: withastro/action@v6
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5
要点:
withastro/action 自己会装依赖、跑 build、把产物上传成 Pages artifact,不用手写 npm install。node-version 输入覆盖。base部署到 https://name.github.io/blog/ 时:
jsexport default defineConfig({
site: 'https://name.github.io',
base: '/blog',
});
同时所有手写的绝对链接(/about/ 这种)都要改成带前缀或相对路径,否则会跳到域名根。模板自带的链接如果是 import.meta.env.BASE_URL 拼的就不用改。
astro.config.mjs 的 site 改成 https://blog.example.com,删除 base。public/CNAME,不用再放这个文件。CNAME blog → <用户名>.github.io;根域名用四条 A 记录(185.199.108.153 / .109.153 / .110.153 / .111.153)。| 现象 | 原因 | 处理 |
|---|---|---|
| 页面能开、样式全丢 | base 没配或配错 | 项目站补 base,站点根域删掉 |
| 站内链接 404 | 手写绝对路径没带 base | 改相对路径或用 BASE_URL |
| Action 构建失败 | Node 版本 / 依赖锁文件 | 看日志,按需覆盖 node-version |
| RSS / sitemap 404 | 没装 @astrojs/rss / @astrojs/sitemap,或 site 未配 | 装集成,补 site |
| 自定义域一直 pending | DNS 未生效 / 记录被代理 | dig 验证,Cloudflare 先关代理 |
| 现象 | 优先检查 |
|---|---|
| 构建报 front matter 校验失败 | src/content.config.ts 的 schema 与文章字段是否一致 |
| 图片 404 | 放 public/ 的用 /xxx.jpg;放 src/assets/ 的必须 import 后用 |
| 中文路径打不开 | 文件名用英文 slug,标题写在 front matter |
| 改了文章线上没变 | 浏览器缓存;Nginx 是否给 .html 设了 no-cache |
| 部署后是旧版本 | 上传目录不对(应上传 dist/ 的内容,不是 dist 文件夹本身) |
模板已经给了大部分结构,下面这几项决定它「像个博客」。
导航在 src/components/Header.astro(模板命名可能略有差异),直接改链接数组:
astro--- const links = [ { href: '/', label: '首页' }, { href: '/blog/', label: '文章' }, { href: '/about/', label: '关于' }, { href: '/rss.xml', label: 'RSS' }, ]; ---
页脚里放 GitHub、邮箱、备案号(国内服务器要放 ICP 备案号并链接到工信部)。
bashnpm install @astrojs/rss npx astro add sitemap
RSS 端点 src/pages/rss.xml.js:
jsimport rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = await getCollection('blog', ({ data }) => !data.draft);
return rss({
title: '我的博客',
description: '记录与分享',
site: context.site,
items: posts.map((post) => ({
title: post.data.title,
pubDate: post.data.pubDate,
description: post.data.description,
link: `/blog/${post.id}/`,
})),
});
}
link 要与实际路由一致:模板的文章路由是 src/pages/blog/[...slug].astro,所以是 /blog/<id>/。sitemap 装好后访问 /sitemap-index.xml 验证。
在 layout 的 <head> 里保证有:
astro<title>{title}</title> <meta name="description" content={description} /> <link rel="canonical" href={new URL(Astro.url.pathname, Astro.site)} /> <meta property="og:title" content={title} /> <meta property="og:description" content={description} /> <meta property="og:type" content="article" /> <meta property="og:image" content={new URL('/og.png', Astro.site)} />
Astro.site 来自 astro.config.mjs 的 site,所以配置没写对,canonical 和 OG 全是错的。
静态站用 Giscus(基于 GitHub Discussions)。新建 src/components/Comments.astro:
astro<script is:inline src="https://giscus.app/client.js" data-repo="your-name/blog" data-repo-id="R_xxx" data-category="Announcements" data-category-id="DIC_xxx" data-mapping="pathname" data-reactions-enabled="1" data-theme="preferred_color_scheme" data-lang="zh-CN" data-loading="lazy" crossorigin="anonymous" async> </script>
data-repo-id 和 data-category-id 在 giscus.app 按仓库生成,别手填。把组件放在文章 layout 的正文之后即可;data-mapping="pathname" 让每篇文章对应一个 Discussion。
模板自带主题切换(ThemeToggle + localStorage);没有的话用 prefers-color-scheme 加一个手动切换按钮即可。阅读相关的两个建议:
65ch 左右;Markdown 在 Git 仓库,写作 = 提交,发布 = 推送触发构建。
改 src/content/blog/*.md → commit → push → GitHub Actions 跑 npm run build → Pages 部署 dist/(服务器路线则本地构建后 rsync)
Astro 比 Hugo 多一层 Node:手机上直接改 md 会触发 CI 构建,所以手机端不需要装环境;但本地预览和新增依赖必须在装了 Node 的机器上做。
src/content/blog/ ├── astro-deploy/ │ ├── index.md # 文章 │ └── cover.jpg # 图片跟着文章走 └── hugo-deploy.md # 也可以单文件
./cover.jpg;放 public/ 时用 /images/xxx.jpg;放 src/assets/ 时必须 import 才能享受图片优化。draft: true 的文章模板默认不会出现在列表和 RSS 里,确认要发再改 false。| 方案 | 平台 | 优点 | 代价 |
|---|---|---|---|
| GitHub 网页 / GitHub Mobile | 全平台 | 零安装,改 md 即触发构建 | 长文编辑弱;不能本地预览 |
| Obsidian + Git | iOS / Android / 桌面 | 本地库、离线写、可直接 push | 手机端冲突要手动合并 |
| Working Copy | iOS | 完整 Git 客户端 | 付费解锁推送 |
| IntelliJ / VS Code | 桌面 | 可 npm run dev 实时预览 | 需要 Node 环境 |
git pull,写完立刻 push;不要两台设备同时挂未提交改动。post: 文章标题。package.json 与锁文件,否则 CI 装不到。astro.config.mjs 后必须本地 npm run build 验证一次,配置错误只有在构建时才暴露。deploy.sh(GitHub Pages 路线删掉 rsync 那行):
bash#!/usr/bin/env bash
set -euo pipefail
msg="${1:-post: 更新}"
git pull --rebase
npm ci
npm run build
git add -A
git commit -m "$msg" || echo "没有需要提交的改动"
git push
# 服务器路线
rsync -avz --delete dist/ deploy@1.2.3.4:/var/www/myblog/
echo "已发布 $(date '+%F %T')"
npm ci 要求仓库里有 package-lock.json,没有就改成 npm install 并把它提交上去——CI 里的构建可复现性全靠这个锁文件。
本文作者:Weee
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!