newblog

June 2, 2026 · View on GitHub

这是 xingpingcn.top 的 Astro 博客源码,由旧的私有 Hexo 源仓库迁移而来。

本仓库是公开仓库。src/content/blog/ 下生成的 MDX 文章才是预期放在这里的迁移结果。

本地开发

场景命令说明
安装依赖npm install首次拉取仓库后运行。
启动开发服务器npm run dev本地服务地址配置为 http://localhost:1234/
完整验证npm run build推送内容或模板改动前运行。
格式化项目npm run prettier只运行 Prettier;需要中英文空格整理时用 npm run format

项目结构

路径作用
src/content/blog/博客文章和 subpost。
src/content/authors/作者资料。
src/components/figure.astro带图片说明的单图组件,支持懒加载、骨架占位和 PhotoSwipe。
src/components/image-grid.astro多图网格组件,支持图片说明、懒加载、骨架占位和 PhotoSwipe。
src/pages/friends.astro友链页,读取公开友链数据并展示申请说明。
src/consts.ts站点元信息、导航链接、社交链接和搜索引擎验证 ID。
scripts/migrate-hexo-blog.mjs只在本地使用的旧私有 Hexo 源迁移脚本。

写一篇文章

src/content/blog/ 下新建目录,并在目录里放一个 index.mdx

src/content/blog/my-post-slug/
  index.mdx

文章 URL 会生成在站点根路径:

/my-post-slug

使用下面这种 frontmatter:

---
title: '文章标题'
description: '用于首页摘要和 SEO 的简短描述'
date: 2026-05-19
tags: ['python']
authors: ['xingpingcn']
coverImage: 'https://cdn.jsdmirror.com/gh/xingpingcn/picx-images-hosting@master/path/to/image.webp'
---

import Figure from '@/components/figure.astro'
import ImageGrid from '@/components/image-grid.astro'

正文内容从这里开始。

支持的文章 frontmatter:

字段要求说明
title必填文章标题。
description必填显示在文章卡片和 meta description 中。
date必填会被解析为日期。
tags可选字符串数组。只添加确实要展示在 /tags 的真实标签,不要为了 SEO 关键词乱加标签。
authors可选字符串数组。当前作者使用 ['xingpingcn']
coverImage可选远程图片 URL 或 /public 下的本地路径,用于文章卡片、文章头图和社交分享图。
image可选本地图片,通过 Astro content collections 导入。
keywords可选SEO 关键词数组。
pinned可选布尔值。设为 true 时,文章会置顶到普通文章前面。
draft可选布尔值。设为 true 时,文章不会出现在生成页面中。
canonicalURL可选canonical URL,用于转载内容。
license可选文章协议配置。默认展示旧博客迁移来的 CC BY-NC-SA 4.0;转载内容可以设为 type: 'original',不展示协议块可以设为 false

文章协议示例:

license:
type: 'cc-by-nc-sa-4.0'

转载文章示例:

canonicalURL: 'https://example.com/original-post'
license:
type: 'original'
sourceTitle: '原文标题'
sourceUrl: 'https://example.com/original-post'

首页和 /blog 列表会排除 subpost,并按下面规则排序:

  1. 置顶文章在前;
  2. date 越新的文章越靠前。

VS Code 写作快捷方式

仓库提供 .vscode/mdx.code-snippets,在 Markdown / MDX 文件里可以直接输入触发词后按 Tab 展开:

