migrate-to-nextjs
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSite Migration Skill
网站迁移Skill
STOP. READ THIS ENTIRE DOCUMENT BEFORE DOING ANYTHING.
This skill migrates legacy websites to modern Next.js. You MUST follow every step exactly. Skipping steps will result in failed migrations.
注意:在进行任何操作前,请完整阅读本文档。
本Skill可将旧版网站迁移至现代化的Next.js框架。你必须严格遵循每一个步骤,跳过步骤会导致迁移失败。
PHASE 1: SETUP
第一阶段:环境搭建
Install all dependencies, ask the user questions, create the Next.js project, and configure MCP. Do not proceed to Phase 2 until everything is verified.
安装所有依赖项,询问用户相关问题,创建Next.js项目,并配置MCP。在所有内容验证通过前,请勿进入第二阶段。
1.1 Install Skills
1.1 安装Skills
Run each command and verify it succeeds:
bash
npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-best-practices --yes
npx skills add https://github.com/vercel-labs/next-skills --skill next-best-practices --yes
npx skills add https://github.com/vercel-labs/next-skills --skill next-cache-components --yes
npx skills add https://github.com/vercel-labs/agent-skills --skill web-design-guidelines --yes运行以下每个命令并验证执行成功:
bash
npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-best-practices --yes
npx skills add https://github.com/vercel-labs/next-skills --skill next-best-practices --yes
npx skills add https://github.com/vercel-labs/next-skills --skill next-cache-components --yes
npx skills add https://github.com/vercel-labs/agent-skills --skill web-design-guidelines --yes1.2 Install Migent
1.2 安装Migent
bash
npm install -g migentVERIFY: returns a version number.
migent --versionbash
npm install -g migent验证:执行应返回版本号。
migent --version1.3 Ask User Questions
1.3 询问用户问题
-
"Which directory contains your legacy site?"
- Scan workspace for ,
package.json,composer.json,Gemfile,index.htmlindex.php - Offer detected options as choices
- Scan workspace for
-
"What port is your legacy site running on?" (or "How do I start it?")
- Common ports: 3000, 4000, 8000, 8080
-
"What should I name the Next.js project?"
- Suggest:
<legacy-name>-next
- Suggest:
-
“你的旧版网站所在目录是哪个?”
- 扫描工作区寻找、
package.json、composer.json、Gemfile、index.html文件index.php - 将检测到的选项提供给用户选择
- 扫描工作区寻找
-
“你的旧版网站运行在哪个端口?”(或“我该如何启动它?”)
- 常见端口:3000、4000、8000、8080
-
“Next.js项目的名称应该是什么?”
- 建议命名格式:
<旧网站名称>-next
- 建议命名格式:
1.4 Validate Legacy Site
1.4 验证旧版网站
bash
ls -la <legacy-directory>
curl -s -o /dev/null -w "%{http_code}" http://localhost:<port>/MUST PASS: Directory exists AND curl returns .
200CAPTURE any observed request patterns. Example: Redirect to /en means the site has localisation — include it in the migration plan.
IF VALIDATION FAILS: Return to user with specific error. Do not guess or proceed.
bash
ls -la <legacy-directory>
curl -s -o /dev/null -w "%{http_code}" http://localhost:<port>/必须通过:目录存在且curl返回状态码。
200记录:任何观察到的请求模式。例如:重定向至/en意味着网站支持多语言本地化——需将此纳入迁移计划。
若验证失败:向用户返回具体错误信息,请勿猜测或继续执行后续步骤。
1.5 Load All Skills
1.5 加载所有Skills
Load all skill contexts now. They will be used throughout the migration.
/next-best-practices
/vercel-react-best-practices
/web-design-guidelinesIMPORTANT: Always use the latest Next.js and tailwindcss (check versions).
立即加载所有Skill上下文,它们将在整个迁移过程中被使用。
/next-best-practices
/vercel-react-best-practices
/web-design-guidelines重要提示:始终使用最新版本的Next.js和tailwindcss(请检查版本)。
1.6 Create Next.js Project
1.6 创建Next.js项目
bash
bunx create-next-app@latest <project-name> \
--typescript \
--tailwind \
--app \
--src-dir \
--import-alias "@/*" \
--use-bun \
--yesNOTE:
- Use , not
bunnpm - Use , not
bunxnpx - ESLint is NOT included — we use Biome
bash
bunx create-next-app@latest <project-name> \
--typescript \
--tailwind \
--app \
--src-dir \
--import-alias "@/*" \
--use-bun \
--yes注意:
- 使用而非
bunnpm - 使用而非
bunxnpx - 不包含ESLint——我们使用Biome
1.7 Install Biome
1.7 安装Biome
bash
cd <project-name>
bun add -D @biomejs/biomeCreate :
biome.jsonjson
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"organizeImports": { "enabled": true },
"linter": {
"enabled": true,
"rules": { "recommended": true }
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
}
}bash
cd <project-name>
bun add -D @biomejs/biome创建文件:
biome.jsonjson
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"organizeImports": { "enabled": true },
"linter": {
"enabled": true,
"rules": { "recommended": true }
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
}
}1.8 Configure MCP
1.8 配置MCP
Configure MCP servers in the workspace root (NOT inside the Next.js project):
workspace/ ← config goes HERE
├── legacy-site/
├── my-next-app/
└── .mcp.jsonCreate :
.mcp.jsonjson
{
"mcpServers": {
"migent": {
"command": "npx",
"args": ["-y", "migent", "mcp"]
}
}
}在工作区根目录(而非Next.js项目内部)配置MCP服务器:
workspace/ ← 配置文件需放在此处
├── legacy-site/
├── my-next-app/
└── .mcp.json创建文件:
.mcp.jsonjson
{
"mcpServers": {
"migent": {
"command": "npx",
"args": ["-y", "migent", "mcp"]
}
}
}1.9 Verify MCP
1.9 验证MCP
Start the Next.js dev server, then test MCP:
bash
bun run devir_capture(port: 3000, route: "/")VERIFY: Returns JSON with .
elementCount > 0启动Next.js开发服务器,然后测试MCP:
bash
bun run devir_capture(port: 3000, route: "/")验证:返回的JSON中。
elementCount > 01.10 Copy Assets
1.10 复制资源文件
bash
mkdir -p <next-project>/public
cp -r <legacy-directory>/images/* <next-project>/public/images/ 2>/dev/null || true
cp -r <legacy-directory>/fonts/* <next-project>/public/fonts/ 2>/dev/null || true
cp -r <legacy-directory>/assets/* <next-project>/public/assets/ 2>/dev/null || truebash
mkdir -p <next-project>/public
cp -r <legacy-directory>/images/* <next-project>/public/images/ 2>/dev/null || true
cp -r <legacy-directory>/fonts/* <next-project>/public/fonts/ 2>/dev/null || true
cp -r <legacy-directory>/assets/* <next-project>/public/assets/ 2>/dev/null || trueSetup Checkpoint
环境搭建检查点
Before proceeding, confirm ALL of the following:
- All 4 skills installed
- Legacy site running and accessible
- Next.js project created with Biome configured
- MCP returning captures for both sites
- Assets copied
IF ANY FAILS: Stop and report the error to the user.
在继续之前,请确认以下所有项均已完成:
- 已安装全部4个Skills
- 旧版网站已启动且可访问
- 已创建Next.js项目并配置好Biome
- MCP可返回两个网站的捕获数据
- 资源文件已复制完成
若有任何项未完成:停止操作并向用户报告错误。
PHASE 2: DISCOVERY
第二阶段:发现与分析
Use MCP tools to capture the legacy site and analyze patterns. DO NOT USE CURL. DO NOT FETCH HTML MANUALLY.
使用MCP工具捕获旧版网站数据并分析模式。禁止使用CURL,禁止手动获取HTML内容。
2.1 Discover Routes
2.1 发现路由
Analyze legacy codebase to find all routes:
- Check if exists
sitemap.xml - Check router files (Express routes, Next.js pages, PHP files)
- Check navigation links in captured IR
分析旧版代码库以找出所有路由:
- 若存在则检查该文件
sitemap.xml - 检查路由文件(Express路由、Next.js页面、PHP文件)
- 检查捕获的IR数据中的导航链接
2.2 Capture All Routes (Parallel)
2.2 批量捕获所有路由(并行执行)
Call for all discovered routes in parallel (batch all calls in a single message). Each creates a fresh Playwright page — no shared state conflicts.
ir_captureir_captureundefined为所有发现的路由调用并行执行(在一条消息中批量调用所有命令)。每个会创建一个全新的Playwright页面——无共享状态冲突。
ir_captureir_captureundefinedCall all in one message — they run concurrently:
在一条消息中调用所有命令——它们会并发执行:
ir_capture(port: <legacy-port>, route: "/")
ir_capture(port: <legacy-port>, route: "/about")
ir_capture(port: <legacy-port>, route: "/contact")
ir_capture(port: <legacy-port>, route: "/")
ir_capture(port: <legacy-port>, route: "/about")
ir_capture(port: <legacy-port>, route: "/contact")
... all discovered routes
... 所有发现的路由
Collect all results. Write captures to `migration.json`:
```json
{
"legacy": {
"directory": "./legacy-site",
"port": 8000,
"framework": "php",
"routes": ["/", "/about", "/contact"]
},
"captures": {
"/": { "elementCount": 150, "animationCount": 12 },
"/about": { "elementCount": 89, "animationCount": 3 }
},
"next": {
"directory": "./my-next-app",
"port": 3000
},
"routeStatus": {
"/": "pending",
"/about": "pending",
"/contact": "pending"
},
"progress": {},
"skippedIssues": []
}
收集所有结果并将捕获数据写入`migration.json`:
```json
{
"legacy": {
"directory": "./legacy-site",
"port": 8000,
"framework": "php",
"routes": ["/", "/about", "/contact"]
},
"captures": {
"/": { "elementCount": 150, "animationCount": 12 },
"/about": { "elementCount": 89, "animationCount": 3 }
},
"next": {
"directory": "./my-next-app",
"port": 3000
},
"routeStatus": {
"/": "pending",
"/about": "pending",
"/contact": "pending"
},
"progress": {},
"skippedIssues": []
}2.3 Analyze JavaScript Patterns
2.3 分析JavaScript模式
IMPORTANT: Legacy JavaScript is ANYTHING that is NOT React or Next.js.
Search legacy codebase for patterns that need conversion:
bash
undefined重要提示:旧版JavaScript指所有非React或Next.js的JavaScript代码。
在旧版代码库中搜索需要转换的模式:
bash
undefinedFind jQuery
查找jQuery
grep -r "jquery|jQuery|\$(" <legacy-directory> --include=".js" --include=".html" --include="*.php"
grep -r "jquery|jQuery|\$(" <legacy-directory> --include=".js" --include=".html" --include="*.php"
Find inline handlers
查找内联事件处理器
grep -r "onclick=|onsubmit=|onchange=" <legacy-directory> --include=".html" --include=".php"
Document findings in `migration.json` under `legacy.javascript`.grep -r "onclick=|onsubmit=|onchange=" <legacy-directory> --include=".html" --include=".php"
将发现的结果记录在`migration.json`的`legacy.javascript`字段下。2.4 Detect Locales & Validate Links
2.4 检测多语言本地化并验证链接
Check results for locale patterns:
ir_capture- Redirect-based detection: If returns
ir_capture(e.g.,redirects→/), the site uses locale prefixes./en/ - Route-based detection: If discovered routes have locale prefixes (e.g., ,
/en/about), locales are in use./fr/about
If locales are detected:
- Set up Next.js i18n middleware for locale routing
- Create with locale detection and redirect logic
src/middleware.ts - Use or Next.js built-in i18n for locale-aware Link components
next-intl - Validate all internal links include the correct locale prefix
- Map each locale route to its Next.js equivalent
Internal link validation: Check against detected locales. Links missing locale prefixes will break in the migrated site.
ir_captureinternalLinks检查结果中的本地化模式:
ir_capture- 基于重定向的检测:若返回
ir_capture数据(例如:redirects→/),则网站使用本地化前缀。/en/ - 基于路由的检测:若发现的路由包含本地化前缀(例如:、
/en/about),则网站已启用本地化。/fr/about
若检测到本地化:
- 为Next.js配置i18n中间件以支持本地化路由
- 创建文件,包含本地化检测和重定向逻辑
src/middleware.ts - 使用或Next.js内置的i18n功能实现支持本地化的Link组件
next-intl - 验证所有内部链接均包含正确的本地化前缀
- 将每个本地化路由映射至对应的Next.js路由
内部链接验证:检查返回的数据是否符合检测到的本地化规则。缺少本地化前缀的链接在迁移后的网站中会失效。
ir_captureinternalLinks2.5 Install Conditional Dependencies
2.5 安装条件依赖
Based on discovery results:
If animations were detected in any :
ir_capturebash
cd <next-project>
bun add framer-motion根据发现阶段的结果安装依赖:
若在任意结果中检测到动画:
ir_capturebash
cd <next-project>
bun add framer-motionPHASE 3: MIGRATE (PER ROUTE)
第三阶段:按路由迁移
For EACH route discovered in Phase 2, repeat steps 3.1 through 3.4. Start with to build the shared shell first.
/针对第二阶段发现的每个路由,重复执行3.1至3.4步骤。先从路由开始,构建共享的页面框架。
/CRITICAL RULES — VIOLATIONS ARE FAILURES
关键规则——违反即视为迁移失败
FORBIDDEN:
- — NEVER use to copy legacy HTML
dangerouslySetInnerHTML - or any inline event handlers
onclick="..." - jQuery or any jQuery patterns
- tags with inline JavaScript
<script> - instead of
class=className=
REQUIRED:
- Proper JSX with
className - React event handlers ()
onClick={handler} - Match computed styles visually (Tailwind utilities, CSS modules, or global CSS — see Appendix A)
- for images
next/image - Server Components by default
- only when needed
'use client'
RECOMMENDED (enforce in Phase 5):
- for fonts (see Appendix C)
next/font - shadcn/ui components for form elements, dialogs, tables (see Appendix D)
- Tailwind utilities over CSS modules/global CSS
禁止操作:
- 使用——绝不能用此方法复制旧版HTML
dangerouslySetInnerHTML - 使用或任何内联事件处理器
onclick="..." - 使用jQuery或任何jQuery模式
- 使用包含内联JavaScript的标签
<script> - 使用而非
class=className=
强制要求:
- 使用标准JSX语法并搭配
className - 使用React事件处理器()
onClick={handler} - 视觉上匹配计算样式(使用Tailwind工具类、CSS模块或全局CSS——见附录A)
- 图片使用组件
next/image - 默认使用Server Components
- 仅在必要时添加指令
'use client'
推荐做法(在第五阶段强制执行):
- 字体使用(见附录C)
next/font - 表单元素、对话框、表格等使用shadcn/ui组件(见附录D)
- 优先使用Tailwind工具类而非CSS模块/全局CSS
3.1 Build Page
3.1 构建页面
For (first route): also build the shared shell:
/- Create (RootLayout) with HTML structure, metadata
src/app/layout.tsx - Extract shared components: Header, Footer, Nav →
src/components/ - Register components in under
migration.jsoncomponents
For all routes (including ):
/Read captured IR from for this route.
migration.jsonUse to get full computed styles for specific elements.
ir_inspect(selector: "...", site: "legacy")Based on captured IR:
- Create
src/app/<route>/page.tsx - Convert layout structure to JSX
- Convert captured computed styles to Tailwind (see Appendix A)
- Convert event handlers to React patterns
- Import shared components from — do NOT recreate them
src/components/ - Recreate animations using captured animation data (see Appendix B)
针对路由(第一个路由):同时构建共享页面框架:
/- 创建(RootLayout),包含HTML结构和元数据
src/app/layout.tsx - 提取共享组件:Header、Footer、Nav → 存入目录
src/components/ - 在的
migration.json字段下注册这些组件components
针对所有路由(包括):
/从中读取当前路由的捕获IR数据。
migration.json使用获取特定元素的完整计算样式。
ir_inspect(selector: "...", site: "legacy")基于捕获的IR数据:
- 创建文件
src/app/<route>/page.tsx - 将布局结构转换为JSX语法
- 将捕获的计算样式转换为Tailwind工具类(见附录A)
- 将事件处理器转换为React模式
- 从导入共享组件——请勿重复创建
src/components/ - 使用捕获的动画数据重新实现动画效果(见附录B)
3.2 Code Quality Gate
3.2 代码质量检查
BEFORE visual validation, verify no anti-patterns:
bash
undefined在视觉验证之前,验证代码中不存在反模式:
bash
undefinedAnti-patterns (ALL MUST RETURN no results)
反模式检查(所有命令必须返回空结果)
grep -r "dangerouslySetInnerHTML" <next-project>/src/
grep -r 'onclick="' <next-project>/src/
grep -r 'class="' <next-project>/src/ --include=".tsx" --include=".jsx"
grep -r 'style={{' <next-project>/src/ --include=".tsx" --include=".jsx"
grep -r "from ['"]jquery['"]" <next-project>/src/
**ALL MUST RETURN**: No results. Fix any violations before proceeding.grep -r "dangerouslySetInnerHTML" <next-project>/src/
grep -r 'onclick="' <next-project>/src/
grep -r 'class="' <next-project>/src/ --include=".tsx" --include=".jsx"
grep -r 'style={{' <next-project>/src/ --include=".tsx" --include=".jsx"
grep -r "from ['"]jquery['"]" <next-project>/src/
**所有命令必须返回**:无结果。若有违反项,请在继续之前修复。3.3 Visual Validation Loop
3.3 视觉验证循环
Start watch mode:
ir_start(legacyPort: <legacy-port>, nextPort: 3000, legacyRoute: "<route>", nextRoute: "<route>")
→ { status: "watching", match: {...}, totalIssues: N, firstIssue: {...} }Loop until match >= 95%:
result = ir_next()
IF result.clsBlocked:
- CLS score is above 0.1 — ir_next REFUSES to serve other issues
- Read result.cls.topShifters to identify which elements shifted
- Fix using result.suggestedFixes:
1. Font shift → next/font with display: "swap", adjustFontFallback: true
2. Image shift → next/image with explicit width + height
3. Dynamic content → min-height or skeleton placeholders
4. Embeds → fixed aspect-ratio container
- Save file → watch recaptures → call ir_next again
- Repeat until clsBlocked is gone
IF result.regressionBlocked:
- New issues were introduced — fix the regression first
- Save file → watch recaptures → call ir_next again
IF result.issue exists:
- Read issue details (selector, styles, position)
- Fix the specific issue
- Save file → wait for rebuild → call ir_next again
- After 3 failed attempts on the same issue: ir_next(skip: true)
- Document skipped issue in migration.json under skippedIssues
IF result.complete or match >= 95%:
- Proceed to 3.4CLS is a hard gate. will not serve style/content/missing issues until CLS score is "good" (<= 0.1). This is enforced by the tool, not by convention. You cannot skip it.
ir_next启动监听模式:
ir_start(legacyPort: <legacy-port>, nextPort: 3000, legacyRoute: "<route>", nextRoute: "<route>")
→ { status: "watching", match: {...}, totalIssues: N, firstIssue: {...} }循环执行直到匹配度≥95%:
result = ir_next()
如果 result.clsBlocked:
- CLS分数超过0.1 —— ir_next会拒绝返回其他问题
- 查看result.cls.topShifters以确定哪些元素导致了偏移
- 使用result.suggestedFixes修复:
1. 字体偏移 → 使用next/font并设置display: "swap", adjustFontFallback: true
2. 图片偏移 → 使用next/image并指定明确的width和height
3. 动态内容 → 设置min-height或骨架屏占位符
4. 嵌入内容 → 使用固定宽高比的容器
- 保存文件 → 等待监听模式重新捕获数据 → 再次调用ir_next
- 重复执行直到clsBlocked状态解除
如果 result.regressionBlocked:
- 引入了新问题 —— 先修复回归问题
- 保存文件 → 等待监听模式重新捕获数据 → 再次调用ir_next
如果 result.issue 存在:
- 查看问题详情(选择器、样式、位置)
- 修复具体问题
- 保存文件 → 等待重建完成 → 再次调用ir_next
- 若同一问题修复3次仍失败:执行ir_next(skip: true)
- 将跳过的问题记录在migration.json的skippedIssues字段下
如果 result.complete 或 匹配度≥95%:
- 进入3.4步骤CLS是硬性检查项。在CLS分数降至“良好”(≤0.1)之前,不会返回样式/内容/缺失类问题。此规则由工具强制执行,而非约定俗成,无法跳过。
ir_next3.4 Mark Route Complete
3.4 标记路由迁移完成
ir_stop()Update to in . Move to next route.
routeStatus"validated"migration.jsonIf only skipped issues remain and match is below 95%: update to , mark route for human review, and continue.
routeStatus"failed"ir_stop()在中将更新为。继续处理下一个路由。
migration.jsonrouteStatus"validated"若仅剩余跳过的问题且匹配度低于95%:将更新为,标记该路由需人工审核,然后继续处理其他路由。
routeStatus"failed"PHASE 4: COMPLETION
第四阶段:迁移完成
4.1 Generate Report
4.1 生成迁移报告
Create with:
MIGRATION_REPORT.md- Summary (routes migrated, match percentages)
- Per-route breakdown
- Skipped issues requiring human review
- Components created
- Recommendations
创建文件,包含以下内容:
MIGRATION_REPORT.md- 摘要(已迁移路由数量、匹配度百分比)
- 各路由详细情况
- 需人工审核的跳过问题
- 创建的组件列表
- 后续建议
4.2 Cleanup
4.2 清理操作
ir_stop()ir_stop()PHASE 5: MODERNIZE (OPTIONAL)
第五阶段:现代化改造(可选)
Post-migration pass to adopt modern component patterns and tooling. Run this after all routes pass visual parity in Phase 4. Each step is independent — skip any that don't apply.
迁移完成后的优化步骤,采用现代组件模式和工具。需在第三阶段所有路由通过视觉一致性检查后执行此阶段。每个步骤相互独立——可跳过不适用的步骤。
5.1 Install shadcn/ui
5.1 安装shadcn/ui
bash
cd <next-project>
bunx shadcn@latest init -ybash
cd <next-project>
bunx shadcn@latest init -y5.2 Configure shadcn MCP
5.2 配置shadcn MCP
Add to :
.mcp.jsonjson
{
"mcpServers": {
"migent": {
"command": "npx",
"args": ["-y", "migent", "mcp"]
},
"shadcn": {
"command": "npx",
"args": ["shadcn@latest", "mcp"]
}
}
}在中添加以下内容:
.mcp.jsonjson
{
"mcpServers": {
"migent": {
"command": "npx",
"args": ["-y", "migent", "mcp"]
},
"shadcn": {
"command": "npx",
"args": ["shadcn@latest", "mcp"]
}
}
}5.3 Install Components
5.3 安装组件
Use data to install the right components:
ir_captureuiPatterns.shadcnComponentsNeededbash
undefined使用返回的数据安装合适的组件:
ir_captureuiPatterns.shadcnComponentsNeededbash
undefinedExample: if uiPatterns shows Button, Dialog, Table, NavigationMenu
示例:若uiPatterns显示需要Button、Dialog、Table、NavigationMenu
bunx shadcn@latest add button dialog table navigation-menu -y
Also search for **blocks** that match legacy page patterns (e.g., login forms, dashboards, pricing pages).bunx shadcn@latest add button dialog table navigation-menu -y
同时搜索与旧版页面模式匹配的**区块**(例如:登录表单、仪表盘、定价页面)。5.4 Convert to shadcn Components
5.4 转换为shadcn组件
Replace raw HTML elements with shadcn equivalents (see Appendix D):
- →
<button><Button> - →
<input><Input> - →
<table><Table> - /
<dialog>→.modal<Dialog>
将原生HTML元素替换为对应的shadcn组件(见附录D):
- →
<button><Button> - →
<input><Input> - →
<table><Table> - /
<dialog>→.modal<Dialog>
5.5 Convert to Tailwind Utilities
5.5 转换为Tailwind工具类
Replace CSS modules and global CSS with Tailwind utilities where possible. Use Appendix A as a mapping reference.
尽可能将CSS模块和全局CSS替换为Tailwind工具类。可参考附录A的映射表。
5.6 Convert to next/font
5.6 转换为next/font
Replace declarations with (see Appendix C).
@font-facenext/font将声明替换为(见附录C)。
@font-facenext/font5.7 Verify No Visual Regressions
5.7 验证无视觉回归
Run again after modernization to confirm no regressions:
ir_startir_start(legacyPort: <legacy-port>, nextPort: 3000, legacyRoute: "<route>", nextRoute: "<route>")Check that match percentages are unchanged or improved.
现代化改造完成后,再次运行以确认无回归问题:
ir_startir_start(legacyPort: <legacy-port>, nextPort: 3000, legacyRoute: "<route>", nextRoute: "<route>")检查匹配度百分比是否保持不变或有所提升。
5.8 Code Quality Checks
5.8 代码质量检查
bash
undefinedbash
undefinedshadcn enforcement — raw HTML elements should be replaced
验证shadcn组件的使用——原生HTML元素应已被替换
grep -rn '<button' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
grep -rn '<input' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
grep -rn '<textarea' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
grep -rn '<select' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
grep -rn '<table' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
grep -rn '<dialog' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
grep -rn '<button' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
grep -rn '<input' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
grep -rn '<textarea' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
grep -rn '<select' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
grep -rn '<table' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
grep -rn '<dialog' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
Font enforcement — no raw @font-face
验证字体使用——禁止使用原生@font-face
grep -r "@font-face" <next-project>/src/
grep -r "@font-face" <next-project>/src/
Verify shadcn IS being used
验证shadcn组件已被正确使用
grep -rn "from ['"]@/components/ui/" <next-project>/src/ --include="*.tsx"
grep -rn "from ['"]@/components/ui/" <next-project>/src/ --include="*.tsx"
**所有命令必须返回**:无结果(除shadcn组件验证命令应返回匹配结果外)。这些检查在第五阶段强制执行,第三阶段迁移过程中不做要求。
---Legacy CSS class names should be converted to Tailwind
附录A:捕获样式 → Tailwind映射表
grep -rE 'className="[^"][a-z]+_[a-z]+' <next-project>/src/ --include=".tsx"
---使用获取计算样式,然后按照以下规则转换:
ir_inspect(selector: "...", site: "legacy")颜色(backgroundColor、color、borderColor):
rgb(196, 30, 58) → bg-[#c41e3a] 或 bg-red-600(若颜色匹配度较高)
rgb(255, 255, 255) → bg-white
rgb(0, 0, 0) → bg-black
rgba(0,0,0,0.5) → bg-black/50间距(padding、margin):
padding: "16px" → p-4
padding: "15px 20px" → py-[15px] px-5
margin: "0 auto" → mx-auto
margin: "24px 0 0 0" → mt-6排版:
fontSize: "14px" → text-sm
fontSize: "18px" → text-lg
fontSize: "32px" → text-3xl
fontWeight: "700" → font-bold
fontWeight: "600" → font-semibold
lineHeight: "1.5" → leading-normal
textAlign: "center" → text-center布局:
display: "flex" → flex
display: "grid" → grid
flexDirection: "column" → flex-col
justifyContent: "center" → justify-center
alignItems: "center" → items-center
gap: "16px" → gap-4尺寸:
width: "100%" → w-full
maxWidth: "1280px" → max-w-7xl
height: "auto" → h-auto
minHeight: "100vh" → min-h-screen定位:
position: "absolute" → absolute
position: "relative" → relative
position: "fixed" → fixed
top: "0px" → top-0
left: "50%" → left-1/2边框:
borderRadius: "8px" → rounded-lg
borderRadius: "9999px" → rounded-full
borderWidth: "1px" → border
borderColor: "rgb(229,231,235)" → border-gray-200字体样式:
fontStyle: "italic" → italic
fontStyle: "normal" → not-italic文本转换:
textTransform: "uppercase" → uppercase
textTransform: "lowercase" → lowercase
textTransform: "capitalize" → capitalize
textTransform: "none" → normal-case文本装饰:
textDecoration: "underline" → underline
textDecoration: "line-through" → line-through
textDecoration: "none" → no-underline溢出:
overflow: "hidden" → overflow-hidden
overflow: "auto" → overflow-auto
overflow: "scroll" → overflow-scroll
overflowX: "auto" → overflow-x-auto
overflowY: "hidden" → overflow-y-hidden网格布局:
gridTemplateColumns: "repeat(3, 1fr)" → grid-cols-3
gridTemplateColumns: "repeat(4, minmax(0, 1fr))" → grid-cols-4
gridTemplateColumns: "200px 1fr" → grid-cols-[200px_1fr]变换:
transform: "translateX(-50%)" → -translate-x-1/2
transform: "rotate(45deg)" → rotate-45
transform: "scale(1.1)" → scale-110效果:
opacity: "0.5" → opacity-50
boxShadow: "0 1px 3px rgba(0,0,0,0.1)" → shadow-sm
boxShadow: "0 10px 15px rgba(0,0,0,0.1)" → shadow-lg自定义值(无对应Tailwind工具类时使用):
padding: "13px" → p-[13px]
backgroundColor: "#c41e3a" → bg-[#c41e3a]
fontSize: "17px" → text-[17px]
maxWidth: "1140px" → max-w-[1140px]ERROR HANDLING
附录B:重实现动画效果
MCP tool fails
—
Stop and report to user. Do not attempt workarounds.
从返回的数据中提取信息:
ir_captureanimationsCSS @keyframes → Framer Motion:
tsx
// 捕获的数据:{ name: "fadeInUp", duration: "0.6s", timingFunction: "ease-out" }
import { motion } from 'framer-motion';
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
>CSS @keyframes → Tailwind动画:
css
// 添加至globals.css文件——复制捕获的keyframes规则
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}tsx
<div className="animate-[fadeInUp_0.6s_ease-out]">过渡效果:
tsx
// 捕获的数据:{ property: "background-color", duration: "0.2s", timingFunction: "ease" }
<button className="transition-colors duration-200 ease-in-out hover:bg-red-700">jQuery动画 → Framer Motion:
tsx
// 捕获的数据:jQueryAnimations: [".fadeIn(300)"]
import { AnimatePresence, motion } from 'framer-motion';
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
>
)}
</AnimatePresence>Site unreachable
附录C:字体迁移
Stop and ask user to restart the server.
从返回的字段中读取字体数据。针对每个检测到的字体族:
ir_capturefontsGoogle Fonts → :
next/font/googletsx
import { Inter, Roboto } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
weight: ['400', '700'], // 来自fonts[].weight
style: ['normal', 'italic'], // 来自fonts[].style
display: 'swap', // 来自fonts[].display或默认使用'swap'
variable: '--font-inter',
});自定义字体 → :
next/font/localtsx
import localFont from 'next/font/local';
const customFont = localFont({
src: [
{ path: './fonts/custom-regular.woff2', weight: '400', style: 'normal' },
{ path: './fonts/custom-bold.woff2', weight: '700', style: 'normal' },
],
variable: '--font-custom',
display: 'swap',
});在中应用:
layout.tsxtsx
export default function RootLayout({ children }) {
return (
<html lang="en" className={`${inter.variable} ${customFont.variable}`}>
<body>{children}</body>
</html>
);
}在中配置:
tailwind.config.tsts
fontFamily: {
sans: ['var(--font-inter)', ...defaultTheme.fontFamily.sans],
custom: ['var(--font-custom)'],
},下载字体文件:若返回的包含URL,将.woff2文件下载至目录,供使用。
ir_capturefonts[].srcpublic/fonts/next/font/localRESUMABILITY
附录D:shadcn组件映射表(第五阶段)
migration.jsonrouteStatus| Status | Meaning |
|---|---|
| Not yet migrated |
| Passed visual validation (match >= 95%) |
| Below 95% after exhausting fixes, needs human review |
If exists when is invoked:
migration.json/migration- Read existing state
- Skip routes entirely
validated - Resume from first route
pending - Retry routes if user requests it
failed
使用shadcn组件替代原生HTML元素。第五阶段(现代化改造)期间可参考此表。将旧版元素映射为对应的shadcn组件:
| 旧版HTML元素 | shadcn组件 | 导入路径 |
|---|---|---|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
转换示例:
tsx
// 错误示例 - 使用原生HTML
<button className="bg-red-600 text-white px-4 py-2 rounded">Submit</button>
// 正确示例 - 使用shadcn Button组件
import { Button } from "@/components/ui/button";
<Button className="bg-red-600 text-white">Submit</Button>tsx
// 错误示例 - 使用原生HTML表格
<table><tr><td>Name</td></tr></table>
// 正确示例 - 使用shadcn Table组件
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
<Table>
<TableBody>
<TableRow>
<TableCell>Name</TableCell>
</TableRow>
</TableBody>
</Table>第五阶段需替换原生HTML元素(、、、等)。
<button><input><table><dialog>第五阶段代码质量检查:
bash
undefinedAPPENDIX A: Captured Styles → Tailwind Mapping
CSS中禁止使用原生@font-face(必须使用next/font)
Use to get computed styles, then convert:
ir_inspect(selector: "...", site: "legacy")Colors (backgroundColor, color, borderColor):
rgb(196, 30, 58) → bg-[#c41e3a] or bg-red-600 (if close match)
rgb(255, 255, 255) → bg-white
rgb(0, 0, 0) → bg-black
rgba(0,0,0,0.5) → bg-black/50Spacing (padding, margin):
padding: "16px" → p-4
padding: "15px 20px" → py-[15px] px-5
margin: "0 auto" → mx-auto
margin: "24px 0 0 0" → mt-6Typography:
fontSize: "14px" → text-sm
fontSize: "18px" → text-lg
fontSize: "32px" → text-3xl
fontWeight: "700" → font-bold
fontWeight: "600" → font-semibold
lineHeight: "1.5" → leading-normal
textAlign: "center" → text-centerLayout:
display: "flex" → flex
display: "grid" → grid
flexDirection: "column" → flex-col
justifyContent: "center" → justify-center
alignItems: "center" → items-center
gap: "16px" → gap-4Sizing:
width: "100%" → w-full
maxWidth: "1280px" → max-w-7xl
height: "auto" → h-auto
minHeight: "100vh" → min-h-screenPosition:
position: "absolute" → absolute
position: "relative" → relative
position: "fixed" → fixed
top: "0px" → top-0
left: "50%" → left-1/2Borders:
borderRadius: "8px" → rounded-lg
borderRadius: "9999px" → rounded-full
borderWidth: "1px" → border
borderColor: "rgb(229,231,235)" → border-gray-200Font Style:
fontStyle: "italic" → italic
fontStyle: "normal" → not-italicText Transform:
textTransform: "uppercase" → uppercase
textTransform: "lowercase" → lowercase
textTransform: "capitalize" → capitalize
textTransform: "none" → normal-caseText Decoration:
textDecoration: "underline" → underline
textDecoration: "line-through" → line-through
textDecoration: "none" → no-underlineOverflow:
overflow: "hidden" → overflow-hidden
overflow: "auto" → overflow-auto
overflow: "scroll" → overflow-scroll
overflowX: "auto" → overflow-x-auto
overflowY: "hidden" → overflow-y-hiddenGrid:
gridTemplateColumns: "repeat(3, 1fr)" → grid-cols-3
gridTemplateColumns: "repeat(4, minmax(0, 1fr))" → grid-cols-4
gridTemplateColumns: "200px 1fr" → grid-cols-[200px_1fr]Transform:
transform: "translateX(-50%)" → -translate-x-1/2
transform: "rotate(45deg)" → rotate-45
transform: "scale(1.1)" → scale-110Effects:
opacity: "0.5" → opacity-50
boxShadow: "0 1px 3px rgba(0,0,0,0.1)" → shadow-sm
boxShadow: "0 10px 15px rgba(0,0,0,0.1)" → shadow-lgArbitrary values (when no Tailwind match):
padding: "13px" → p-[13px]
backgroundColor: "#c41e3a" → bg-[#c41e3a]
fontSize: "17px" → text-[17px]
maxWidth: "1140px" → max-w-[1140px]grep -r "@font-face" <next-project>/src/
APPENDIX B: Recreating Animations
禁止在components/ui/目录外使用原生<button>(必须使用shadcn Button组件)
From captured data in :
animationsir_captureCSS @keyframes → Framer Motion:
tsx
// Captured: { name: "fadeInUp", duration: "0.6s", timingFunction: "ease-out" }
import { motion } from 'framer-motion';
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
>CSS @keyframes → Tailwind animation:
css
/* Add to globals.css — copy the captured keyframes rule */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}tsx
<div className="animate-[fadeInUp_0.6s_ease-out]">Transitions:
tsx
// Captured: { property: "background-color", duration: "0.2s", timingFunction: "ease" }
<button className="transition-colors duration-200 ease-in-out hover:bg-red-700">jQuery animations → Framer Motion:
tsx
// Captured: jQueryAnimations: [".fadeIn(300)"]
import { AnimatePresence, motion } from 'framer-motion';
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
>
)}
</AnimatePresence>grep -rn '<button' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
APPENDIX C: Font Migration
禁止在components/ui/目录外使用原生<input>(必须使用shadcn Input组件)
Read font data from response ( section). For each detected font family:
ir_capturefontsGoogle Fonts → :
next/font/googletsx
import { Inter, Roboto } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
weight: ['400', '700'], // from fonts[].weight
style: ['normal', 'italic'], // from fonts[].style
display: 'swap', // from fonts[].display or default to 'swap'
variable: '--font-inter',
});Custom Fonts → :
next/font/localtsx
import localFont from 'next/font/local';
const customFont = localFont({
src: [
{ path: './fonts/custom-regular.woff2', weight: '400', style: 'normal' },
{ path: './fonts/custom-bold.woff2', weight: '700', style: 'normal' },
],
variable: '--font-custom',
display: 'swap',
});Apply in :
layout.tsxtsx
export default function RootLayout({ children }) {
return (
<html lang="en" className={`${inter.variable} ${customFont.variable}`}>
<body>{children}</body>
</html>
);
}Configure in :
tailwind.config.tsts
fontFamily: {
sans: ['var(--font-inter)', ...defaultTheme.fontFamily.sans],
custom: ['var(--font-custom)'],
},Download font files: If contains URLs, download .woff2 files to for .
ir_capturefonts[].srcpublic/fonts/next/font/localgrep -rn '<input' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
APPENDIX D: shadcn Component Mapping (Phase 5)
禁止在components/ui/目录外使用原生<table>(必须使用shadcn Table组件)
Use shadcn components instead of raw HTML. Reference this during Phase 5 (Modernize). Map legacy elements:
| Legacy HTML | shadcn Component | Import |
|---|---|---|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
Example conversion:
tsx
// WRONG - raw HTML
<button className="bg-red-600 text-white px-4 py-2 rounded">Submit</button>
// CORRECT - shadcn Button
import { Button } from "@/components/ui/button";
<Button className="bg-red-600 text-white">Submit</Button>tsx
// WRONG - raw HTML table
<table><tr><td>Name</td></tr></table>
// CORRECT - shadcn Table
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
<Table>
<TableBody>
<TableRow>
<TableCell>Name</TableCell>
</TableRow>
</TableBody>
</Table>Raw HTML elements (, , , ) should be replaced in Phase 5.
<button><input><table><dialog>Code quality checks (run during Phase 5):
bash
undefinedgrep -rn '<table' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
No raw @font-face in CSS (must use next/font)
验证shadcn组件已被正确使用
grep -r "@font-face" <next-project>/src/
grep -rn "from ['"]@/components/ui/" <next-project>/src/ --include="*.tsx"
**所有命令必须返回**:无结果(除shadcn组件验证命令应返回匹配结果外)。这些检查在第五阶段强制执行,第三阶段迁移过程中不做要求。
---No raw <button> outside components/ui/ (must use shadcn Button)
附录E:MCP工具参考
—
ir_capture
grep -rn '<button' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
捕获页面的DOM树、计算样式、动画元数据和CLS分数。
ir_capture(port: number, route?: string, width?: number, height?: number)确定性捕获流程:
- 等待网络空闲
- 强制加载所有懒加载图片
- 等待所有图片和字体加载完成
- 提取动画元数据(在动画完成前)
- 强制所有动画进入结束状态
- 等待DOM稳定
返回值:
- 布局模式(header、nav、footer、sidebar、main)
- 组件层级结构
- 包含计算样式的顶层元素
- 动画数据:keyframes、animatedElements、transitionElements、jQueryAnimations
- CLS数据:score、rating、top shifters
- 字体数据():totalFontFaces、fontFaces、uniqueFamilies
fonts - UI模式():包含shadcnComponentsNeeded的模式
uiPatterns - 重定向数据():{ from, to, statusCode }数组——用于检测本地化
redirects - 内部链接():{ total, links[] }——用于路由验证
internalLinks
No raw <input> outside components/ui/ (must use shadcn Input)
ir_start
grep -rn '<input' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
启动迁移监听模式。捕获两个网站的数据、对比差异、启动文件监听器,返回第一个问题。
ir_start(legacyPort, nextPort, legacyRoute?, nextRoute?, watchPaths?)返回值:。
{ status: "watching", match: {...}, totalIssues: N, firstIssue: {...} }No raw <table> outside components/ui/ (must use shadcn Table)
ir_next
grep -rn '<table' <next-project>/src/ --include=".tsx" --include=".jsx" | grep -v 'components/ui/'
获取下一个需要修复的问题。会被CLS检查和回归问题阻塞。
ir_next(skip?: boolean)- —— 修复失败后跳过当前问题,进入下一个问题
skip: true - 返回值:包含选择器、位置、样式和修复建议的问题详情。
Verify shadcn components ARE being used
ir_status
grep -rn "from ['"]@/components/ui/" <next-project>/src/ --include="*.tsx"
**ALL MUST RETURN**: No results (except shadcn verification which SHOULD return matches). These checks are enforced in Phase 5, not during Phase 3 migration.
---迁移进度:匹配度百分比、按严重程度分类的问题数量、CLS分数、回归状态。
APPENDIX E: MCP Tools Reference
ir_inspect
ir_capture
—
Capture a page's DOM tree, computed styles, animation metadata, and CLS score.
ir_capture(port: number, route?: string, width?: number, height?: number)Deterministic capture sequence:
- Waits for network idle
- Forces all lazy images to load
- Waits for all images and fonts
- Extracts animation metadata (BEFORE finishing animations)
- Forces all animations to END STATE
- Waits for DOM stability
Returns:
- Layout patterns (header, nav, footer, sidebar, main)
- Component hierarchy
- Top-level elements with computed styles
- Animation data: keyframes, animatedElements, transitionElements, jQueryAnimations
- CLS data: score, rating, top shifters
- Font data (): totalFontFaces, fontFaces, uniqueFamilies
fonts - UI patterns (): patterns with shadcnComponentsNeeded
uiPatterns - Redirects (): Array of { from, to, statusCode } — useful for locale detection
redirects - Internal links (): { total, links[] } — for route validation
internalLinks
通过选择器或文本检查元素。
ir_inspect(selector: string, site?: "legacy" | "next" | "both")- 或
site="legacy":返回单一侧元素的完整样式、矩形区域和代码片段"next" - (默认值):返回两侧元素的对比数据及样式差异
site="both"
ir_start
ir_stop
Start migration watch mode. Captures both sites, diffs, starts file watcher, returns first issue.
ir_start(legacyPort, nextPort, legacyRoute?, nextRoute?, watchPaths?)Returns: .
{ status: "watching", match: {...}, totalIssues: N, firstIssue: {...} }停止监听模式并关闭浏览器。
ir_next
—
Get next issue to fix. Blocks on CLS gate and regressions.
ir_next(skip?: boolean)- — skip current issue after failed attempts, advance to next
skip: true - Returns: Issue with selector, position, styles, and fix suggestion.
—
ir_status
—
Migration progress: match percentages, issue counts by severity, CLS score, regression state.
—
ir_inspect
—
Inspect element by selector or text.
ir_inspect(selector: string, site?: "legacy" | "next" | "both")- or
site="legacy": full styles, rect, snippet for one side"next" - (default): side-by-side comparison with style diffs
site="both"
—
ir_stop
—
Stop watch mode and close browser.
—