上周上线了一个目录类网站,我又用回了自己屡试不爽的老办法——把YAML文件当成数据层,配合Next.js 15做静态生成。没有CMS,没有数据库,没有API路由。就是一堆文件放在目录里,构建时读取。
这套架构不炫技,但能让你把精力完全放在内容上。下面拆解具体实现。
![]()
什么场景适合这么干?四个条件同时满足就行:实体数量固定且已知(产品、游戏、餐厅、图书馆都行);内容按天更新,不需要秒级实时;追求零运行时成本;愿意让Git同时扮演版本控制和CMS的角色。有一条不满足,老老实实上数据库。四条全中,这就是最简可行方案。
![]()
目录结构长这样:data/games/下放YAML文件,klondike.yaml、freecell.yaml、spider.yaml各自对应一个页面。文件名即URL slug,整个文件夹就是你的数据库。app/games/[slug]/page.tsx负责渲染,lib/games.ts处理数据加载。
单个YAML文件的结构很直观。以Klondike纸牌游戏为例:name字段是显示名称,slug用于路由,metaTitle和metaDescription给SEO用,difficulty标难度等级,deckCount记牌组数量,tags打标签,description和rules用YAML的块标量(>)写长文本——换行会被折叠成单段,正好适合内容字段。选YAML而不是JSON,纯粹因为带换行的长文本在人类可读性上完胜。
数据加载层用js-yaml库配合Node的fs模块。同步读取没问题,因为这只在构建时执行。loadGame函数按slug读文件、解析YAML、返回类型化的Game对象;getAllGameSlugs扫描目录,过滤出所有.yaml文件,去掉扩展名就是全部路由参数。Game类型手写放在lib/types.ts,小项目够用。想要运行时校验可以上JSON Schema生成,但多数时候没必要。
![]()
页面组件用Next.js 15的App Router范式。generateStaticParams预先生成所有静态路由;generateMetadata异步读取游戏数据,动态填充标题和描述;主体组件同样异步加载数据,找不到就抛notFound。整套流程在构建时走完,部署后就是纯静态HTML,托管成本趋近于零。
这套模式的隐性收益常被忽略。内容变更走Git流程,PR即审稿,回滚即撤销,历史记录天然完整。多人协作没有锁冲突,合并冲突用熟悉的diff工具解决。本地开发零配置,复制几个YAML文件就能跑。对于内容更新频率以天为单位、实体规模几百以内的场景,数据库反而是负担。
当然边界也很清晰。需要实时搜索、用户生成内容、复杂关联查询、或者内容团队非技术背景时,这套方案就捉襟见肘了。但在它适用的区间里,复杂度降维带来的开发效率提升,往往比技术选型本身的优劣更值得权衡。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.