惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Announcements
Recent Announcements
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Last Week in AI
Last Week in AI
Scott Helme
Scott Helme
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LINUX DO - 最新话题
S
Security @ Cisco Blogs
Webroot Blog
Webroot Blog
S
Security Affairs
H
Hacker News: Front Page
TaoSecurity Blog
TaoSecurity Blog
W
WeLiveSecurity
G
GRAHAM CLULEY
T
Tenable Blog
Schneier on Security
Schneier on Security
S
Securelist
Cyberwarzone
Cyberwarzone
P
Privacy International News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Schneier on Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recent Commits to openclaw:main
Recent Commits to openclaw:main
O
OpenAI News
N
News and Events Feed by Topic
AWS News Blog
AWS News Blog
C
Cisco Blogs
T
Threat Research - Cisco Blogs
S
Secure Thoughts
大猫的无限游戏
大猫的无限游戏
C
Check Point Blog
The GitHub Blog
The GitHub Blog
G
Google Developers Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
美团技术团队
Martin Fowler
Martin Fowler
Microsoft Security Blog
Microsoft Security Blog
L
LangChain Blog
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
D
DataBreaches.Net
博客园_首页
MyScale Blog
MyScale Blog
博客园 - 叶小钗
博客园 - 三生石上(FineUI控件)
P
Proofpoint News Feed
J
Java Code Geeks
SecWiki News
SecWiki News
P
Palo Alto Networks Blog
Know Your Adversary
Know Your Adversary
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org

澄沨的漫游茶记

岁暮杂记 在 KOReader 上显示直排文本 虎落平阳:TNO 美国罗伯特·F·肯尼迪线·前篇 烟火·乙巳 蜀都行纪 实习日 事故小记 烟火·甲辰 藏地行纪 元旦杂记 浴火重生:TNO 东方红西北国防阵线党权线·后篇 太阳旗之下:TNO 东方红西北国防阵线党权线·前篇 尚在人间:TNO 乌克兰-苏维埃社会主义共和国线 上海电信 SDN 网关全局域网皆可收看 IPTV 的实现 论 AList 的滥用:多平台串联搭建个人影音-番剧流媒体服务 俄罗斯已逝:TNO 俄罗斯科米-神俄线·后篇 紫日高悬:TNO 俄罗斯科米-神俄线·前篇 The Pure WINDOWS Project: 使用 Windows Sandbox 隔离腾讯系软件的笔记 廣東手艺人:TNO 广东国索尼-长江实业线 使用 PaaS 服务部署 umami 统计工具的笔记 柏林春晓:TNO 德国施佩尔左线 闲话 COMICUP 与敝站 解决小米手机在 fastboot 模式下不被 Windows 识别的问题 贺正·癸卯 MIUI 下观看 Netflix 等流媒体时 Widevine DRM L1 认证失效之解决 崇明骑行纪事·合 磊哥小传 崇明骑行纪事·转 让 Hyper-V 虚拟机支持调用 USB 摄像头 崇明骑行纪事·起承 炎夏小憩 飞哥小传 さよなら、WordPress 使用 Cloudflare Workers 加速甲骨文存储桶 博客流浪纪事 moe-counter-cf:将萌萌计数器部署到 Cloudflare Workers 烟火·迪士尼 红十字救护员培训小记 迁移到了狗云 探索优雅纯粹的追番体验:蜜柑计划 + Resilio Sync Moe-counter: 给网站装上多种风格可选的萌萌计数器 疫情,在下的朋友圈拾遗 Postcrossing 初探小记 解决 Github 同步复刻上游仓库时冲突的问题 使用甲骨文免费云服务器搭建 frp 内网穿透工具的笔记 「在下」的两周年 The Pure WINDOWS Project: Hyper-V 虚拟机使用笔记 The Pure MIUI Project: 小米 10 刷机笔记 烟火·壬寅 贝恩小传「大盛り」 非法侵入住宅罪之浅究 碧蓝航线舰娘原型考据「铁血驱逐篇」 贰拾 火警纪事 观 EDG 夺冠有感 华硕路由器实现远程唤醒局域网设备的笔记 军训随笔 夏日蓝调 在 Wordpress 博客里创建彩色标签云页面的笔记 贝恩小传 迁移到了 Bandwagon Host 杂诗二首 来给 WordPress 添加一个书单页面吧 乡影随摄 从刑法的报复主义论中国刑法的发展 漫谈二次元的「破圈」 浅谈「FBI WARNING」 とある魔都の ACGN 巡礼目録 桜色 在 WordPress 上添加 moc3 Live2d 看板娘的超简单方法 COVID-19 疫苗接种全纪录 Steam 转区小记 元旦 更新主题为 Sakurairo 华为 HarmonyOS / EMUI 安装 GMS 谷歌框架的笔记 下载没有下载权限的钉钉直播回放的笔记 Re:从零开始的 Windows 简明优化指南 Re:从零开始的计算机使用指南 使用 ADB 工具箱卸载手机预装应用 基于宝塔面板的 Google Drive 百宝箱搭建笔记 The Story of the Snake | 蛇纪事 参议院纪事 箱轮纪事
こんにちは、Astro - 澄沨的漫游茶记
澄沨 · 2026-07-25 · via 澄沨的漫游茶记
NOTE

