I recently rewrote a Galgame resource site entirely from Hugo to Next.js. The repo is vns-next. From the initial commit create-next-app to being deployable, it took 13 commits over less than 20 hours.
When I looked back through the repo afterward, I noticed something pretty funny — the convention file I wrote for Claude is just one line long. And I didn’t even write that line myself…
This post isn’t about rendering modes or deployment. It’s only about one thing: “what conventions should you leave for AI when starting a project.” Let me record the conclusions I’ve worked out after bouncing between several projects…
My entire CLAUDE.md is one line
Here’s the original — the full contents of CLAUDE.md in the root of vns-next:
@AGENTS.mdYep, that’s the whole thing, using import syntax to point at the neighboring AGENTS.md. Now let’s look at AGENTS.md@ — all 5 lines:
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->Notice that pair of BEGIN / END markers — I didn’t write that part; create-next-app inserted it itself. The core message is just one sentence: don’t trust the Next.js in your training data, go read node_modules/next/dist/docs/ before writing any code.
When I started thdl (a Touhou doujin resource site) earlier, it was the same setup — CLAUDE.md was identical, also a one-line import pointing to AGENTS.md, same contents word for word. The benefit is obvious: a single source of truth, so the two rule sets can’t conflict with each other~
So is that little enough? Is it enough to get by? At first I definitely thought it wasn’t enough…
The other extreme: one repo, three sets of convention files
That earlier project acg-site took the completely opposite approach — maintaining three sets at once:
CLAUDE.mdin the root, 111 lines- 9
.mdcfiles under.cursor/rules/, among whichproject.mdcandlibrary-versions.mdcarealwaysApply: true .github/copilot-instructions.md, which demotes itself to an index right at the top: "For the complete conventions, see the project rootCLAUDE.md"
Line 3 of CLAUDE.md opens like this:
Freshness warning: This project uses a large number of cutting-edge library versions that AI training data very likely doesn’t cover. When you hit an uncertain API usage, be sure to check the latest docs (Context7 MCP is available in Cursor).
Then it dedicates a whole section to “Key version differences (common AI mistakes),” 6 items:
- `params` / `searchParams` 在 Next.js 16 中是 **Promise**,服务端用 `await`,客户端用 `use()`
- Tailwind v4 用 **CSS 配置**(`@import "tailwindcss"` + `@theme {}`),无 `tailwind.config.ts`
- Prisma 7 generator 为 `"prisma-client"`(不是 `"prisma-client-js"`),导入路径 `@/generated/prisma/client`
- Zod 4 导入 `import { z } from "zod"`(不是 `zod/v4`)
- 日期用 `dayjs`,不用 `date-fns`
- 认证用 Better Auth(`@/lib/auth-client`),不是 next-authAt the end of .cursor/rules/library-versions.mdc there’s also a ❌→✅ comparison table, 7 rows:
1. ❌ `params.id` → ✅ `(await params).id` 或 `use(params).id`
2. ❌ `tailwind.config.ts` 的 `theme.extend` → ✅ `globals.css` 的 `@theme { }`
3. ❌ `import { PrismaClient } from "@prisma/client"` → ✅ `import { PrismaClient } from "@/generated/prisma/client"`
4. ❌ `provider = "prisma-client-js"` → ✅ `provider = "prisma-client"`
5. ❌ `import { z } from "zod/v4"` → ✅ `import { z } from "zod"`
6. ❌ 用 `date-fns` 处理日期 → ✅ 用 `dayjs`
7. ❌ 用 `next-auth` API → ✅ 用 Better Auth(`@/lib/auth-client`)This one is genuinely useful. Because everything it writes is stuff AI can’t possibly know — version differences. The generator name in Prisma 7 changed, the import path in Zod 4 changed — if that isn’t written down, the model will whip you up a version based on the old way it remembers, and then you’ll spend twenty minutes debugging an import that doesn’t even exist. I’ve paid that bill several times…
The cost of three sets of files is drift
In the commit 519e5a7 I updated the docs and split apart a wrong explanation in CLAUDE.md — the original said "pnpm lint — ESLint + TypeScript check," but in package.json, lint only runs eslint . and doesn’t include type checking at all. After the fix it’s three items:
- `pnpm lint` — 仅 ESLint
- `pnpm typecheck` — 仅 TypeScript 类型检查(`tsc --noEmit`)
- `pnpm check` — ESLint + TypeScript 类型检查(提交前完整校验)The same commit also filled in the version numbers: tRPC 11.15 → 11.17, Zod 4.3 → 4.4, Prisma 7.6 → 7.8, Better Auth 1.5 → 1.6.
And then? .cursor/rules/project.mdc still says tRPC 11.15, Zod 4.3, Prisma 7.6, Better Auth 1.5 to this day, and even that wrong lint explanation is still lying there as-is. I updated one and forgot the other — that’s exactly how drift happens…
In a repo with multiple collaborators this gets even worse: two AI tools each read their own directory and end up with contexts that aren’t even the same document. I wrote about that scenario separately in another post, so I won’t expand on it here.
Convention files are code too — they expire. And no CI will tell you they’ve expired…
So where does the knowledge actually live
Looking back at vns-next: no .claude/, no .cursor/, no .github/, no docs/, no task list and no spec doc. Yet it’s not short on context at all.
Because all the knowledge is written into code comments and commit bodies~
In next.config.ts, that stretch of trailingSlash nails down both “why” and “why we also have to turn off the built-in one”:
// 与旧站(Hugo)URL 完全一致:尾斜杠必须保留,
// Artalk/Valine 历史评论按 /p/{id}/ 路径存储,去掉斜杠会丢评论
trailingSlash: true,
// 但内置的尾斜杠 308 会把 POST /api/auth/* 重定向成 /api/auth/*/ 导致 404(Better Auth 全挂),
// 故关掉内置重定向,由 src/proxy.ts 只对页面 GET 请求补尾斜杠
skipTrailingSlashRedirect: true,src/proxy.ts:21-23 is a pure warning, written for the next person who wants to “optimize” this code (most likely AI):
// 注意:不能用 nextUrl.clone() 再改 pathname——NextURL 的 setter 会按
// trailingSlash 配置把尾斜杠再规范化掉,Location 指回自身造成死循环。
// 官方 skipTrailingSlashRedirect 示例即用裸 new URL() 构造。src/lib/cache.ts:1-5 spells out how this file will be replaced in the future:
/**
* 内存 TTL 缓存 helper
*
* 接口对齐 acgn-flow 的 redis helper(getCache/setCache/deleteCache/deleteCachePattern),
* 将来若要换 ioredis 可以无痛替换本文件实现,调用方不需要改动。
*/The message of commit b07e20d even writes down the verification method — I went into the pitfall itself (no database at build time) in another post, so here I’ll just look at what it stuffed into the commit body:
fix(deploy): 全站 force-dynamic——Docker 构建环境无数据库,静态预渲染必然失败
根 layout 声明 dynamic='force-dynamic'(查库页面请求时渲染,内存缓存兜底),
sitemap/feed.xml/llms.txt/index.xml 同步声明。已用死库 DATABASE_URL 本地复现
构建通过。(This is part 1 of 2 of the same post.)
What these places have in common is that they will definitely be read next time. Anyone modifying proxy.ts will inevitably see that infinite-loop warning, and anyone reading next.config.ts will inevitably see where the trailing slash came from. Whether docs/architecture.md ever gets opened, though, is pure luck…
How I split it up now
| Content type | Where it goes | Frequency of use |
|---|---|---|
| Framework version differences and API pitfalls | CLAUDE.md | ★★★★★ |
| Why a particular line of code is written that way | Code comments | ★★★★★ |
| The motivation and verification method for a change | commit body | ★★★★ |
| Deployment and ops steps | A separate README | ★★★ |
| Project intro, directory structure | Don’t write it, it’ll go stale | ★ |
Only the first category is something the model could never infer from the code — if you don’t write it down, it simply doesn’t know. As for things like “this project uses the App Router, and the directory structure is as follows…”, the model gets that with a quick ls~
While I’m at it, some self-deprecation about the README
Speaking of things that go stale. The README of vns-next is still the default create-next-app template to this day — 36 lines, talking about npm/yarn/pnpm/bun run dev and Deploy on Vercel, not a single character changed since the initial commit 85aa812.
But this project is actually self-hosted with bun + Docker Compose, and deployment never went through Vercel at all (that VERCEL branch in next.config.ts is just a fallback). The README and reality are completely out of sync…
Still, .dockerignore:13-19 says this:
# 内容导出快照 / 文档 / 部署自身
content-export
deploy
README.md
AGENTS.md
CLAUDE.md
.claudeREADME, AGENTS.md, CLAUDE.md, and .claude are all excluded from the image context together (.claude is just a precaution — this project never actually created that directory). So I simply couldn’t be bothered to change it — it can’t make it into the image anyway…
Incidentally, 12 of those 13 commits carry the Co-Authored-By: Claude Opus 4.8 trailer; the only one without it is the initial commit generated by create-next-app. The author on every commit is me — credit where credit is due, but the blame is mine too~
Anyway, my next project will probably still be just one line, and I’ll add to it once I hit a snag~
