Vibe 出一个博客:从渲染排障到可验证界面
这个博客不是先画完 Figma,再照着稿子实现的。它从一句“代码块没有高亮”开始,经过几十次很小的反馈逐步长出来:桌面端太像手机、表格太重、字号太小、组件 hover 会位移、About 信息太散、动画做得难看。
这听起来很像纯粹凭感觉改页面,但真正让它没有失控的,是一条反复执行的工程闭环:
观察真实页面
-> 把不适感改写成可检查的问题
-> 修改最小范围的代码
-> 构建
-> 在桌面与移动视口验证
-> 保留、继续收敛,或者直接删除这篇只记录这条链路里真正有技术含量的部分。
从渲染结果开始,而不是从源码猜
第一次排查发生在文章最底部。代码块看起来没有高亮,单看 Markdown 和配置无法判断问题究竟在哪一层:
- Markdown 是否被识别为 fenced code block;
- VitePress 是否调用了 Shiki;
- Shiki 是否输出了 token span;
- 自定义 CSS 是否把 token 颜色覆盖掉了;
- 问题是否只发生在页面底部或移动端横向滚动容器中。
所以检查不是停在 npm run build,而是直接打开实际页面,滚到底部,再看 DOM 和计算样式:
playwright-cli open http://192.168.1.163:5173/blog/articles/hello-world
playwright-cli resize 1440 1000
playwright-cli snapshot
playwright-cli eval "document.querySelectorAll('.shiki span').length"
playwright-cli close最终的职责划分很明确:VitePress 在构建阶段调用 Shiki,主题只负责代码容器、语言标签、滚动和留白,不在运行时再引入一套高亮器。
export default defineConfig({
markdown: {
lineNumbers: true,
},
})这里最重要的不是某个配置项,而是先确认“语义有没有生成”,再调整“视觉有没有表达出来”。否则很容易一边改 Shiki,一边其实只是 CSS 把颜色盖掉了。
先建立内容模型,再做首页
站点被拆成四个稳定入口:
export const routes = {
"/": "最近更新",
"/articles": "普通文章",
"/skills-agents": "Skill 与 Agent 实践",
"/about": "个人信息与项目",
} as const文章列表没有维护第二份手写数据,而是直接让 Vite 读取 Markdown 模块:
const postModules = import.meta.glob("/blog/**/*.md", {
eager: true,
})
const posts = Object.values(postModules)
.map((mod) => mod.__pageData || {})
.filter((page) => page.relativePath?.startsWith(sourceFolder))
.map((page) => ({
url: "/" + page.relativePath.replace(/\.md$/, ""),
title: page.frontmatter.title || "",
description: page.frontmatter.description || "",
date: page.frontmatter.date || "",
tags: page.frontmatter.tags || [],
}))这让 frontmatter 成为唯一内容源。首页和两个文章索引只负责筛选、排序与展示,不复制标题、日期和标签。blog-posts.json 只作为机器可读索引,不参与页面运行时渲染。
桌面端不是移动端的放大版
第一版在宽屏上仍然是单列、窄间距和小字号,视觉上像一张被拉宽的手机截图。修复方式不是继续加 max-width,而是让两个断点承担不同的信息结构。
移动端优先保证顺序与触控:
- 导航占满宽度;
- 项目和文章使用单列;
- 表格只在自身容器内横向滚动;
- 标题、标签和箭头不能挤出父容器。
桌面端则提高并行信息密度:
- 首页使用主视觉与文章网格双栏;
- 文章索引的标题与简介并排;
- About 的身份信息、经历和项目列表形成不同扫描层级;
- 列表 hover 只改变背景和颜色,不改变位置。
<div
class="grid gap-12 py-10
lg:min-h-175
lg:grid-cols-[minmax(330px,0.8fr)_minmax(0,1.55fr)]
lg:gap-20 lg:py-20"
>
<HomeHero />
<section><!-- latest posts --></section>
</div>响应式不是把同一套尺寸缩放,而是在不同宽度下重新决定哪些信息应该并排、哪些必须排队。
Tailwind 接管组件,CSS 只处理生成内容
项目使用 Tailwind CSS v4,通过 Vite 插件接入:
import tailwindcss from "@tailwindcss/vite"
export default defineConfig({
vite: {
plugins: [tailwindcss()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./theme", import.meta.url)),
},
},
},
})颜色、字体和容器宽度集中在 @theme。Vue 组件主要使用工具类,style.css 只保留两类无法靠模板直接控制的内容:全局基础规则,以及 VitePress 生成的 Markdown、Shiki、表格后代选择器。
组件目录也在迭代中被重新分层:
.vitepress/theme/components/
├── pages/
│ ├── HomePage.vue
│ ├── AboutPage.vue
│ └── PostIndex.vue
└── ui/
├── HomeHero.vue
├── CodeBlock.vue
├── ImageBox.vue
├── Kbd.vue
├── Note.vue
├── Quote.vue
└── Tag.vue页面组件负责路由级组合,小组件负责局部表达。Markdown 需要使用的组件在 index.ts 全局注册,HomeHero 这类页面私有组件只做局部 import,不扩大主题 API。
Vibe 的关键动作是删除
这个过程中最耗时间的尝试,是首页的一段“蓝色月牙沿弧线移动,最后变成橙色落日”的动画。它经历了关键帧、运动路径、形状遮罩、中轴对称和连续插值,技术上都能运行,但视觉上仍然抢内容、显得刻意。
最后的处理不是继续调参数,而是删除元素、关键帧和相关工具类。
同样被删掉或收回的还有:
- 与导航 hover 边框重叠的页眉装饰;
- hover 时让文章卡片发生位移的效果;
- 用两个 Tabs 隐藏两段本来应该同时读到的说明;
- 过重的表格边框和卡片阴影;
- 只为解释功能而存在的页面文案。
Vibe coding 很容易不断往页面上加东西。真正困难的是承认某个已经写完、甚至已经验证通过的实现仍然不应该留下。
每一轮都必须落到可验证条件
“看起来更好”不能直接作为完成条件。每次调整至少检查四件事:
| Check | Method | Failure signal |
|---|---|---|
| 构建 | npm run build | Vue、Markdown 或 Tailwind 编译失败 |
| 桌面布局 | 1440 × 1000 | 内容仍像移动单列,间距失衡 |
| 移动布局 | 390 × 844 | 横向溢出、文字遮挡、表格撑宽页面 |
| 交互稳定 | hover / click / computed style | 位移、错误颜色、状态没有切换 |
一个常用的溢出断言只有一行:
document.documentElement.scrollWidth > document.documentElement.clientWidth检查 hover 时,则同时记录背景色和元素坐标。背景应该变化,top 不应该变化:
const item = page.locator("a").filter({ hasText: "SwanLab" })
const before = await item.evaluate((element) => ({
background: getComputedStyle(element).backgroundColor,
top: element.getBoundingClientRect().top,
}))
await item.hover()
await page.waitForTimeout(200)
const after = await item.evaluate((element) => ({
background: getComputedStyle(element).backgroundColor,
top: element.getBoundingClientRect().top,
}))最终交付循环保持得很短:
npm run build
playwright-cli open http://192.168.1.163:5173/
playwright-cli resize 1440 1000
playwright-cli resize 390 844
playwright-cli close"用感觉提出下一步,用浏览器决定这一步是否成立。— Build log
这个博客的“vibe”不是某一种渐变、字体或动画,而是一个持续收敛的过程:把模糊的不满意变成具体问题,把具体问题变成最小修改,再让真实渲染结果决定保留还是删除。