newskin | 养病中低浮上

迁移背景#

自把敝站从 WordPress 迁到 Hexo,已三年有余。彼时 Astro 才刚刚发布第一个 Beta 版本不久,尚不为在下所知。直到 2023 年底,在下才终于了解到这个推行「群岛架构」的框架,不过当时的 Astro 社区生态还处于起步阶段,对于低技术力的在下来说,将其作为博客框架来用还为时尚早。况且,Volantis 主题的优化做得还不错,在下也没有在博客里塞进大量的 JavaScript 资源,因而 Lighthouse 评分还是看得过去的。另外,敝站的部署早就扔到了 Serverless 服务上运行,对部署时间的长短并不敏感,对服务器渲染更是没有要求,因此,没有必要追求那一点点性能提升而放弃现有成熟的解决方案。

虽然如此,在下依旧对 Astro 的 View Transitions API 印象深刻,无论是切换页面时的体验还是平滑的原生动画,都是老旧的 Hexo 打补丁所不能及的。因此,在下还是在心中暗自决定了下一代的敝站将会由 Astro 驱动,并为此持续关注着社区当中的新主题。

在下最早关注的主题是 fuwari,基于 MD3 风格的设计一下子抓住了在下的眼睛。不过 fuwari 依旧缺乏一些基本的博客功能,例如文章目录(现在已经支持)、评论模块和友链页面,虽然可以自己手搓,不过对于在下来说还是有些费时费力,仅仅只是将其拉到了本地略微研究了一番,便就此作罢。

随着时间的推移,更多有趣的主题开始涌现,其中基于 Hexo 主题 Shoka 设计的 koharu 吸引了在下的注意。Hexo 的 Shoka 本就是在下比较喜欢的主题,但在下同时也注意到更多基于 fuwari 修改的主题开始出现,例如 TwilightMizuki 等,都是在下比较中意的。最终,在下在左挑右拣中选定了 MD3 设计的 Mizuki。

迁移备忘#

比起上次从 WordPress 迁移出来,这次的迁移就要方便的多了。首先,由于文章都是以 MD 文档的形式储存,只需要简单的复制粘贴,改一下 Frontmatter,就可以直接用了,唯一需要注意的是 Volantis 主题的特色功能代码需要适配新主题,不过这充其量只是字符串替换,无论是手动 Ctrl + F 之后批量替换还是直接让 AI 修改都不算费事。

而在上次迁移花费最多精力的评论方面,由于之前使用的是 twikoo 评论系统,只需要引入前端 JS 并连接到之前的数据库,就可以像模块一样方便地在不同的博客之中插拔。

值得一提的是,Mizuki 给 twikoo 写的样式还是基于 1.7.6 的,而在在下部署博客的时候,发现其对当时 twikoo 最新的 1.7.13 的适配有问题,为此拷打了一番 AI,提交了 PR 把这个问题给解决了。

最终,在迁移过程中最花时间的项目落到了对主题的微调上,由于这是在下首次接触到 Astro 项目,花了一点时间才搞明白文件结构,特此把一些重要的修改项目列出来备忘。

字体#

Mizuki 之前的字体配置在 src/config/siteConfig.ts 中调整,不过后来由于一些问题,转而直接使用 Astro 原生的字体 API 配置,需要到 astro.config.mjs 中修改。

