今天是我独立博客走过的第八个年头。还记得那一年怀着对独立站的疑问,给孔令贤发邮件,询问是否可以使用他写的轮子,就是从他回复我那一刻起,我掉进了 WEB 深渊。
独立博客这个词,在 2025 年这个年代确实足够小众,但其中的快乐和对生活的态度,想必也只有博友能理解。
正是为了这第八个年头,才有了今天这全新的博客。从年初用 Jekyll 从零开始写,后来又用 Recat 写个半成品。最终阴差阳错选择了开源的 Astro Paper。
Astro Paper 这款主题性能极强,可拓展性也非常高,这也得益于 Astro 的静态特性和原作者优越设计。
经过一段时间的二次开发,这个博客差不多达到了我理想的样子。
在全站无缝刷新的基础下,我把博客全站的图片都做了懒加载,订阅和归档模块也做了滚动懒加载。
再加上页面内链的预加载处理,无论你点击哪个页面,都是一种享受。
Lighthouse 评分
下面就把新增的功能一一道来。
分类路由支持
Astro Paper 原生不支持平铺式 URL,也不能把文章进行分类:
改进后:
- 文章可按分类(技术、生活、运动等)划分路由。
- 支持按年份归档,且不会影响已有 URL 访问。
- URL 更加简洁:
分类路由结构如下,可在/src/pages/中按需创建:
blog/ ├── _releases/ ├── examples/ ├── life/ │ ├── 2024/ │ └── 2025/ ├── sports/ │ ├── 2024/ │ └── 2025/ └── technology/ ├── 2024/ └── 2025/路由中间件处理
在不用 Artalk 评论系统的情况下,这个功能其实可有可无。但 Artalk 在路径识别上,存在很大的问题(带斜杠与不带斜杠会被视为不同页面),存在一定的隐患。
其实使用 Nginx 会更方便一些。
import { defineMiddleware } from "astro:middleware";
export const onRequest = defineMiddleware((context, next) => { const url = context.url; const pathname = url.pathname;
const staticExtensions = [ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.css', '.js', '.json', '.xml', '.txt', '.pdf', '.woff', '.woff2', '.ttf', '.eot', '.otf' ];
const isStaticResource = staticExtensions.some(ext => pathname.toLowerCase().endsWith(ext) );
// 如果是静态资源,不处理尾部斜杠 if (isStaticResource) { return next(); }
// 如果URL不以斜杠结尾且不是根路径,则重定向到带斜杠的版本 if (pathname !== "/" && !pathname.endsWith("/")) { const newUrl = new URL(pathname + "/" + url.search + url.hash, url.origin); return Response.redirect(newUrl.toString(), 301); }
return next();});运动数据可视化
接入 Strava Riding Api 做了运动数据的可视化。
- 光标悬浮可切换月数据
- 点击日期可查看当天运动详情
EXIF 元数据显示
借助腾讯云数据万象 API,默认自动启用。
- 参数为 Boolean 类型,false 可禁用
- 光标悬浮显示两秒
- 解析失败会自动生成合理参数并缓存
import Img from "@/components/Img.astro";
<Img src="20250524003018.jpg" />图片标签
所有图片默认使用长标签,支持切换为短标签或禁用标签
若想在文章中启用 EXIF,需要将 .md 改为 .mdx,并引入组件:
import Img from "@/components/Img.astro";
<Img src="20250530173339.jpg" alt="义乌美术馆一角" caption="short" // false 表示不显示/>悬停提示(tooltip)效果
图片的 title 属性不必声明,只要有 alt 属性,Img.astro 组件就会自动读取并渲染到页面中。
数学公式支持
通过 KaTeX 集成 支持了数学公式。纯静态渲染,无性能问题。示例:
$$\text{骑行里程} = \text{均速} \times \text{时间}$$Artalk 集成
说到评论系统,首先感谢 Disqus PHP API 开源作者 Fooleap,感谢好大哥这些年来帮我在境外挂着接口…
Artalk 官方提供了简单的配置文件,不过足够了
services: artalk: container_name: artalk image: artalk/artalk-go restart: unless-stopped ports: - 9998:23366 volumes: - ./data:/data environment: - TZ=Asia/Shanghai - ATK_LOCALE=zh-CN - ATK_SITE_DEFAULT=游钓四方的博客 - ATK_SITE_URL=https://lhasa.icu创建容器运行 Artalk:
docker-compose up -d
# 执行命令创建管理员账户docker exec -it artalk artalk admin再使用 Nginx 反代 9998 端口就可以实现域名访问了。
由于我是 Disqus 迁移过来的,需要把格式转换为 Artrans,然后再导入 Artalk。
由于无缝刷新的存在,就单单评论来说,调试花了不少时间,踩了很多坑,这里还把 Artalk 随着主题变化适配了配色。
<script is:inline data-astro-rerun>(function () { // 单例模式存储 Artalk 实例 window.artalkInstance = window.artalkInstance || null;
const artalkConfig = { el: "#Comments", server: "https://artalk.lhasa.icu", site: "游钓四方的博客", pageKey: window.location.pathname, vote: false,
};
function destroyArtalk() { if (window.artalkInstance) { try { window.artalkInstance.destroy(); document .querySelectorAll(".atk-sidebar, .atk-layer-wrap") .forEach(el => el.remove()); window.artalkInstance = null; console.log("Artalk 实例已销毁", window.location.pathname); } catch (err) { console.error("销毁失败:", err); } } }
// 初始化 Artalk 实例 function initArtalk() { const container = document.getElementById("Comments"); if (!container || container.querySelector(".atk-app")) return;
const isDark = document.documentElement.getAttribute("data-theme") === "dark";
artalkConfig.pageKey = window.location.pathname; artalkConfig.darkMode = isDark;
window.artalkInstance = Artalk.init(artalkConfig); console.log("Artalk 初始化完成", window.location.pathname); }
function handleThemeChange() { const themeBtn = document.querySelector("#theme-btn"); if (!themeBtn) return;
const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.attributeName === "aria-label") { const isDark = mutation.target.getAttribute("aria-label") === "dark"; if (window.artalkInstance) { window.artalkInstance.setDarkMode(isDark); } } }); });
observer.observe(themeBtn, { attributes: true, attributeFilter: ["aria-label"], }); }
function handlePageLoad() { destroyArtalk(); initArtalk(); handleThemeChange(); }
function setupArtalk() { if (window._artalkInitialized) return; window._artalkInitialized = true;
document.addEventListener("astro:before-swap", destroyArtalk); document.addEventListener("astro:after-swap", handlePageLoad);
if (document.readyState === "complete") { handlePageLoad(); } else { document.addEventListener("DOMContentLoaded", handlePageLoad); }
// 监听主题 window .matchMedia("(prefers-color-scheme: dark)") .addEventListener("change", ({ matches }) => { if (window.artalkInstance) { window.artalkInstance.setDarkMode(matches); } }); }
setupArtalk();})();</script>Artalk 自带的验证码不好用,这里强烈推荐 Cloudflare Turnstile。无感验证,很省心。
在 Cloudflare 控制台主页可以看到 Turnstile,在填完域名后可以申请到Site Key和Secret Key。
随后打开 Artalk 控制中心,填入相应参数后,captcha_type选择turnstile即可。
本地开发
纯净版 Astro Paper:
# pnpmpnpm create astro@latest --template satnaing/astro-paper
# npmnpm create astro@latest -- --template satnaing/astro-paper
# yarnyarn create astro --template satnaing/astro-paper或者直接使用我的扩展版本:
git clone https://github.com/achuanya/Blog.git然后通过安装依赖启动开发环境
# 安装依赖pnpm install
# 启动开发环境pnpm devDocker 部署
用于生产环境的 Docker 配置已经写好了,可以直接构建镜像。
# 构建生产镜像docker build -t astropaper .
# 启动生产环境,端口为 4321docker run -p 4321:80 astropaper配合 Nginx 反代:
server { listen 80; server_name lhasa.icu;
# 404 error_page 404 /404.html; location = /404.html { root /home/github/Blog/dist; internal; }
location / { proxy_pass http://127.0.0.1:4321; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}不想折腾,建议安装宝塔Linux面板,随便点击几下,两分钟上线
if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484becGithub + Vercel 部署
只需点击 Deploy 按钮,按提示一步步即可上线。
相关命令
| Command | Action |
|---|---|
pnpm install | 安装依赖项 |
pnpm run dev | 启动本地开发服务器,访问地址为 localhost:4321 |
pnpm run build | 将生产环境网站构建到 ./dist/ 目录 |
pnpm run preview | 本地预览生产环境构建的站点,部署前检查效果 |
pnpm run format:check | 使用 Prettier 检查代码格式 |
pnpm run format | 使用 Prettier 格式化代码 |
pnpm run sync | 为所有 Astro 模块生成 TypeScript 类型。 了解更多。 |
pnpm run lint | 使用 ESLint 进行代码检查 |
docker compose up -d | 使用 Docker 运行 AstroPaper,可通过 dev 命令中相同的主机名和端口进行访问 |
docker compose run app npm install | 在 Docker 容器中执行任意上述命令 |
docker build -t astropaper . | 为 AstroPaper 构建 Docker 镜像 |
docker run -p 4321:80 astropaper | 在 Docker 中运行 AstroPaper。网站可通过 http://localhost:4321 访问 |
注意!
Windows PowerShell 用户如果想在开发期间运行诊断(例如astro check --watch & astro dev),可能需要安装 concurrently 包。
更多信息请参考 这个 issue。
下一步
目前来说,还有很多地方没有完善,细节没有做到位:
- Strava Riding Api 还没有实现完全自动化,更新数据还是需要人工
- Img.astro 组件没有封装到位,还有细节需要把控
- Sports 在移动端时的表现还需要好好想想
- 给 Feeds 做个后台管理,先把头像显示问题解决了。当然,有邮箱最好
- 吃透 Astro Paper 无缝刷新机制