让 AI 给项目加一个新页面。它写得飞快,还主动用上了 Tailwind,交付时连配置都配好了。我顺手翻了眼它新建的文件,一个
tailwind.config.js,往里面写theme.extend。可这个项目是 Tailwind v4。v4 根本没有配置文件这回事,主题是直接写在 CSS 里的,官方迁移指南把
tailwind.config.js称为 legacy。我指出问题,它道歉、改掉,转身在下一个组件里,又把所有计算和回调都包上了useMemo,而这次的项目是 React 19,Compiler 都开了,手动 memo 化全是冗余代码。严格说它没写错,它写的是自己认知里"最标准"的写法。只是那份认知,停在训练截止的那一天。
一、模型的知识有保质期,而且它自己不知道
先把话说清楚:这不是模型的锅。
大模型的知识来自训练数据,训练数据是某个时间点之前的快照。快照之后世界发生的事,模型一概不知。这个问题在大多数领域不致命,数学不会过期,HTTP 协议十年没变。但前端恰好是迭代最快的领域,一年一个大版本是常态:
- Tailwind v4 砍掉了配置文件,改成 CSS-first 的
@theme指令 - ESLint v9 废弃了
.eslintrc,flat config 成了唯一格式 - React 19 里
ref可以作为普通 prop 传给函数组件,Compiler 接管了 memoization - Next.js 的 Pages Router 进入维护模式,新项目默认 App Router
- Vue 明确表态:Composition API 是唯一推荐范式,Options API 新代码禁用
这些变化有个共同点,旧的写法至今仍然能用。useMemo 不会报错,.eslintrc 也能跑,Pages Router 的项目一大把活得好好的。模型写出来的东西语法上完全正确,只是不符合当前的生态惯例。
结果就很拧巴:AI 交付的代码编译零报错,review 时却处处透着一股两年前的味道。而且模型不会给自己标注"以下写法基于 2023 年的认知",每次输出都同样自信。
比能力不足难对付的是过时还自信。能力不足你能看出来,过时的代码得你自己先知道最新版本长什么样,才认得出它旧。
二、比过时更坑的是幻觉:它不知道自己不知道
过时 API 好歹是真实存在过的东西。幻觉不一样,幻觉是从未存在过的东西。
我在前端场景里见过三种。
最阴的是臆造配置项。往 vite.config.ts 里写一个听起来很合理的选项,比如自定义插件的某个参数。选项不存在,不报错、不生效、静默跳过,你排查半天逻辑,最后发现配置压根没被读进去,全程没有任何失败信号。
其次是编造库方法。模型对某个第三方库记忆模糊时,会按"这个库应该有什么方法"来补全,方法名编得有模有样,链式调用写得行云流水,一跑,xxx is not a function。
还有张冠李戴的,把 A 库的 API 用在 B 库上,或者把旧版本的用法套在新版本上,参数个数都对,就是对象不对。
一个能通过司法考试、能写 LeetCode hard 的模型,为什么会一本正经地编造不存在的东西。
因为生成模型干的事是按概率补全,谈不上"回忆事实",只是在"生成通顺的文本",而通顺和正确之间没有必然关系。更麻烦的是,模型没有"我不知道"这个开关。人对没把握的事会犹豫、会去查,模型没有犹豫这个机制,概率最高的补全是什么就输出什么。
回头看过时和幻觉,其实是同一个根源:模型把训练数据当成了实时事实,把概率补全当成了记忆。
三、在对话里纠正,是一笔不划算的账
大多数人遇到这类问题的第一反应:发现了就纠正。我跟它说"这是 Tailwind v4",它道歉、改掉,皆大欢喜。
但账不能这么算。纠正只对当前会话生效。模型的记忆不跨会话,你今天教过它的东西,明天新开一个对话,它原样复发,你等于在一个人肉循环里当复读机,同一个知识点教十遍。
有人会说:写进项目的 CLAUDE.md / .cursorrules 里不就行了?这确实是正确方向,但有两个现实问题。一是规则得靠你自己踩坑总结,你团队踩过的坑,不会自动变成别的团队项目里的护栏。二是每换一个工具就要复制粘贴一遍配置,维护成本随工具数量涨。
往深一层想,模型的缺陷是系统性的,每一次会话、每一个用户、每一个项目都会复现。系统性的缺陷,靠零散的纠正对冲不了,约束也得是系统性的。
系统性约束长什么样?写进文件里,让模型每次干活之前先读。
四、换一种分工:模型管能力,文件管事实
我后来想明白了一个分工问题。不该指望模型"什么都知道",该指望的是模型"读什么、听什么"。模型负责能力,怎么写组件、怎么组织逻辑、怎么调样式,这部分它比大部分人都强。事实部分,当前主流版本是什么、哪些 API 已经废弃、哪些配置项根本不存在,交给一份随时可以更新的文件。
这两者拼起来,才是完整的认知。模型很聪明但知识是静态快照,文件不聪明但可以跟着生态滚动更新,互补的正是对方的短板。
顺着这个思路去找,还真让我找到一份现成的:frontend-guidelines,一份开源的 AI 前端规范技能。就一个 SKILL.md 文件,Agent 执行前端任务时先加载它,用它校准认知之后再动手。我把它的文档从头到尾读了一遍,治幻觉这块,它做了四层设计,一层比一层有意思。
第一层:版本基线,先校准再动手
技能里有一张版本基线表,标着基线日期(2026-09),逐项列着当前主线版本和"训练数据盲区",也就是模型记忆和现实差异最大的地方:
Tailwind CSS v4 CSS-first 配置(@theme 指令,无 tailwind.config.js),
Vite 项目用 @tailwindcss/vite 插件
ESLint v9+ flat config(eslint.config.js)为唯一格式,.eslintrc 已废弃
React 19.x Compiler 已发布 1.0 稳定版,启用后自动 memoization;
ref 作为普通 prop 直接传递
Next.js 15/16 App Router 为默认;Pages Router 维护模式;
getServerSideProps 在 App Router 中不存在
Agent 动手前先读这张表,把认知从"训练截止日"校准到"今天"。这张表明确写着用途:它只在校准认知时使用,优先级排在项目实际版本之后。为什么这么设计,后面说。
第二层:deprecated 防幻觉清单
光有基线不够,模型写代码是凭肌肉记忆的,所以技能里直接把高频过时写法列成了"禁止写出"清单:
React 禁止:useMemo/useCallback 包裹所有计算与回调(Compiler 时代是冗余代码)
禁止:React.forwardRef 包裹普通函数组件(ref 作为普通 prop)
Next.js 禁止:getServerSideProps / next/head / useRouter from next/router
Vue 禁止:Options API 新代码、this 访问 props、$set/$on/$children
Tailwind 禁止:tailwind.config.js + theme.extend、PostCSS 方式接入 Vite
ESLint 禁止:.eslintrc.*
命中任何一条都算缺陷,必须改掉。清单也留了兜底:如果项目锁的就是旧版本,遵循项目现状,交付时提示升级建议就行,防幻觉不等于无脑追新。
第三层:治幻觉的根,是把"凭记忆"换成"先核对"
前面两层治的是"过时",这一层治的是"幻觉"本身。技能里写死了几条正确性红线:
- 不确定的 API 用法,禁止凭训练数据记忆直接写,先查证。查 node_modules 里的类型定义,查官方文档,都不行就明说"此用法未经查证",不臆造
- 构建工具配置里的选项必须查证存在性,臆造的配置项会静默失效
- 用任何库的"新特性"前,先读 package.json 确认版本支持
第一层留的那个问题,在这条有了答案:项目实际版本 > 技能基线 > 模型记忆。连技能自己都不信任自己的时效。基线表只是校准用的地图,package.json 才是现场。模型写代码前先读一眼依赖版本,很多幻觉在动手前就被掐掉了。
第四层:这张表会过期,所以它自带更新机制
最妙的是第四层。任何"认知校准文件"都有这个问题:今天写的基线,半年后就是新的过时认知。如果这份文件烂掉了,它就从护栏变成了误导源。
所以基线表旁边写着一条元规则:当前时间距基线超过 6 个月,或任务涉及基线未覆盖的新版本特性时,先通过官方文档校准认知,然后顺带更新这张表。
普通的知识文档是静态的,写完就开始腐烂。这份文件被设计成跟着生态滚动,Agent 每次校准,都可能是对它自己的一次修订。它能不能一直有用,就看这个更新循环转不转得动。
五、幻觉不止在 API 层,界面也是重灾区
前面说的都是代码层面的幻觉,还有一类更隐蔽的:审美幻觉。
你让十个模型各写一个落地页,交出来的东西惊人地相似:紫色渐变 hero、默认蓝按钮、Inter 字体、白卡片配浅灰阴影、三栏等距网格、hover 时 scale(1.02)。业内管这种产物叫 AI slop,slop 直译是泔水,模型批量产出的那种千篇一律的糊。
原因还是概率。这些元素在训练数据里出现频率最高,模型眼里的"好设计"就是它们的最大公约数。它有能力做出有个性的界面,只是概率把它往默认组合上拽。跟编造 API 是一个病根,把高频当成了正确。
所以技能里专门有一节去 AI 味的硬约束:紫色渐变 + 默认字体 + 默认蓝 + 白卡片 + 全居中 + hover 缩放,这套组合出现即视为设计缺陷。写新界面之前必须先回答两个问题,这个页面给谁用、解决什么场景;设计调性选哪一个(极简/杂志编辑/科技感/高端质感),选一个贯彻到底,再定一个记忆锚点。色彩纪律、动效克制、布局的非对称,都是从"先有设计意图,再有实现"推出来的。说白了,就是禁止模型抄自己最顺手的作业:先想清楚这个界面该是什么感觉,再动手。
六、没验证过的代码,不许说已完成
规范解决的是"别写错",但 AI 前端交付还有老问题:写完了但没验过。这部分我之前单独写过一篇,就是 AI 改完代码自己开浏览器验收那个事,技能里做的是制度化约束:
静态检查(typecheck/lint)零错误才算完成;涉及页面渲染的改动,要跑起来验证渲染结果和控制台无错误;交付说明必须写清验证手段和遗留项;没验过的环节必须明示"未验证",不得宣称"已完成"。
最后一句话是关键。把"没验证"包装成"已完成",是 AI 最常见的越轨行为,比写错代码还普遍。完成的标准写死在技能里,它就没得含糊了。
七、技能分享
地址直接贴出来,GitHub 上,MIT 协议:
https://github.com/cigery-useio/useio-skills/tree/main/frontend-guidelines
我现在的每个前端项目都会挂上它。最直观的变化是,引子里那个场景再没发生过,AI 再没在我的 Tailwind v4 项目里建过 tailwind.config.js。
整个 SKILL.md 大概两百多行,除了前面讲的版本基线、防幻觉清单和查证机制,还有几块内容:
- 缺陷防御红线:正确性、安全(XSS/敏感信息/依赖安全)、质量(类型/命名/错误处理)、状态与数据获取、样式、可访问性、性能,每一条都是硬约束,另附一张 Top 10 禁止事项速查表
- 执行工作流:环境感知(先读 package.json 和项目惯例)→ 生成前自检 → 生成约束 → 验证闭环 → 交付标准,约束每一次代码生成
- 异常处理:项目版本和基线冲突怎么办、查证工具不可用怎么办、用户明确要求和规范冲突怎么办
- 交付前自检清单:十二条,逐项核对
使用上没什么门槛。装进支持 Skills 机制的 Agent 工具(UseIO、Codex、Claude Code 这类),写前端任务时就会自动触发加载;不支持的工具,把它当一份 prompt 片段塞进系统提示或项目规范文件里,效果打折但同样可用。
有两个原则值得提前知道。一是技术栈中立,条款按框架标注生效,写死的具体版本以项目 package.json 为准,技能基线只做认知校准;二是项目规范优先,项目里已有自己的 编码规范.md 或规范文档时,技能只补位不覆盖。
总结一下
写这篇文章的时候,我越来越觉得"过时"和"幻觉"这两个问题,答案其实是同一个:模型负责能力,文件负责事实。
模型的参数里装着写代码的能力,这份能力每年都在涨,但它的知识永远是快照。快照和现实之间的差距,靠更大的模型填不平。下一个大模型训练完的那天,生态又往前跑了一段。能指望的还是工程手段:让规范文件跟着生态滚动去校准事实,用查证机制守住"不确定就查、查不到就明说"的底线,再靠更新循环让文件别过期。
AI 写代码的能力上限在模型,下限在这些工程细节上。
技能地址再放一次:https://github.com/cigery-useio/useio-skills/tree/main/frontend-guidelines。