配套文档:performance-analysis.html · 初版 2026-04-19 · 修订 2026-04-20(根据 self-review 修 C1–C5 / M1–M4,回退原 P0.0 的 localeDetection 关停方案) · 代码基准
release@3f254fd71
ignoreBuildErrors / ignoreDuringBuilds 放在 P0,避免一边优化一边引入回归。基线:Lighthouse 2026-04-19 实测 · 8 个页面 · 最差页取值
| 指标 | 基线实测 | 最差页 | P0 末目标 | P1 末目标 | 最终目标 | 如何测 |
|---|---|---|---|---|---|---|
| Performance Score | 25–46 | /app/workflow | ≥ 50 | ≥ 80 | ≥ 90 | Lighthouse CLI |
| LCP (移动 Slow 4G) | 44.9 s | /app/workflow | < 15 s | < 5 s | < 2.5 s | Lighthouse × 3 取中位 |
| FCP | 9.7 s | /app/workflow | < 4 s | < 2 s | < 1.8 s | Lighthouse |
| TBT | 2.4 s | /app/workflow | < 1 s | < 0.4 s | < 0.2 s | Lighthouse |
| TTI | 45.2 s | /app/workflow | < 18 s | < 7 s | < 3.8 s | Lighthouse |
| CLS | 0.145 | /app/strategy-studio | < 0.1 | < 0.08 | < 0.05 | strategy-studio 专题修 |
| 主线程工作 | 45.8 s | /app/strategy-studio | < 18 s | < 6 s | < 2 s | Lighthouse main-thread-tasks |
| Unused JS | 1.6 MB | /home | < 1 MB | < 400 KB | < 200 KB | Lighthouse unused-javascript |
| 单页面下载 | 11.0 MB | /app/strategy-studio | < 6 MB | < 3 MB | < 1.5 MB | Lighthouse network-requests |
| 重定向链 (P0.0) | 7.8 s | /app/workflow | 0 s | 0 s | 0 s | Lighthouse redirects audit |
| INP(真实用户) | 未测,估 > 500 ms | – | < 300 ms | < 200 ms | < 100 ms | Cloudflare Web Analytics P75 |
| public/ 总体积 | 138 MB | – | < 95 MB | < 60 MB | < 40 MB | du -sh public/ |
全局硬性门槛(P1 起):首页、/about、/use-case、/how-to-guides/[slug] 必须全部为 SSG;对应路由不能出现 'use client'。
| URL | 类型 | 基线 Score | 基线 LCP | 关注指标 |
|---|---|---|---|---|
/home |
产品首页(匿名) | 28 | 20.8 s | LCP, TBT |
/about |
关于页 | 29 | 21.0 s | LCP(大 SVG + YouTube) |
/use-cases |
use-cases 列表页 | 33 | 17.1 s | LCP, 路由包体 |
/how-to-guides/btc-eth-trading-strategy-complete-battle-plan |
重型模板页 | 46 | 4.0 s | 路由包体(30 份复制) |
/app/chat |
App shell | 28 | 20.6 s | 下载总量 · 10.7 MB |
/app/trade/perps/BTC |
交易页(perps) | 26 | 28.3 s | TradingView 加载、CLS |
/app/strategy-studio |
策略工作室 | 29 | 29.4 s | 主线程 46s · 下载 11 MB |
/app/workflow |
workflow 画布 | 25 | 44.9 s | 最差页,重定向 7.8 s |
详见 performance-analysis.html §🚨 Lighthouse 实测。关键回校:
/zh/,单次重定向浪费 1.3–4.3s(/about 最严重,4.3s)。这是最廉价的改进,必须作为 P0 第一件事。api.minara.ai/quick-search/all-assets 单次返回 2.5 MB JSON(/app/chat 冷载)。minara.ai/zh/icon0.svg 文件体积 1.2 MB — 几乎肯定是 bug(把某个 logo/插画错用作 favicon)。Chrome MCP 实测补充(同一报告 §⚡):
/home 冷加载调了 api.minara.ai/users/pnls/all × 6、cross-chain/activities × 8、arbitrum/batch × 6。facebook.com/tr + privacy_sandbox/pixel/register 各 3 次 = 6 次无谓请求。fonts.googleapis.com/css2 一次加载请求 7 次:印证 7 套字体。_next/static/chunks/1476-*.js)。/cdn-cgi/rum):可直接从 CF 后台拉真实用户 P75 CWV。P0 据此增补了 P0.0(/about 双跳消减,保留自动语言检测) 与 P0.9(favicon / quick-search / YouTube embed 单点爆炸修复) 两节。
self-review 发现初版若干问题,本轮修订:
localeDetection 属于功能回退(首次访问的非英语用户看英文首页)。产品侧确认自动语言检测是必须保留的 feature。新方案只做 /about 双跳消减,不动 locale 检测;收益范围从"-1.3 ~ -7.8s" 缩到 "-0.3 ~ -4s"(以 /about 的 4.3s redirect saving 为上界)。.gif 引用分类,避免 <img src=.gif> 与 <video> 混用断点。scripts/perf-report/deploy.sh snapshot <label> 产出 JSON + HTML 作为对比证据,不再截图。背景:Lighthouse 对所有 5 页报告 "Avoid multiple page redirects",省时 1.3–4.3s。 实际观察到的跳转链:
/about → /zh/about → /zh/why-minara/about(3 hops / 2 redirects,/about 的 4.3s 全部来源于此)/home → /zh/home(1 redirect · 来自 locale 检测)/app/workflow → /zh/app/workflow(1 redirect · locale 检测 · 实测 7.8s savings,但主要来自跳转落地页主线程堵塞而非跳转本身)本章只处理可不回退功能的部分:/about 的第二跳(/why-minara/about 重定向)。
Locale 检测(localeDetection: true)保留不动 —— 首次访问非英语用户的自动跳转是产品必需 feature。
当前 /about 的设计问题(src/app/[locale]/about/page.tsx):
// 实际服务页在 /why-minara/about
// 但 /why-minara/about/metadata.ts 声明 canonical = '/about'
// 所以 /about 用 redirect() 跳 /why-minara/about —— canonical URL ≠ 实际服务 URL
这是个历史 SEO 反模式:canonical 不可直达。
两个二选一方案(需产品/SEO 决定):
src/router/about/ 组件渲染搬到 src/app/[locale]/about/page.tsx;src/app/[locale]/why-minara/about/page.tsx 改成 redirect('/about') 做 301 永久;metadata 保持 canonical = '/about' 不变。外链系统不受影响。/why-minara/about;保留 /about 的 redirect(或在 next.config.mjs 的 redirects() 声明为 permanent 301,由 CDN 缓存,对非首次访问无视);更新 header 链接保持已有写法。历史外链仍经 1 跳。改点(方案 A 为例):
src/app/[locale]/about/page.tsx 从 redirect() 改为直接渲染 <About />;保留 query string 透传src/app/[locale]/why-minara/about/page.tsx 改为 redirect('/about');关键:使用 redirect() with RedirectType.permanent(301)让浏览器缓存src/components/seo/metadata.ts 生成的 canonical = /about/why-minara/about → /about(grep src/router/homepage/use-header-link.ts 等 9 处)/why-minara/about 条目,改成 /about验收:
curl -sI http://localhost:3000/about -H 'Accept-Language: en' → 直接 200(不再 307)curl -sI http://localhost:3000/about -H 'Accept-Language: zh' → 只有 1 跳到 /zh/about(不再有第 2 跳)/about:redirect savings 从 4,265ms 降到 ≤ 1,800ms/why-minara/about 的 SE 抓取下一次爬取返回 301,页面权重无损失(观察 Search Console 2 周)风险 / 回滚:
| 风险 | 预防 | 回滚 |
|---|---|---|
| Header 内链漏改某处导致 404 | grep 全部 /why-minara/about 引用双重验证 |
revert 单 PR;/why-minara/about 页面仍存在即可兜底 |
| Google 短期排名波动(URL 结构变化惯例) | 301 正确返回,hreflang 正确 | 无需回滚,2–4 周自然恢复 |
/about 下还有子路由期望 /why-minara/about/xxx |
搜 /why-minara/about/ 前缀确认 |
只改 about 本身,子路由维持原路径 |
预期收益:
/about LCP -1.5 ~ -4.3s(取决于 301 是否进 CDN 缓存)目标:立刻回收最大流量、解除最明显的主线程阻塞、建立指标闭环;不改变任何架构。 全阶段应可在 5–7 个工作日内由 1 名工程师完成。
next.config.mjs 接入 @next/bundle-analyzer:ANALYZE=true pnpm build 导出 .next/analyze/*.html,归档到 docs/perf-baseline/2026-04-YY/next build 记录:初始 JS (gzip)、每路由页面 JS (gzip)、首屏 HTML 大小web-vitals 上报到 GA/Sentry:首期只看 LCP/INP/CLS 中位数docs/perf-baseline/2026-04-YY/ 有完整数字;后续每任务完成后重跑并对比分两步,不能合并(原方案"10 分钟"低估了历史欠债):
P0.2a · Spike(1 小时):
eslint.ignoreDuringBuilds + typescript.ignoreBuildErrors,跑一次 next builddocs/perf-baseline/ci-errors-$(date +%F).logP0.2b · 修复并开启 (0.5–3 天):
next.config.mjs 正式删除两行 ignore*next build + next lint + tsc --noEmit风险:如果错误集中在第三方 dep 的 type 声明(非业务代码),可能需要 // @ts-expect-error 标注 + 单独跟踪。
改点:src/app/[locale]/layout.tsx
<Script> 加 strategy="afterInteractive"(显式)<Script> 改 strategy="lazyOnload";三个 addPixelId 跟 Growth 确认用途,无法砍则保留 —— 不能静默删除,避免转化事件丢失markdown-it-fix 移除 beforeInteractive(需先跑步骤 a 验证):grep -rn "isSpace" node_modules/markdown-it/lib 看哪些 fn 调用;在 dev 模式下确认 SSR / CSR 两条路径都能在 polyfill 缺失时正确抛错src/instrumentation-client.ts(Next 15 原生支持)或 markdown-it import 的 wrapper 顶部next/script + strategy="lazyOnload")canvas-datagrid npm 依赖,通过 next/dynamic + ssr:false 导入;同时避免未固定版本的供应链风险<Script> 补全 strategylazyOnload 后,早期 pageview 事件可能在用户秒关页面时丢失。观察 FB Events Manager 48h,若转化数据下降 ≥ 10%,回到 afterInteractive。TradingView charting_library.js 目前在 layout.tsx L149 全局 <Script defer>,实测 /app/workflow、/app/chat(都不需要图表)也会加载它。下沉到真正使用的路由 比想象复杂。
改点:
src/components/tv-loader/index.tsx 组件:useEffect 里 append <script> 到 document.headuseTVReady() hook 供下游 widget 组件等待 script readysrc/router/session/trade/layout.tsx(或 perps/spot 各自 layout)挂 <TVLoader />src/router/session/strategy/ 挂<Script src="/charting-library/..." />useTVReady() 返回 true 再实例化/home、/about、/app/chat、/app/workflow 的 Network 面板不再出现 charting_library.js/app/trade/perps/BTC 正常渲染 K 线window.TradingView 做了 eager 引用,<TVLoader> 需要在 strategy 组件挂载前就已注入 → 用 <TVLoader /> 放 layout 顶层即可<Script> 回到 layout;白名单路由方案保留作 fallback改点:src/app/[locale]/layout.tsx L152–166 + src/app/[locale]/globals.css
grep -rn "font-family.*Jost\|font-Jost\|DM Serif\|Pixelify\|Righteous" src 定位每种字体的实际使用组件zh / zh-TW 时注入(在 layout 内判断 locale 后条件渲染 <link>;其他语种不拉)next/font/google(自动 preload、避免 CLS;与 Roboto 同)<link href="fonts.googleapis.com"> 外部 <link> 清零(全部 next/font)docs/perf-baseline/2026-MM-DD/font-review.md)Step 0 · 先枚举调用点(关键!不同调用点迁移动作不同):
# 所有 GIF 引用位置 + 引用方式
grep -rn "cooking/[0-9]\.gif\|minara\.gif" src --include="*.tsx" --include="*.ts" --include="*.md"
分类:
<img src="xxx.gif"> 或 <Image src="xxx.gif"> → 换 <video autoPlay muted loop playsInline> 并 poster="xxx.webp" 保留首帧WrappedImage 包装(内部 <img>) → 需先扩展 WrappedImage 支持 type="video" 或提供 WrappedVideo → markdown 无法渲染 <video>,需在 MD → HTML 时加规则把 .gif 换成 <video> 或保留 GIF(不处理这类,单独评估)<video> 替代不自然,保持 GIF 或换 WebP 静态图og:image → 只能 PNG/JPG/WebP(YouTube/X 不支持 video),保持 PNG 但压缩Step 1 · GIF → 视频(优先级高):
public/images/guide/minara.gif 27 MB →ffmpeg -i minara.gif -c:v libx264 -movflags +faststart -pix_fmt yuv420p \
-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" minara.mp4
ffmpeg -i minara.gif -c:v libvpx-vp9 -crf 30 -b:v 0 minara.webm
# 生成首帧 poster
ffmpeg -i minara.gif -vframes 1 minara-poster.webp
cooking/1-4.gif(每个 1–1.5 MB)同样三件套<img src=".gif"> 调用点改为:<video autoPlay muted loop playsInline poster="minara-poster.webp"><source src="minara.webm" type="video/webm"><source src="minara.mp4" type="video/mp4"></video>Step 2 · 巨型 PNG → WebP + 降分辨率:
share/dark/background.png 14 MB、share/light/background.png 7.1 MB:先在浏览器里测真实展示尺寸,sips -Z <maxdim> 降分辨率到 2× DPR 需要的像素,再 cwebp -q 85 转 WebPcopilot/hero-bg.png 13 MB → 同上campaign/korea-trading-competition/hero.png 4.9 MB → WebP<Image> / <img> src 引用Step 3 · OG 图压缩:
og/image-0/1/2.png 3 × 3MB → JPEG 85q 或 WebP,目标每张 < 500 KB(OG 规范 1200×630)og/*.png 保持文件名不变,只替换内容(防止社媒缓存失效时 URL 变动)Step 4 · 加 CI 守护:
scripts/check-public-size.sh:单文件 > 500 KB 在 CI fail(白名单:已有的合法大 asset)package.json scripts 加 "lint:public-size": "scripts/check-public-size.sh"验收:
du -sh public/ < 95 MBdocs/assets-archive/ 以防需要重压)风险:
<video> 在 iOS Safari 偶现黑屏(autoplay policy),muted + playsInline 必须同时设;在 iPhone 物理设备上实测一次每个依赖的移除必须 grep 验证 + build 验证,不能按 audit 断言直删。
pnpm why prismjs 看 transitivegrep -rn "from 'prismjs'\|require.*prismjs" src 看直接引用pnpm remove prismjs;跑 next build + 手动打开包含代码块的页面import 'highlight.js/styles/github.css' 在 layout.tsx L22 存在 —— 先决定 P1.3 要不要迁 Shiki,如果迁则此 CSS 也去;如果不迁,highlight.js 不删grep -rn "from 'jotai\|from 'jotai-immer'" src | wc -l 统计canvas-datagrid、dom-to-image、@zumer/snapdom、cropperjs、@xyflow/react:next/dynamicpnpm install 后 node_modules 缩小next build 初始 JS chunk 下降 ≥ 200 KB改点:src/store/discover/index.ts + src/trade/shared/trade.service.ts + src/services/use-chat-list.ts
useRequest 补 cacheKey(如 discover:fear-greed:{locale})usePolling(fn, interval) hook,内部监听 document.visibilityState === 'hidden' 暂停;返回前台 resumeclearInterval 在 unmount / 登出 / visibilitychange=hidden 都要触发(目前部分缺失)范围说明:完整的 route-group 拆分(营销路由单独 layout 不挂 Auth/Wallet Provider)已挪到 P1.1,避免同一件事 P0/P1 重复列。P0.8 只做能在不改架构的前提下快速止血的 1 件事:让 Store 的自动请求在无登录态时不发生。
背景:未登录的 /home 上 pnls/all 被调 6 次、cross-chain/activities 8 次,原因是 Store 里 useEffect 无条件 fire。
改点:
grep -rn "useEffect" src/store/custodial-wallet src/store/user src/services/use-chat-list | head -20
grep -rn "useRequest\|useRequest(" src/store src/services --include="*.ts*"
useEffect(() => {
if (!hasCredential) return;
refresh();
}, [hasCredential]);
或对 useRequest 使用 ready: hasCredential(ahooks 原生支持)验收:
/home,Network 面板中:pnls/all / cross-chain/activities / arbitrum/batch 请求数 = 0/use-cases、/about 同上/app/chat:这些接口正常发送,数据正常渲染(对比基线,功能 0 回退)风险 / 回滚:
每个修复点前都有一步 "先确认"——不按假设直接动手。
cat src/app/icon0.svg | head -5 && ls -la src/app/icon*.{svg,png} 读文件头 + 看体积icon0.svg 的 <svg> 标签里 base64 内嵌了某张 logo 插画 → 导出为 PNG/WebP 作 icon,SVG 重绘为简单轮廓 ≤ 5 KB/quick-search/all-assets 2.5 MB JSON 分三步:curl -H 'Accept-Encoding: gzip' ... -o - -w '%{size_download}\n%{content_type}\n' 看是否已压缩。如果没开 gzip/br,这是后端一行 config 改动/top-assets 返回 50–100 高频交易对(≤ 200 KB),用户开始输入搜索词时 lazy fetch /all-assetssrc/features/chat-base/input/lexical/use-all-assets.ts 的 fetch 逻辑/about YouTube 嵌入换 lite-youtube-embed:grep -rn "youtube\|youtu\.be" src/router/about src/app 找到实际嵌入点lite-youtube-embed (~3 KB) 或手写 <iframe> + click-to-load placeholder验收:
/quick-search/all-assets 首响应 < 200 KB(含压缩)或首屏只拉 /top-assets/about 首屏 Network 面板不再有 youtube.com/.../player_embed 加载/about 视频占位图,YouTube 正常播放风险 / 回滚:
VideoObject schema 补偿Lighthouse 显示 CoinGecko widget(213 KB、28–30 ms 主线程)在所有 5 个页面加载;同时 Unused CSS 约 30 KB。
<Script> 从 src/app/[locale]/layout.tsx L148 挪到真正使用它的组件(如 src/components/token-pop-up/ 或 src/router/session/token/),改 next/dynamic 或组件挂载时 next/script strategy="lazyOnload" 注入globals.css 或 Tailwind 未扫描到的文件中遗留):tailwindcss purge scope 正确?highlight.js/styles/github.css 全局引入是否有必要?验收:/home、/about、/use-cases、/how-to-guides/[slug] 均不再加载 gecko-coin-price-chart-widget.js。
目标:真正把架构从"全部 client SSR on-demand"拉回"静态优先 + 按需 client";这个阶段改动大但收益最持久。
范围:/、/home、/about、/use-case、/faq、/pricing(评估)
'use client',放 server componentcapability-animations.tsx 等)page.tsx 导出 export const dynamic = 'force-static' 或 revalidate = 3600<Suspense fallback={<Skeleton />}> 包裹任何含数据获取的 client islandlayout.tsx Provider 下沉:把 UserAuthStoreProvider / CustodialWalletStoreProvider / ShareChatImageStore 挪到 新建的 src/app/[locale]/(session)/layout.tsx 组分组,营销页不再承担认证态初始化成本next build 输出 ● (Static) 标记src/router/how-to-guides/<slug>/index.tsx 内容抽取为 JSON:src/content/how-to-guides/<slug>.json(结构:hero、relatedGrid、faq、…)src/router/how-to-guides/template.tsx(仅 1 份 ~200 行)src/app/[locale]/how-to-guides/[slug]/page.tsx:import { loadGuideContent, listGuideSlugs } from '@/content/how-to-guides';
export async function generateStaticParams() {
return listGuideSlugs().map(slug => ({ slug }));
}
export default async function Page({ params }) {
const data = await loadGuideContent(params.slug);
return <GuideTemplate data={data} />;
}
export const revalidate = 86400;
src/router/how-to-guides/<slug>/ 目录整体删除;保留 sitemap 生成脚本对内容目录的指向更新src/router/how-to-guides/ 代码量从 ~60K LOC 降到 < 500 LOCnext build 输出 ● (Static)next-sitemap 仍产出全部 30 条 URLreact-syntax-highlighter 或(推荐)迁到 Shiki (SSR 渲染出纯 HTML,客户端 0 JS)highlight.js 与 prismjs(含 layout.tsx 中 import 'highlight.js/styles/github.css')next/dynamic + ssr:falsepnpm why prismjs highlight.js 输出 "no such package";消息流 markdown 无视觉差异回归测试通过批量把以下依赖改成 next/dynamic({ ssr: false, loading }):
| 依赖 | 建议承载点 |
|---|---|
| Monaco editor | ✅ 已做 |
@xyflow/react(workflow 画布) |
仅 /app/workflow 动态导入 |
echarts + echarts-for-react |
仅 strategy studio 图表组件 |
canvas-datagrid |
spreadsheet section 内部 dynamic |
cropperjs + react-cropper |
头像上传 modal 打开时 dynamic |
dom-to-image / @zumer/snapdom |
share 截图功能触发时 dynamic |
canvas-confetti |
胜利动画触发时 dynamic |
@lexical/*(编辑器) |
聊天输入框 focus 后再加载(可选) |
| TradingView charting library | P0 已下沉到交易/策略路由;这里确认最终只在 trade/strategy bundle |
import * as X 全部改为命名 import(ast-grep 可批量)components/primitive/index.ts、features/chat-base/index.ts、utils/date|number/index.ts@/components/primitive/button、@/features/chat-base/core/use-chat 等no-restricted-imports 禁止从 barrel 导入(给一个 codemod 脚本帮忙替换)from 'lodash' → from 'lodash-es/capitalize' 之类(或在 optimizePackageImports 命中前提下保留,但统一风格)next build 首页/guide 页 JS chunk 再降 10–20%next.config.mjs 加 experimental.ppr = true(需 Next canary/15.4;15.3 stable 尚有限制——先 POC 评估)babel-plugin-react-compiler,在 babel 配置开启;先在 src/features/chat-base/ 小范围试点reactStrictMode 设回 true(默认 off 是反模式;会暴露一批并发模式相关 bug,值得在这一阶段集中修复)src/router/session/discover/news-page/index.tsx eventsList 的 .map() 改为 virtua 或 react-window(项目已有)motion.* 入场动画仅对视口内的 item 触发(IntersectionObserver + useReducedMotion)src/store/trade-store/index.ts:41 个 selector 保留,但消费端禁止 useXxx() 整取——通过 ESLint 规则或人工 code review 确保只拿需要字段src/store/chat/index.tsx L286 的 s => s 改为导出若干窄 selectornext.config.mjs images.remotePatterns 改白名单(仅允许 space.minara.ai、CoinGecko CDN、Twitter/Google OAuth、Solana/Ethereum 等必要域名)minara-logo-with-text-*.svg 用 SVGO 精简 + 删除重复副本;blockchain.svg 959 KB 若为装饰,降级 PNG/WebP;chain/base.svg 同理<img> → next/image;如果 WrappedImage 组件内部是 <img>,也包一层 next/imagegetTranslations → 一次create-markdown-render.tsx 热路径的 console.warnuseMemo 为一条(组合函数)style={{}} 提取为常量size-limit 或 bundlesize:首页 JS 超过阈值直接 fail(建议 200 KB gzip)@next/bundle-analyzer 结果自动贴到 PR 评论docs/perf-reports/YYYY-MM/每周末必须跑
./scripts/perf-report/deploy.sh snapshot pX-end并归档对比,数字说话。
| 周 | 阶段 | 主任务 | 预期(按最差页 /app/workflow 估算) |
|---|---|---|---|
| W1 day 1–2 | P0.1 + P0.2a | 基线工具入 CI + spike CI type errors 数 | 基线归档;P0.2b 工作量确定 |
| W1 day 3 | P0.0 + P0.9(需先与产品确认 /about canonical + favicon 修法) | /about 双跳消减 + 3 个单点 bug | LCP: 44.9 → 42 s; /about: 21 → 17 s |
| W1 day 4–5 | P0.3 + P0.3b | 脚本 strategy + TradingView 下沉 | TBT -400~800 ms |
| W1 末(day 5–7) | P0.4 + P0.5 + P0.6 + P0.7 + P0.8 | 字体 / 大图 / 依赖瘦身 / 轮询 / Store guard | score ≥ 40; LCP ≤ 30 s(最差页) |
| W2–W3 | P1.1 + P1.2 | 营销页 RSC/SSG 化(含 Auth/Wallet Provider 下沉到 (session) 组)+ how-to-guides 模板重构 | 营销页 LCP < 6 s; score ≥ 65 |
| W4 | P1.3 + P1.4 | 高亮合一 + 重型依赖全 dynamic | Unused JS < 600 KB; TBT < 600 ms |
| W5 | P1.5 | barrel / namespace import 重构 | 首屏 JS -40% vs W1 末 |
| W6 | P1.6 + P1.7 + P1.8 | PPR/Compiler POC + 虚拟化 + store 审计 | INP < 200 ms; score ≥ 80 |
| W7 | P2 | 资源链路收紧 + 观测守护 | score ≥ 85 最差页;最终验收跑 snapshot final |
和原版差异:
W1 day 1 不再塞 3 件事。先建基线 + spike CI 错误数,有了数才能定节奏。
| 风险 | 预防 | 回滚策略 |
|---|---|---|
移除 ignoreBuildErrors 后 CI 挂一大片 |
P0.2a spike 先看错误数,据此决定节奏 | 不移除 flag,单独 epic 分批修(见 P0.2) |
RSC 化漏加 'use client' 引发 "useState 不能在 server" |
单 PR 控制单页面;先预览环境验证 | revert 单页面;其他页面不受影响 |
| P0.0 /about 迁移引发 SEO 排名短期波动 | 301 正确返回;hreflang 正确;发布前通知增长 | 无需回滚,2–4 周自然恢复;Search Console 持续观察 |
| SSG how-to-guides 导致 sitemap 生成链断 | 迁移时双跑 next-sitemap + 原脚本 |
保留旧 router/how-to-guides 目录直至 sitemap 验证通过再删 |
| TradingView 下沉到 trade 路由后某些非 trade 页仍在用 | 代码检索 TradingView/charting-library/trading_view 引用 |
Hotfix:恢复 layout 注入,但仅限白名单路由 |
| Facebook Pixel lazyOnload 后早期 pageview 事件丢失 | 发布前与 Growth 对齐;观察 Events Manager 48h | 回到 afterInteractive;pixel 事件是法律 / 广告投放依赖,不能静默降级 |
| 字体删除 / fallback 引发品牌视觉回退 | 设计师签字 + 每字体截图对比 + 单字体单 PR | revert 单字体 PR |
| WCAG 对比度 / 最小字号违规 | Lighthouse Accessibility audit | revert + 调 fallback 字体族 |
| PageSpeed 分数跑分方差 5–10 分 | 每次 snapshot 跑 3 次取中位 |
若单次 drop < 10%,视为噪声;持续性回退才 revert |
| 缺 staging gate:改动直上生产 | 所有 P0/P1 PR 必须先合 release 分支在预发 minara-perf 对应预发域名(若有)验证 |
若无预发,用本地 next start + Lighthouse CLI 本地验收 |
| PPR 在 15.3 稳定性不够 | 先在 canary 15.4 POC | 移除 experimental flag,页面仍以 SSG 运作 |
| Zustand selector 收窄引入 stale closure | 以路由为单位改动 + 人工 code review | 回到整 store 读取(损失性能但功能正确) |
ahooks 自动 cacheKey 破坏"每次拿新数据"的组件 |
不统一加 cacheKey;显式配置或走 staleTime: 0 |
单 PR 恢复无 cacheKey |
每个任务 PR 必须:
@next/bundle-analyzer 输出附 PR./scripts/perf-report/deploy.sh snapshot <pr-label> 产出 JSON;与 baseline 对比的关键指标(LCP / TBT / score)用 jq 提取贴到 PR 描述test/<branch>/test-cases.md 的验收清单next build + next lint + tsc --noEmitrelease 分支预发验证(如项目启用预发域名)或本地 next start + Lighthouse CLI 验收PR 排序约束:
release 基础上 cherry-pick,等 P0.2b merge 后再 rebase阶段完成:
./scripts/perf-report/deploy.sh snapshot p0-end 归档,最差页 LCP 改进 ≥ 30%;报告展示在 https://minara-perf.yijian.nftgo.cloud/post-fix/p0-end/snapshot p1-end;营销页 + guide 全 Static;Lighthouse 移动端 Performance 分中位 ≥ 85snapshot final;CI 加入 bundle 守护;月度回归报告第 1 期归档| 问题 | 文件 |
|---|---|
| 脚本/字体堆叠 | src/app/[locale]/layout.tsx |
| 构建配置 | next.config.mjs |
| 模板复制 | src/router/how-to-guides/<slug>/index.tsx |
| 轮询源头 | src/store/discover/index.ts, src/trade/shared/trade.service.ts, src/services/use-chat-list.ts |
| 状态管理 | src/store/trade-store/index.ts, src/store/chat/index.tsx |
| 流式渲染(保持) | src/features/chat-base/core/xhr-stream-utils.ts, src/features/chat-base/message-view/section/create-streaming-markdown.tsx |
| 巨型资源 | public/images/guide/minara.gif, public/images/share/*, public/images/copilot/hero-bg.png, public/images/og/* |
| Barrel 文件 | src/components/primitive/index.ts, src/features/chat-base/index.ts, src/utils/date/index.ts, src/utils/number/index.ts |