Astro 的字体 API 可以非常方便地引用敝站使用的谷歌字体:

export default defineConfig({

fonts: [

{

provider: fontProviders.google(),

name: "Noto Sans",

cssVariable: "--font-body",

weights: [400, 600],

},

{

provider: fontProviders.google(),

name: "Noto Sans SC",

cssVariable: "--font-cjk",

weights: [400, 600],

},

{

provider: fontProviders.google(),

name: "Lobster",

cssVariable: "--font-lobster",

},

]

});

需要注意的是 Mizuki 必须要有 --font-body--font-cjk 这两个 CSS 变量,修改的时候要确保这两个变量被正确配置,不然会报错。

在 astro.config.mjs 中引入之后,就可以在 src/styles/main.css 中进一步自定义了,例如敝站:

@theme {

--font-sans: var(--font-body), var(--font-cjk), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";

--font-display: var(--font-lobster), var(--font-body), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";

--font-title: var(--font-rampart-one), var(--font-body), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";

}

如果在 astro.config.mjs 定义了新的字体变量,不要忘记在 src/layouts/Layout.astro 中引入,否则不会有效果,例如敝站引入了 --font-lobster

<!doctype html>

<html

lang={siteLang}

class="bg-(--page-bg) text-[14px] md:text-[16px]"

data-overlayscrollbars-initialize

data-waves-config-enabled={siteConfig.banner?.waves?.enable ? "true" : "false"}

data-banner-title-config-enabled={siteConfig.banner?.homeText?.enable ? "true" : "false"}

data-post-list-layout-enabled={siteConfig.postListLayout?.enable ? "true" : "false"}

>

<head>

<meta charset="UTF-8" />

<Font cssVariable="--font-body" preload />

<Font cssVariable="--font-cjk" />

<Font cssVariable="--font-jetbrains-mono" />

<Font cssVariable="--font-lobster" />

<Font cssVariable="--font-rampart-one" />

...

顶栏菜单#

Mizuki 使用 Tailwind CSS,只需找到位置,调整起来还是很方便的。

  • 调小元素之间的间距:在 src/components/organisms/navigation/DropdownMenu.astro 的第 44 行及 106 行处分别调小 lg:px-5 的值
  • 防止左侧 LOGO 被挤压后整体下移:在 src/components/organisms/navigation/Navbar.astro 的第 103 行处删除 shrink-0
  • 二级菜单取消右侧保留空白:在 src/components/organisms/navigation/DropdownMenu.astro 的第 153 行处把 min-w-[12rem] 改为 min-w-max

永久链接#

Mizuki 的文章链接默认存在 src/contest/posts 下,使用默认的文件名作链接,默认链接路径为 /posts/文件名/ ,如果不想要前面的 /posts/ ,则需要开启 src/config/permalinkConfig.ts 中的 PermalinkConfig 功能。不过,即便开启永久链接之后,旧有的带 /posts/ 的链接依旧能访问到文章。

这里一些值得注意的问题,一是使用永久链接访问的文章,底部的上下文按钮依旧会导航到带 /posts/ 的链接;二是最底部的主题相关文章和随机文章模块不显示,排查后发现,主要原因出在组件内部的硬编码以及永久链接的路由组件缺失。

首先来到 src/utils/content-utils.ts,让模块和上下文按钮在生成数据时,就携带计算好的文章路径,先将第 45 行 getSortedPosts 处改为:

export async function getSortedPosts() {

const sorted = await getRawSortedPosts();

for (let i = 1; i < sorted.length; i++) {

sorted[i].data.nextSlug = getPostUrl(sorted[i - 1]);

sorted[i].data.nextTitle = sorted[i - 1].data.title;

}

for (let i = 0; i < sorted.length - 1; i++) {

sorted[i].data.prevSlug = getPostUrl(sorted[i + 1]);

sorted[i].data.prevTitle = sorted[i + 1].data.title;

}

return sorted;

}

再将第 382 行处开始的两个循环改为:

for (const s of withTagMatch) {

if (result.length >= maxCount) break;

result.push({

id: s.post.id,

data: s.post.data,

url: getPostUrl(s.post)

});

}

