用 Nuxt Content 管理日常数据
2026年8月16日#Nuxt#内容管理
这个站点只有一页,日常的内容却要用 YAML 单独维护。记录一下为什么不用数据库,以及按年份分组、图片路径这些细节怎么处理。
一开始我也想过用数据库:后台表单、富文本编辑器、图片上传……但仔细想想,一个只给自己用的日常记录,需要的只是「写一条,提交,上线」。
为什么用文件而不是数据库
贴几行 YAML 就能发一条动态,写完 git commit 就是发布记录,顺带还有历史版本可以回滚。这个成本比维护一套后台低太多。
// content.config.ts
moments: defineCollection({
type: 'data',
source: 'moments/**/*.yaml',
schema: z.object({
content: z.string(),
image: z.array(z.object({ src: z.string(), alt: z.string().optional() })).optional(),
date: z.string(),
location: z.string().optional(),
tags: z.array(z.string()).optional()
})
})
注意 image、location、tags 都设成了可选。这不是偷懒:schema 校验失败会让整个集合查询失败,一条写错就把整页日常变空白。可选字段能把这种事故降到最低。
时间线按年份分组
想让年份标签在滚动时吸顶,就不能把整个列表铺成一串兄弟节点——sticky 的约束范围是整个父容器,多个年份会一起粘在顶部互相压着。
办法是先分组,每年一个自己的容器:
const groups = computed(() => {
const out: { year: string, items: typeof items.value }[] = []
for (const item of items.value) {
const year = getYear(item.date)
const last = out[out.length - 1]
if (last && last.year === year) last.items.push(item)
else out.push({ year, items: [item] })
}
return out
})
每个 <section> 里再渲染各自的年份标签和卡片,吸顶行为就正常了。
图片路径的小约定
YAML 里只写文件名,代码补全前缀:
function resolveSrc(src?: string): string {
if (!src) return ''
if (/^(https?:)?\/\//.test(src) || src.startsWith('/')) return src
return `/moments/${src}`
}
这样本地放 public/moments/、以后换成对象存储,内容文件都不用动。
踩过的坑
- 图片加载失败要有兜底:手机拍的照片经常忘了传,页面上一个破图很难看。现在失败时会降级成一个占位图标。
content.config.ts改完要重启 dev:不然会冒no such table的报错,页面直接空掉。