触发词插入内容说明
imports / import-writingCalloutFigureImageGrid 三个 import放在 frontmatter 后、正文前。
import-calloutCallout import只需要提示块时使用。
import-figureFigure import只需要单图时使用。
import-imggridImageGrid import只需要多图网格时使用。
image / imggrid<ImageGrid />只插入组件块,不会自动重复 import。
fig<Figure />单图组件块。
callout<Callout />提示块,variant 会给候选项。
callout-folded默认折叠的 <Callout />会插入 defaultOpen={false}
g / gaoliang / 高亮 / inline-code`高亮内容`不用切英文输入反引号。
code / codeblock普通代码块可选择 bashtspython 等语言。
code-title / codefiletitle 的代码块适合展示文件名。
code-noline关闭行号的代码块插入 showLineNumbers=false
code-collapse可折叠行范围的代码块插入 collapse={1-5}
diff / codediffdiff 代码块支持 lang="js" 这类二级高亮。
linkMarkdown 链接插入 [文字](URL)
quote引用块插入 > 引用内容
details<details> 折叠块普通 HTML 折叠内容。
table三列表格快速插入 Markdown 表格。
math数学块插入 $$ ... $$
hr分隔线插入 ---

VS Code 的快捷键配置是用户级配置,不会读取仓库里的 .vscode/keybindings.json。如果想选中文字后一键包成内联代码,需要打开 Preferences: Open Keyboard Shortcuts (JSON),在用户级 keybindings.json 加:

{
  "key": "ctrl+alt+e",
  "command": "editor.action.insertSnippet",
  "when": "editorTextFocus && editorLangId =~ /^(markdown|mdx)$/",
  "args": {
    "snippet": "`${TM_SELECTED_TEXT:${1:高亮内容}}`"
  }
}

添加 Subpost / 系列合集

Subpost 沿用上游 astro-erudite 的约定:如果一篇博客条目的 ID 里包含 /,它就会被当作 subpost。不需要额外写父子关系字段。

它适合把一组强相关内容做成“系列合集”:

  • 父文章是合集入口,负责写总览、背景、目录说明和整体结论;
  • 子文章是系列里的每一节,适合拆分很长的教程、笔记、章节或连续记录;
  • 站点会根据文件路径自动把子文章挂到父文章下,不需要额外配置 seriesparent 之类字段。

使用下面这种结构:

src/content/blog/parent-post/
  index.mdx
  first-subpost.mdx
  second-subpost.mdx

会生成下面这些 URL:

/parent-post
/parent-post/first-subpost
/parent-post/second-subpost

建议把父目录命名成这个系列的固定 slug,例如 docker-learning-notes;子文章文件名命名成每一节的 slug,例如 install.mdxnetwork.mdxcompose.mdxindex.mdx 永远是父文章本身,不要拿来当第一节。

父文章示例:

---
title: 'Docker 学习笔记'
description: 'Docker 基础、网络、Compose 和常见运维问题的系列笔记'
date: 2026-05-19
authors: ['xingpingcn']
tags: ['docker']
---

这里写这个系列为什么要整理、每一节大概讲什么,以及读者应该按什么顺序看。

Subpost 示例:

---
title: 'Docker 网络基础'
description: '整理 Docker bridge、host、container 网络模式的基本用法'
date: 2026-05-19
order: 1
authors: ['xingpingcn']
tags: ['docker']
---

# Docker 网络基础

子文章正文。

Subpost 注意事项:

项目说明
嵌套深度目前只建议使用一层嵌套,避免写成 parent/child/grandchild.mdx
列表展示Subpost 不会出现在首页、/blog、作者页和标签页。
父文章页面会显示 subpost 数量和导航。
Subpost 页面会显示父文章链接、兄弟 subpost 的上一篇/下一篇、移动端 subpost 导航和桌面端 subpost 侧栏。
默认排序Subpost 按 date 升序排列;如果日期相同,则按 order 升序排列。
手动排序建议让同一系列的 subpost 使用相同 date,再用 order: 1order: 2order: 3 排序。
首页位置父文章的 date 决定合集入口在首页和 /blog 里的位置;子文章的 date/order 只影响同一合集内的顺序。
搜索引擎Subpost 页面会加 noindex,避免搜索引擎单独索引过薄的系列片段。

图片

普通 Markdown 图片仍然可用:

![图片说明](https://cdn.jsdmirror.com/gh/xingpingcn/picx-images-hosting@master/path/to/image.webp)

从 Hexo 迁移过来的图片块,或者任何需要图片说明和大图预览的图片,优先使用 FigureImageGrid

带说明的单图

单张图片使用 Figure

import Figure from '@/components/figure.astro'

<Figure
  src="https://cdn.jsdmirror.com/gh/xingpingcn/picx-images-hosting@master/path/to/image.webp"
  caption="测速截图"
/>

Props:

Prop要求说明
src必填图片 URL。
caption可选可见图片说明,同时会作为图片 alt 和 PhotoSwipe 的说明文字。

图片尺寸由 npm run sync:image-metadata 自动同步到 data/image-metadata.sqlite,并生成 src/generated/image-metadata.ts 供组件使用。npm run devnpm run build 会自动先同步一次;通常不需要在 MDX 里手写 width / height

多图网格

相册或对比截图使用 ImageGrid

import ImageGrid from '@/components/image-grid.astro'

<ImageGrid
  images={[
    {
      src: 'https://cdn.jsdmirror.com/gh/xingpingcn/picx-images-hosting@master/path/to/a.webp',
      caption: '官方解析',
    },
    {
      src: 'https://cdn.jsdmirror.com/gh/xingpingcn/picx-images-hosting@master/path/to/b.webp',
      caption: '优化解析',
    },
  ]}
/>

网格行为:

图片数量网格行为
1 张1 列。
2 张中等屏幕起为 2 列。
3 张及以上中等屏幕起为 2 列,超大屏幕起为 3 列。

FigureImageGrid 都会使用:

能力说明
懒加载内容图片使用 loading="lazy"
骨架占位用于减少图片加载时的布局抖动。
大图预览文章内容中点击图片时打开 PhotoSwipe 大图预览。

ImageGrid 会自动从同一组图片的 metadata 里找出按同宽展示时最高的图片比例,作为整组统一骨架高度;其他图片会在这个统一区域里填充显示。

除非有明确理由保留其他域名,图片 CDN 默认使用 https://cdn.jsdmirror.com/gh/xingpingcn/picx-images-hosting@master/...

标签和作者

标签页由顶层文章里的 tags 生成:

tags: ['volantis', 'python']

当前迁移策略比较保守:标签应该来自旧博客里真实存在的标签,或者来自明确的新分类调整。不要因为文章里偶然提到某个主题就添加无关标签。

作者 ID 指向 src/content/authors/ 下的文件:

src/content/authors/xingpingcn.md

普通文章使用 authors: ['xingpingcn']。作者卡片、头像、邮箱、GitHub 和网站都在作者文件里维护。

友链系统

友链页在 /friends,配置集中在 src/consts.tsSITE.friends

friends: {
  api: 'https://raw.githubusercontent.com/xingpingcn/friends/output/v2/data.json',
  repo: 'xingpingcn/friends',
  applyUrl: 'https://github.com/xingpingcn/friends/issues',
}

页面会在构建时读取公开的 xingpingcn/friends 输出数据。JSON 里每个站点建议包含:

{
  "title": "站点名",
  "url": "https://example.com",
  "avatar": "https://example.com/avatar.png",
  "screenshot": "https://example.com/screenshot.webp",
  "description": "站点简介",
  "labels": [{ "name": "active", "color": "58EC80" }]
}

只有带 active 标签的站点会显示在友链页。

字段说明
title站点名。
url站点链接。
avatar头像 URL,支持懒加载、骨架占位和失败占位。
screenshot截图 URL,支持懒加载、骨架占位和失败占位。
description站点简介。
labels标签数组;只有带 active 标签的站点会显示在友链页。

常见 jsDelivr URL 会自动规范化为 cdn.jsdmirror.com

从旧私有博客迁移

迁移脚本会从本地路径读取旧的私有博客源,并生成 Astro MDX 文章:

node scripts/migrate-hexo-blog.mjs /tmp/newblog-migration/blog-source

脚本会把旧 Hexo 图片 / gallery 标签转换成 FigureImageGrid,把已知 CDN URL 规范化为 cdn.jsdmirror.com