for (const s of withoutTagMatch) {

if (result.length >= maxCount) break;

result.push({

id: s.post.id,

data: s.post.data,

url: getPostUrl(s.post)

});

}

搞定了数据部分,再修改组件逻辑,使其能够读取传入的永久链接。先是上下文按钮的,来到 src/components/PostNavigation.astro 第 33 行处,将 <a> 标签作如下修改,这个链接对应「上一页」:

<a

href={prevSlug.startsWith('/') || prevSlug.startsWith('http') ? prevSlug : getPostUrlBySlug(prevSlug)}

class="w-full font-bold overflow-hidden active:scale-95"

>

第 52 行处的 <a> 标签也是同理,这个链接对应「下一页」:

<a

href={nextSlug.startsWith('/') || nextSlug.startsWith('http') ? nextSlug : getPostUrlBySlug(nextSlug)}

class="w-full font-bold overflow-hidden active:scale-95"

>

这样上下文按钮就改好了,接下来是相关文章和随机文章模块中携带的路径,来到 src/components/widget/PostListItem.astro 的第 34 行处,将 <a> 标签作如下修改:

<a

href={post.url || getPostUrl(post as any)}

class="group flex items-center gap-3 px-3 py-3 -mx-1 rounded-lg transition-all hover:bg-black/5 dark:hover:bg-white/5 active:scale-[0.98]"

>

最后,补全永久链接的页面路由中的相关文章和随机文章模块,来到 src/pages/[…permalink].astro,找到:

import {

LastModified,

PostMeta,

PostNavigation,

} from "@components/features/posts";

部分,在 PostNavigation, 后面补上 RandomPosts,RelatedPosts,,接着找到:

import { getSortedPosts } from "@utils/content-utils";

getSortedPosts 这个变量加到 getSortedPosts 后面,再找到 import { licenseConfig } from "src/config"; ,将其改为:

import {

licenseConfig,

randomPostsConfig,

relatedPostsConfig,

} from "src/config";

随后在 Frontmatter 结束前(--- 前)补充:

const relatedPosts = relatedPostsConfig.enable

? await getRelatedPosts(entry, relatedPostsConfig.maxCount)

: [];

最后,在最后(</MainGridLayout> 前)补上:

{

(relatedPostsConfig.enable || randomPostsConfig.enable) && (

<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">

{relatedPostsConfig.enable && relatedPosts.length > 0 && (

<RelatedPosts relatedPosts={relatedPosts} />

)}

{randomPostsConfig.enable && (

<RandomPosts

excludeIds={[entry.id]}

maxCount={randomPostsConfig.maxCount}

/>

)}

</div>

)

}

到这里,问题就算全部解决。不过在下还建议顺便带 /posts/ 的链接加个重定向,来到 src/pages/posts/[…slug].astro,在第 22 行引入 getPostUrl

import { getFileDirFromPath, removeFileExtension, getPostUrl } from "@utils/url-utils";

再于第 97 行 const { entry } = Astro.props; 后加入判断就好了:

if (permalinkConfig.enable) {

const targetUrl = entry.url || getPostUrl(entry);

if (targetUrl && !targetUrl.startsWith("/posts/")) {

return Astro.redirect(targetUrl, 301);

}

}

另外,出于对 SEO 的考虑,建议修改 src/pages/robots.txt.ts,因为 Mizuki 的默认配置只允许首页和 /posts/ 下的地址被爬取:

import type { APIRoute } from "astro";

const robotsTxt = `

User-agent: *

Disallow:

Sitemap: ${new URL("sitemap-index.xml", import.meta.env.SITE).href}

`.trim();

/* 至少也要保证站点地图能被正常爬取

Disallow: /

Allow: /$

Allow: /posts/

Allow: /sitemap-index.xml

Allow: /sitemap-0.xml

Allow: /sitemap-1.xml(如果有)*/

export const GET: APIRoute = () => {

return new Response(robotsTxt, {

headers: {

"Content-Type": "text/plain; charset=utf-8",

},

});

};

更新 Twikoo 前端#

Mizuki 自带的源在 /assets/js/twikoo.all.min.js,可以选择下载最新版本将其替换,后端不使用腾讯云云函数的只需要 twikoo.min.js 即可。

也可以直接引用官方 CDN:https://unpkg.com/twikoo/dist/twikoo.min.js,需要在 src/components/comment/Twikoo.astro 中替换 TWIKOO_SCRIPT_URL

