前阵子用 Claude Code 做博客CMS,接到一个看似不起眼的需求:文章详情页,动态路由/article/:id。当时我觉得这东西太常规了,直接在提示词里写了一句“开发一个文章详情页”,结果 Claude Code 生成的代码给我上了一课——没有上下文,再强的模型也只会给你一套“标准答案”而不是“正确答案”。后来我把提示词重构成结构化规格书,配合两轮追问,才真正把这页面做成可以上线的状态。
这个案例特别适合拿出来复盘,因为动态路由详情页看似简单,实际是前端开发里最容易翻车的场景之一:路由参数怎么取、组件复用时怎么触发更新、异步请求并发怎么处理、返回列表页时状态怎么保留,每一个环节都可能出问题。而这些问题,恰恰是 Claude Code 这类 AI 编程工具最需要你通过提示词去表达清楚的地方。这篇文章我会把完整的提示词案例、背后的设计思路、实际踩过的坑都摊开讲一遍,希望能给你一些可以直接抄作业的东西。
1. 需求拆解:为什么动态路由详情页是提示词翻车重灾区
1.1 动态路由详情页的隐藏复杂度
先说清楚动态路由是什么。像/article/:id这种路径,id是变量,同一个路由文件承载的是不同数据。前端拿到这个id,调接口拿数据,再渲染页面。听起来简单,但实际项目里它有四层隐藏复杂度。
第一层是参数获取与校验。Vue Router 里要用route.params.id,React Router 里要用useParams(),取出来的值还需要判断是否合法,不合法就要走 404 或者重定向。很多 AI 生成代码会忽略这一步,直接把id塞进接口请求,等后端报错才反应过来说参数有问题。
第二层是组件复用。从/article/1跳到/article/2,路由组件实例是复用的,不会重新走 mounted,如果没有监听route.params的变化,页面会一直显示第一篇文章。这也是 Claude Code 这类工具很容易漏的地方,因为单文件生成时它没有全局视角。
第三层是异步竞态。用户快速切换文章时,前一个请求可能比后一个请求更慢返回,如果不做竞态处理,页面会被旧数据覆盖。这个细节很多初级开发者都意识不到,模型更不会主动给你写,除非你在提示词里明确要求。
第四层是页面状态留存。从列表页进详情页,再返回列表页时,页码和滚动位置应该保留。这涉及到列表页的缓存策略、keep-alive的配置、路由的 scroll behavior,是一整套跨页面的联动逻辑。
你把这些复杂度列出来,就会发现一个事实:详情页不是一个页面,而是一套完整的状态链路。普通一句话提示词根本表达不了这个链路,所以 Claude Code 大概率只会给你一个“能跑但很脆”的 demo:数据能显示、样式还行,但一刷新、一快速切换、一返回,全是问题。
1.2 普通提示词为什么写不出合格详情页
我复盘过自己翻车的那次,提示词就一句话:“帮我写一个文章详情页,路径是 /article/:id,用 Vue3 + TS。”Claude Code 生成的代码从语法上讲没毛病,但离可用差得远:路由配了,组件写了,接口也调了,可没有加载状态、没有错误处理、没有参数变化监听、没有竞态处理、没有 SEO 基础标签,更别提返回列表页的状态恢复了。
问题不在模型能力,而在提示词的“信息密度”。Claude Code 本质上是根据你给的上下文去预测最合理的代码,它不知道你的项目里有没有统一的请求封装、不知道设计师要求的骨架屏长什么样、不知道你要不要缓存、不知道路由是怎么组织的。它会默认选一个“最大概率正确”的方案,而这个方案往往是通用得不能再通用的实现。
我用一个对比来说明:
| 维度 | 一句话提示词 | 结构化提示词 |
|---|---|---|
| 角色 | 未指定 | 指定资深前端工程师角色 |
| 技术栈 | 只说 Vue3 + TS | 明确到 Vue Router 4、Pinia、Vite、接口封装方式 |
| 功能清单 | 未给 | 逐条列出标题、正文、评论、推荐、标签等模块 |
| 边界条件 | 未提 | 明确要求处理 loading、error、404、竞态、参数变化 |
| 文件结构 | 交给模型随意发挥 | 明确组件目录、文件职责、路由注册方式 |
| 验证标准 | 无 | 提供自检点,让 AI 输出前检查 |
同样的需求,信息密度不同,产出完全不是一个量级。所以我把“给 Claude Code 写提示词”这件事重新定义成:写一份领域内的技术规格书,而不是描述性的需求文。
2. 提示词结构化设计:把需求变成 AI 能执行的“规格书”
2.1 提示词的六大关键要素
我把写提示词比喻成给一个新同事交代任务。如果你只说“把那个详情页做了”,他大概率会按自己的经验去猜:技术栈可能猜错,接口路径可能猜错,文件放哪也可能猜错。但如果你把角色、背景、任务清单、约束条件、输出格式、验收标准这六件事讲清楚,他能一次性把活干到你满意的概率会提高十倍。Claude Code 的提示词也是这个逻辑。
六大要素分别是:
- 角色定义:告诉模型它是谁,具备什么能力倾向。比如“你是精通 Vue3 和 Vue Router 的资深前端工程师”,这能让模型调整技术决策的倾向性。
- 项目背景:说明当前项目是什么、技术栈是什么、已有的基础设施有哪些。模型知道的信息越多,生成的代码越贴合项目实际。
- 任务清单:用编号列出要做的事和必须覆盖的功能点。功能点越具体越好,别用“完善”这种虚词。
- 约束条件:明确哪些能做、哪些不能做。比如“请求必须走项目已有的 request 封装,不要新增 axios 实例”、“不要修改现有路由文件,新增独立路由模块”。约束是防止模型自由发挥的紧箍咒。
- 输出格式:告诉模型先给什么、再给什么、代码按什么结构组织。比如“先列文件清单,再按文件逐个输出代码,每个文件开头注释职责”。
- 验收标准:给出你判断“做完了”的标准,让模型自检。比如“组件复用切换路由时必须重新请求数据”、“必须处理请求竞态”。
这六个要素不是每次都要写全,但写全了,生成的代码质量会稳定很多。尤其当你面对的是一个多文件、跨组件、涉及路由和状态管理的复杂页面时,缺任何一个要素,后面都得多花几轮对话去补课。
2.2 动态路由场景下的要素写法要点
拿动态路由详情页来说,有几个地方需要特别花心思。
第一是任务清单里要写明路由参数的变化场景。我吃过的亏是:提示词里只写了“根据 id 获取文章数据”,模型自然就写了一个 onMounted 里请求数据,完全没有考虑从一篇切到另一篇的情况。后来我在任务清单里明确写“从 /article/1 跳转到 /article/2 时,组件是复用的,需要监听路由参数变化并重新请求数据”,它才把watch补上。
第二是约束条件里要写清竞态处理。很多人不会想到跟 AI 提“竞态”这个词,但你不提,它大概率就漏了。我实际验证过:明确写了“必须处理请求竞态,只展示最新一次请求的结果”,Claude Code 会主动用请求序号或者取消旧请求的方式去处理。这个能力它是具备的,只是默认不会给你加。
第三是状态保持不能只写一句“返回列表页时保持页码不变”。你要给它线索,比如“列表页使用了 keep-alive 缓存,返回时需要恢复滚动位置和页码”,它才会去检查keepAlive配置、scrollBehavior设置是否是配套的。
第四是 404 的处理策略。动态路由的:id可能是非法字符串,也可能是库里不存在的 ID,这两种情况要不要区分?提示词里写清楚“文章不存在时渲染 Not Found 组件,而不是停留在空白页”,模型就会在接口返回 404 时做对应处理,而不是简单 console.error 一下完事。
把细节写进提示词,看起来像是在“照顾”模型,其实是提前替自己省掉后续的追问和调试时间。一句话提示词一时爽,后面排查火葬场。
3. 完整案例实操:从提示词到可运行详情页
3.1 我的提示词原文
下面是我在博客 CMS 项目里实际用过的提示词,技术栈是 Vue3 + Vite + TypeScript + Vue Router 4 + Pinia,接口统一走@/api/request封装。原样贴出来,你可以根据自己项目调整:
# 角色 你是一名精通 Vue3、TypeScript、Vue Router 4 的资深前端工程师,擅长复杂动态路由页面的开发与状态管理。 # 项目背景 我负责的技术博客 CMS 前端使用 Vue3 + Vite + TypeScript + Pinia,接口统一通过 src/api/request.ts 封装的 request 函数请求,接口基础路径是 /api。现在需要新增文章详情页,路径为 /article/:id。 # 任务 实现文章详情页,包含以下功能: 1. 根据路由参数 id 从 GET /api/articles/:id 获取文章数据,id 非法时直接展示 404 页面。 2. 页面模块包括:文章标题、作者信息、发布时间、封面图、正文(Markdown 渲染)、标签列表、评论区列表、相关文章推荐。 3. 组件加载期间显示骨架屏,加载失败显示错误提示与重试按钮。 4. 从 /article/1 跳转到 /article/2 时,组件复用,必须监听路由参数变化并重新请求数据。 5. 处理异步请求竞态:只展示最新一次请求的结果,防止旧响应覆盖新页面。 6. 文章不存在(接口返回 404)时,渲染项目现有的 NotFound 页面。 7. 详情页返回列表页时,列表页页码和滚动位置保持不变,请检查项目是否已启用 keep-alive 并配置对应路由 meta。 # 约束 - 使用 Composition API 的 <script setup> 语法,样式使用 scoped。 - 请求必须走 src/api/request.ts 的 request 函数,不要新增 axios 实例或 fetch。 - 评论区和相关推荐拆分为独立组件,放在 src/components/article/ 目录下。 - Markdown 渲染使用项目已有的组件,遇到图片懒加载配置不要改动。 - 不要修改现有路由文件的逻辑,采用独立路由模块文件接入。 - 生成代码必须完整可运行,不要省略任何 import 语句。 # 输出格式 1. 先列出需要新增和修改的文件清单及每个文件的职责。 2. 再按文件逐个输出完整代码,文件开头用注释说明职责。 3. 最后给出路由注册方式,说明 meta 字段为 keepAlive 时的注意事项。 4. 输出完成后,对照任务清单逐条检查,指出你尚未覆盖的条目。这个提示词的关键词是“任务-约束-输出-自检”四段式,看起来挺长,但每个词都有用。比如明确说“不要省略任何 import 语句”,是因为 Claude Code 生成多文件代码时经常在展示时省略 import,导致你复制到项目里一堆报错。
3.2 生成结果的关键代码拆解
Claude Code 第一轮就给了完整的文件清单:src/views/article/ArticleDetail.vue、src/components/article/CommentList.vue、src/components/article/RelatedArticleList.vue、src/router/article.ts。其中核心的详情页组件大概长这样,和普通代码不同,它把竞态和参数监听都处理了:
<script setup lang="ts"> const route = useRoute() const router = useRouter() const article = ref<Article | null>(null) const loading = ref(true) const error = ref('') let requestSeq = 0 const fetchArticle = async (id: string) => { const currentSeq = ++requestSeq loading.value = true error.value = '' try { const { data } = await request<Article>({ url: `/articles/${id}` }) if (currentSeq === requestSeq) { article.value = data } } catch (e: any) { if (currentSeq !== requestSeq) return if (e.response?.status === 404) { router.replace('/404') return } error.value = '加载失败,请重试' } finally { if (currentSeq === requestSeq) { loading.value = false } } } watch(() => route.params.id, (id) => { if (typeof id === 'string' && /^\d+$/.test(id)) { fetchArticle(id) } else { router.replace('/404') } }, { immediate: true }) </script>这个实现里,requestSeq就是竞态处理的实用手段,每次请求前序号加一,响应回来后只有当前序号是最新的才更新数据。Claude Code 能写出这段,不是因为它多“聪明”,而是因为我的提示词里明确说了“只展示最新一次请求的结果”,它需要做的只是挑一种实现策略。
路由配置这边,它给了独立文件router/article.ts:
import type { RouteRecordRaw } from 'vue-router' export const articleRoutes: RouteRecordRaw[] = [ { path: '/article/:id', name: 'ArticleDetail', component: () => import('@/views/article/ArticleDetail.vue'), meta: { keepAlive: false, title: '文章详情' } } ]keepAlive: false是我在提示词里要求它检查并配置的结果。详情页本身不需要缓存,但列表页需要,这两个 meta 如果搞混了,后面的状态保持逻辑会连环出错。
3.3 一次追加对话把状态保持需求补上
第一轮生成后,我检查了列表页的滚动位置保持。Claude Code 在提示词里已经输出了检查结论:列表页组件没有被keep-alive包裹,所以需要在路由出口处补配置,并在列表页恢复滚动位置。我直接追加了一句话:
请按照你刚才的检测结论,修改 App.vue 中的 RouterView 缓存策略,使列表页路由组件进入 keep-alive,并补全 ScrollBehavior 配置,要求返回时滚动位置和页码都能恢复。它很快给出的方案是在RouterView外层加<KeepAlive :include="['ArticleList']">,然后在scrollBehavior里对带savedPosition的路由恢复滚动位置。这里有个小细节,keep-alive的include匹配的是组件 name,不是路由 name,它帮我检查了列表页的defineOptions({ name: 'ArticleList' })是否存在,这个细节对新手很有用。
实际跑起来之后,从列表页进详情页再返回,页码和滚动位置确实都保留了。这个功能不是提示词里一开始就有,而是第二轮追加对话引导出来的。这说明一个方法论:第一轮提示词解决“主链路”,第二轮追加解决“边界状态”,两者结合才能做出真正可用的页面。
4. 常见问题排查:Claude Code 生成动态路由的典型坑
4.1 路由参数名不匹配导致白屏
Claude Code 生成代码时,路由文件里定义了path: '/article/:id',组件里却用了route.params.articleId或者直接写死了一个route.query.id。这种情况在 AI 生成多文件代码时特别常见,因为它在写组件时,可能“记不清”路由里参数叫什么,就会自己猜一个。
排查方法很简单:打开页面看路由地址,再对照组件里取的参数名,不一致就改组件。更好的办法是在提示词里就把参数名固定下来,比如明确写“路由参数统一使用 id,组件通过route.params.id获取,禁止使用其他参数名”。一次说不清,后面生成 3 个页面你就要改 3 次。
4.2 组件复用时完全不重新请求
这是动态路由页面最有代表性的问题。场景是:在文章 A 详情页点击推荐区跳到文章 B,URL 变了,但页面内容还是文章 A。原因就是组件实例复用,onMounted不会重复执行。
如果生成代码里没写watch(() => route.params.id, ...),你就要让 Claude Code 补上。我习惯的提示词是:“当前组件实例会在路由参数变化时复用,请添加对 route.params.id 的监听,并在回调中重新调用数据请求函数,同时保留 loading 状态。”如果 AI 生成了监听但没设置{ immediate: true },首次进入页面就不会请求数据,这也是一个容易漏的小点。
4.3 刷新后页面 404 或空白
动态路由页面对服务端配置有要求。开发环境 Vite 自带 history fallback 没问题,但生产环境如果是 Nginx,地址/article/123刷新时 Nginx 找不到对应文件,就会返回 404。Claude Code 不会知道你的部署环境,它只负责前端代码。
这块不能在提示词里完全解决,但可以提示它“输出部署注意事项”,让模型主动告诉你需要配置try_files $uri $uri/ /index.html;。对开发来说,我一般把它列为上线前检查项,跟打包构建一起过一遍。如果页面在本地开发正常、上线刷新就 404,优先检查这个。
4.4 错误处理被忽略
很多 AI 生成的详情页只有一个请求成功的路径,没有失败处理。接口 500、超时、断网的情况下一律空白。我测试时模拟过一次断网,页面直接卡在 loading 状态,转个不停。原因就是finally里的 loading 结束逻辑没写。
提示词里的“功能 3”我明确提了“加载失败显示错误提示与重试按钮”,它才在 catch 里补上 error 状态和重试按钮。这里有个小技巧:把“重试”当成一个明确功能来提,不要只写“处理错误”,否则 AI 往往只是console.error一下,根本不做 UI 反馈。
4.5 列表页返回时状态丢失
详情页做完了,返回列表页时发现页码回到第一页、滚动位置在顶部,这也是高频问题。原因有两层:路由组件没有缓存,返回时重新创建,自然回到初始状态;或者有缓存但滚动位置没有恢复。
排查思路是:先看列表页组件有没有被KeepAlive包含,再看scrollBehavior是否做了savedPosition处理。如果两者都配了还是不行,要检查KeepAlive的include正则是否匹配组件 name。这个坑我踩过至少两次,都是 name 大小写不一致导致的静默失败。
| 问题现象 | 可能原因 | 排查切入点 |
|---|---|---|
| 进入页面白屏 | 参数名不匹配 | 检查 route.params 与路由定义是否一致 |
| 切换文章内容不更新 | 未监听路由参数 | 补 watch route.params.id,加 immediate |
| 上线刷新 404 | 服务端未配置 fallback | Nginx try_files / 部署平台路由配置 |
| 接口失败无提示 | 缺少错误处理和重试 | 检查 catch 分支和 finally 状态 |
| 返回列表页码丢失 | keep-alive 未配置 | 检查 include 匹配组件 name、scrollBehavior |
5. 提示词迭代技巧:把一次会话变成可持续维护的资产
5.1 让 AI 先自检再交付
我现在的习惯是,凡涉及多文件、多状态的项目,提示词末尾一定加一条“输出完成后,对照任务清单逐条检查,指出你尚未覆盖的条目”。这一条能让 Claude Code 自己把漏掉的功能主动说出来,而不是让你逐条盯着代码去翻。
有一次它看了自己的输出,承认“任务 6 中 404 路由未配置,只在代码里做了 replace 但 NotFound 页面未接入”,我只需要追加一句“请补充路由配置”就解决了。这种自检机制比你自己 review 几十行代码高效得多,尤其是当你同时对多个文件做改动的时候。
5.2 把验证过的提示词变成团队模板
同一个项目的多个动态页面,比如商品详情页、用户主页、订单详情页,它们的路由参数、组件复用、竞态处理逻辑大同小异。我会把第一次验证有效的提示词存成模板,每次新建页面时替换掉接口路径和页面模块清单,其他部分不动。这样既保证一致性,也降低每次从头写提示词的认知负担。
模板维护有一点要注意:技术栈升级或者项目基础设施变化,比如请求封装换了参数形式,模板里对应的约束条件要同步更新,否则 AI 会持续生成过期用法。我踩过一次坑,request 封装返回结构改了,模板没改,两台新页面都是按旧接口格式写的,顺手就全错了。
5.3 给 AI 提供错误现场,加速修复
Claude Code 生成代码后,如果运行报错,不要只说“这代码跑不起来,帮我修一下”。它没有本地运行环境,看不到你的报错详情。更好的做法是把报错信息直接复制给它,包括浏览器 console 的报错栈、接口返回的状态码、页面的表现现象。信息越具体,它越能定位到准确的代码位置。
比如有次页面报Cannot read properties of null (reading 'title'),我把堆栈和场景发过去,它直接判断是初始化时 article 为 null,模板里又索引了 article.title,然后给出可选链方案article?.title加默认值。整个过程不到 10 秒,比你自己一行行检查快太多了。
以我个人实际操作的经验来说,Claude Code 这类工具的价值不取决于你会多少命令,而取决于你能不能用提示词把“模糊需求”翻译成“精确规格”。动态路由详情页这个案例,前前后后我迭代了四轮提示词,从第一轮的不堪用到最后一轮基本不用改,靠的就是不断把项目里的约定、边界和验收标准塞进提示词里。你每一次补充的细节,都在替未来的自己省时间。