鼠标指针#

也算是敝站为数不多的自 WordPress 时代以来一直传承下来的特性。直接在 src/layouts/Layout.astro 添加:

<style>

/* 1. 默认状态 */

html, body {

cursor: url('https://PATH_TO_YOUR_DOMAIN/normal.cur'), auto;

}

/* 2. 悬停与点击状态 */

a, button, [role="button"], summary, select, option {

cursor: url('https://PATH_TO_YOUR_DOMAIN/selecting.cur'), pointer;

}

a:active, button:active {

cursor: url('https://PATH_TO_YOUR_DOMAIN/selected.cur'), pointer;

}

/* 3. 文本输入与选择状态 */

input[type="text"], input[type="search"], textarea, [contenteditable="true"] {

cursor: url('https://PATH_TO_YOUR_DOMAIN/text.cur') 16 16, text;

}

/* 4. 等待/后台加载状态 */

.is-loading, .loading-state {

cursor: url('https://PATH_TO_YOUR_DOMAIN/waiting.cur'), wait;

}

</style>

适配动态页#

敝站的动态页基于 Mastodon embed timeline widget 制作,用于展示自己在 Mastondon 发表的内容,本质上是直接引用 JS。不过需要注意的是,Astro 需要把 JS 内容单独拿出来,用 script is:inline 放到 UI 孤岛中,否则将不会被加载。

另外,似乎必须完全刷新一次页面才能正确加载出页面内容,因此加入了强制刷新代码:

<script>

initMastodon();

document.addEventListener('astro:page-load', initMastodon);

</script>

最后是对主题颜色的适配:

<style is:global>

.mt-container,.mt-container[data-theme],.mt-dialog,.mt-dialog[data-theme] {

--mt-txt-max-lines: none;

--mt-preview-max-lines: none;

--mt-color-bg: rgba(255, 255, 255, 0);

--mt-color-bg-hover: rgba(233, 238, 241, 0);

--mt-color-line-gray: var(--line-color);

--mt-color-contrast-gray: var(--content-meta);

--mt-color-content-txt: var(--color-neutral-700);

--mt-color-hashtag: var(--primary);

--mt-color-link: var(--btn-content);

--mt-color-error-txt: var(--admonitions-color-caution);

--mt-color-btn-bg: var(--btn-regular-bg);

--mt-color-btn-bg-hover: var(--btn-regular-hover);

--mt-color-btn-txt: var(--btn-content);

--mt-color-backdrop: #00000090;

--mt-color-placeholder: #60698425

}

.dark .mt-container,.dark .mt-container[data-theme],.dark .mt-dialog,.dark .mt-dialog[data-theme] {

--mt-color-bg: rgba(255, 255, 255, 0);

--mt-color-bg-hover: rgba(233, 238, 241, 0);

--mt-color-content-txt: var(--color-neutral-300);

--mt-color-backdrop: #00000090;

--mt-color-placeholder: #60698425

}

</style>

迁移小结#

这次的迁移,在下大约花了一个周末的时间完成。除了上述必要的一些适配之外,在下还删掉了一部分文章封面,虽然封面确实能提高页面美观程度,但是给每一篇文章都加上封面反而让网站显得过于花哨,删掉一部分与文章相关不大的封面之后,看起来有了呼吸的空间,错落交致之间反而提升了整体的美观度;另一方面,也能变相加快网页的加载速度。

对于既往文章的修订当然也是必不可少的,不过对于这些琐碎的部分,就不再一一赘述。此外,由于架构迁移,旧有的一些功能,例如剧集页无法继续提供,不过有动态页的 NeoDB 消息补足,可以等之后再慢慢打磨上线。

在迁移的过程中,在下总是能迸发出最初搭建博客时的,发自内心的快乐,在电脑前一坐就是好几个小时,心无旁骛,无视外界的打扰,把食欲和困意也暂时放在一边,脑子里只考虑自己想要考虑的东西,进入全神贯注的心流状态。哪怕是结束之后只想瘫在床上被睡意裹挟,那种带给在下的充实感与满足感,也依然会从骨子里蔓延开来,告诉在下:博客在迁移,时代在变,但那份对折腾发自内心的热爱与专注,从未改变。