核心要点
- Next.js 提供多种渲染模式(SSR、SSG、ISR、App Router),每种都可能为同一页面产生不同的视觉结果
- React hydration 是传统功能测试无法发现的视觉回归的主要来源
- 基于 Storybook 的方法无法测试 Next.js 页面在生产条件下的实际渲染
- 一个框架无关的视觉测试工具,在浏览器中捕获最终结果,是对抗 Next.js 视觉回归的唯一可靠保障
视觉测试,根据 ISTQB(International Software Testing Qualifications Board,国际软件测试资格认证委员会)的定义,是指*"通过将参考截图与应用程序的当前状态进行比较,验证软件的用户界面是否按照预期的视觉规范显示"*(ISTQB Glossary, Visual Testing)。
当你将这一原则应用到 Next.js 应用时,情况会变得相当复杂。Next.js 不是一个简单的 React 应用。它是一个提供至少四种不同渲染模式的框架——有时甚至出现在同一个页面上。而每种渲染模式都可能产生微妙不同的视觉结果,其原因是你单元测试和集成测试永远无法检测到的。
根据 State of JS 2024 的数据,Next.js 被 58% 的 React 开发者在生产环境中使用。Stack Overflow Developer Survey 2024 将其列为仅次于 React 本身的最常用 Web 框架。这不是小众市场:它是专业 React 应用的事实标准。然而,大多数使用 Next.js 的团队没有制定适应其特性的视觉测试策略。
本文将解决这个问题。
为什么 Next.js 让视觉测试更加重要,而不是更加不重要
在开发团队中经常听到这样的论点:"Next.js 为我们处理渲染,我们不需要视觉验证每个页面。"这与现实恰恰相反。
Next.js 使视觉测试变得更加关键,恰恰是因为它成倍增加了渲染路径。当你使用 Create React App 构建一个简单的 React SPA 时,渲染是可预测的:一切都在客户端的浏览器中完成。视觉结果是确定性的。使用 Next.js 后,同一页面可能在第一次请求时在服务端渲染,然后在客户端进行 hydration,然后在客户端导航时部分重新渲染。这些步骤中的每一步都可能引入视觉差异。
Hydration 问题
Hydration 是 React 获取服务端生成的 HTML 并在客户端"重新激活"它的过程——通过附加事件监听器和同步虚拟 DOM。理论上,hydration 前后的视觉结果应该完全相同。实际上,很少能达到 100% 的完全一致。
原因是多方面的。服务端计算的样式不考虑实际的浏览器窗口大小。依赖于 window 或 document 的组件在服务端显示 fallback 内容,而在客户端显示实际内容。Web 字体在服务端渲染时并不总是可用,导致 hydration 后出现无样式文本闪烁(FOUT)。CSS 动画在 hydration 时才开始播放,而不是在页面加载时。
结果:服务器发送的内容和用户在 hydration 后看到的内容之间存在视觉差距。这种差距通常很微妙——几个像素的偏移、文本重排、图片跳动。但它是真实存在的,并且会降低用户体验。
没有单元测试可以检测到这个问题。检查 DOM 中元素是否存在的集成测试也看不到。只有视觉测试——在完全 hydration 后在真实浏览器中捕获渲染页面的图像——才能确认最终结果符合你的预期。
四种渲染模式及其视觉陷阱
Next.js 提供四种主要的渲染策略,每种从视觉角度来看都有其特定的陷阱。
静态渲染(SSG)在构建时生成 HTML。它是最可预测的,但要注意:如果你的数据在两次构建之间发生变化,你的静态页面会显示过时数据。视觉测试可以在重建前后比较页面,揭示意外的内容变化——更新的产品图片、修改的价格、翻译不同的文本。
服务端渲染(SSR)在每次请求时生成 HTML。这里,内容本质上是动态的。同一 URL 可能根据时间、已登录用户、数据库数据产生不同的视觉渲染。视觉测试必须处理这种变化性,这需要数据模拟或数据稳定策略。
增量静态再生(ISR)是一种混合模式:页面是静态的但周期性地在后台重新生成。视觉陷阱在于新旧版本之间的过渡。在一个短暂的窗口期内,有些用户看到旧页面,有些用户看到新页面。如果你的布局发生了变化,这种过渡可能会在视觉上不一致。
App Router 在 Next.js 13 中引入,添加了 Server Components、streaming 和嵌套 layouts。Server Components 从不在客户端执行。Client Components 经历 hydration。同一个页面混合了两者。结果是渐进式渲染,内容分块出现——每个块都可能引入视觉偏移。
测试 Next.js 应用的视觉测试方法
现在你理解了为什么视觉测试对 Next.js 至关重要,让我们看看可供你使用的具体方案。
Playwright:暴力方法
Playwright 由 Microsoft 开发,是 2026 年最全面的浏览器自动化框架。它支持 Chromium、Firefox 和 WebKit,提供强大的导航 API,并包含原生截图比较功能。
对于 Next.js 视觉测试,Playwright 有一个主要优势:它测试真实的页面,在真实的浏览器中,进行完整渲染(SSR + hydration + CSS + 字体)。你捕获的是用户实际看到的内容。
但 Playwright 在视觉测试方面也有显著的局限性。
首先,它是一个开发者工具。它需要编写和维护测试脚本。对于每个页面、每个视口、每种状态,你都需要编写一个导航到页面、等待完整加载并捕获截图的场景。对于一个拥有 100 个页面、三个视口和多种状态的应用,维护负担会急剧增加。
其次,Playwright 的截图比较是基础级别的。它逐像素进行比较,配有可配置的容差阈值。它不理解内容。文本变化和图片移动两个像素被同等对待。误报频繁出现,尤其是在不同操作系统和抗锯齿处理导致的字体渲染差异上。
第三,基线基础设施是你的责任。你将参考截图存储在 Git 仓库中(这会使其显著膨胀),或者存储在你必须自己管理的外部服务中。
Playwright 对于拥有专职测试开发者且想要完全控制权的团队来说是一个出色的工具。但对于大多数团队来说,它的维护成本对于系统性视觉测试来说太高了。
Chromatic 通过 Storybook:隔离组件的幻觉
Chromatic 由 Storybook 维护者创建,是一个云端视觉测试服务,用于捕获 Storybook 组件的截图并在构建之间进行比较。
这种方法具有明显的吸引力:如果你已经有了 Storybook,Chromatic 可以直接插入。设置快捷。审查界面友好。
但对于 Next.js 应用,Chromatic 有一个根本性的问题:它不测试你的 Next.js 页面。它测试的是 Storybook 中隔离的组件。然而 Next.js 中最危险的视觉回归不是来自单个组件——它们来自组件之间的交互、整体页面布局、SSR 渲染、hydration 和 App Router 的嵌套 layouts。
一个在 Storybook 中完美显示的按钮,放在与其他元素一起的 flex 容器中时可能会破坏页面布局。一个通过了所有 Chromatic 测试的导航组件,通过 SSR 加载 web 字体时可能导致 0.3 的累积布局偏移(CLS)。Chromatic 永远看不到这些问题,因为它永远看不到完整的页面。
此外,Chromatic 要求每个组件都有带有真实数据的 Storybook story。如果你的 stories 不能反映应用的真实数据——坦诚地说,它们几乎从来不能完美反映——Chromatic 的视觉测试验证的是你 UI 的一个虚构状态。
Chromatic 是设计系统的优秀工具。但要在真实条件下对 Next.js 应用进行视觉测试,它缺少了关键的一环:页面上下文。
Delta-QA:真实页面的视觉测试,无需代码
Delta-QA 采用了一种截然不同的方法。它不是测试隔离的组件或编写自动化脚本,而是捕获你的真实页面的截图——你的用户看到的那些——并在应用的两个版本之间进行比较。
对于 Next.js 的好处是立竿见影的。Delta-QA 不需要知道你的应用使用的是 Next.js、React、App Router 还是 Pages Router。它捕获浏览器中的最终渲染结果——在 SSR 之后、hydration 之后、字体加载之后、客户端 JavaScript 执行之后。它比较的正是你的用户看到的。
这种方法解决了我们识别出的三个问题。Hydration 差异会被捕获,因为截图是在完全 hydration 之后拍摄的。组件和布局的交互是可见的,因为捕获的是整个页面,而不是隔离的组件。维护成本最低,因为没有测试脚本需要编写,也没有 Storybook stories 需要维护。
Delta-QA 以 no-code 方式工作:你配置要监控的 URL、要捕获的视口,工具完成剩余的工作。对于一个拥有 50 个页面和三个视口的 Next.js 应用,每次部署你可以获得 150 次自动视觉捕获,无需编写一行测试代码。
Next.js 特有挑战及应对方法
即使有了正确的工具,Next.js 上的视觉测试也需要管理某些特性。
动态内容
如果你的 Next.js 页面显示实时访客计数器、时间戳或每次请求都会变化的数据,那么每张截图都会与前一张不同——而没有任何视觉回归。这就是动态内容问题,SSR 和 ISR 会加剧这个问题。
解决方案是在捕获前稳定内容。Delta-QA 允许你在页面上定义排除区域:你指出包含可变内容的区域,工具在比较时忽略它们。这比模拟所有数据源更加实用,而且无论你的渲染架构多么复杂都能正常工作。
字体和 FOUT
无样式文本闪烁(Flash of Unstyled Text)是 Next.js 的经典问题。页面使用系统字体在服务端渲染,然后 web 字体加载后文本重新排版。如果你的截图在闪烁期间捕获,它不会反映页面的最终状态。
技术解决方案是在捕获前等待字体完全加载。document.fonts.ready API 可以确保这一点。Delta-QA 原生集成了这个等待机制,以保证截图反映页面的稳定状态。
动画和过渡
动画组件——轮播、滚动淡入、骨架加载器——给视觉捕获引入了不确定性。同一页面的两次捕获可能仅仅因为动画处于不同阶段而不同。
最佳实践是在视觉测试环境中禁用 CSS 和 JavaScript 动画。Delta-QA 原生提供了这个选项。对于 CSS 动画,它注入一个强制设置 animation-duration: 0s 和 transition-duration: 0s 的样式表。对于 JavaScript 动画,你可以定义一个参数让你的应用检测到后绕过动画。
App Router 嵌套 Layouts
Next.js App Router 引入了嵌套 layouts:每个路由段可以有自己的 layout 来包裹子段。这对应用架构来说很强大,但对于微妙的视觉回归来说是肥沃的土壤。
父 layout 的修改会影响所有子页面。根 layout 中一个 padding 的修改可能会移动数十个页面的内容。没有系统性的视觉测试,你只会在生产环境中发现这种回归——当一个用户报告"什么东西移动了"的时候。
视觉测试自动覆盖这个面。如果你捕获了 50 个页面且父 layout 发生了变化,所有 50 个捕获都会显示差异。你在部署前立即看到影响的范围。
将视觉测试集成到 Next.js CI/CD 流水线
视觉测试只有自动化到部署流水线中才有价值。以下是针对 Next.js 构建此集成的方法。
最优工作流
推荐的流程遵循以下步骤。你将代码推送到分支。你的 CI 构建 Next.js 应用并将其部署到预览环境(Vercel、Netlify 或你自己的基础设施)。Delta-QA 捕获预览环境的截图并与生产基线进行比较。结果直接显示在你的 merge request 或 pull request 中。你的团队审查视觉差异并批准或拒绝变更。
这个工作流程与 Next.js 和 Vercel 特别契合,后者会为每个分支自动创建预览环境。Delta-QA 挂接到这个预览 URL 来捕获截图,这意味着你在接近生产条件的场景下测试你的应用。
预览环境与 SSR
一个 Next.js 特有的注意事项:你的预览环境中的 SSR 页面不一定能访问与生产环境相同的数据。如果你的首页通过 SSR 显示"10 个最受欢迎的产品",预览环境可能会显示不同的数据,产生合理的视觉差异。
解决方案是明确定义哪些页面是"确定性的"(各环境之间内容稳定)和哪些是"可变的"(内容依赖于数据)。确定性页面受益于像素精确的比较。可变页面使用排除区域或结构化比较,验证布局而不关心确切内容。
要跟踪的指标
你如何知道你的 Next.js 视觉测试策略是否有效?以下是需要监控的指标。
生产前视觉回归检测率衡量你的安全网的有效性。目标是接近 100%——没有任何视觉回归应该到达生产环境。
误报率衡量你的捕获的相关性。如果你检测到的视觉差异中超过 20% 是误报(动态内容、不确定性渲染),你的配置需要优化。
平均审查时间衡量对你开发速度的影响。如果你的团队每个 merge request 花费超过 15 分钟检查视觉差异,说明你捕获了太多噪音而信号太少。
覆盖页面数与总页面数之比衡量你的覆盖完整性。目标是 100% 的关键页面——即用户最常访问的那些。
常见问题
视觉测试能替代 Next.js 的单元测试和集成测试吗?
不能。视觉测试是其他测试形式的补充,不是替代。单元测试验证组件逻辑。集成测试验证组件协同工作。视觉测试验证最终结果——用户看到的——是否符合预期。三者都是完整覆盖所必需的。视觉测试对于 Next.js 尤其不可替代,因为 hydration 和布局问题对其他两种方法是不可见的。
如何在视觉测试中处理 Next.js Server Components?
Server Components 不会给视觉测试带来特定的困难——这恰恰是它的优势。由于视觉测试捕获浏览器中的最终结果,它不关心组件是在服务端还是客户端渲染的。这是与 Chromatic 等方法的关键区别,后者需要在特定环境中渲染每个组件。Delta-QA 像用户一样看到页面,无论底层渲染架构如何。
Next.js 应用应该测试多少个视口?
至少三个:移动端(375px)、平板(768px)和桌面端(1440px)。对于使用复杂响应式布局的 Next.js 应用,如果设计在平板和桌面之间有显著变化,可以添加第四个中间断点。重要的不是测试每种可能的屏幕宽度,而是覆盖布局发生结构性变化的断点。App Router 的嵌套 layouts 可能在每个断点表现不同,使响应式覆盖比经典 SPA 更加关键。
视觉测试会减慢 Next.js CI/CD 流水线吗?
使用 Delta-QA,捕获和比较与流水线的其余部分并行运行。对于一个 50 页面、三个视口的应用,整个捕获和比较过程预计需要两到五分钟。与 Next.js 应用的构建时间(中型项目通常五到十分钟)相比可以忽略不计,而且避免了部署后修复往返所节省的时间更多。如果你部署在 Vercel 上,一旦预览环境就绪,捕获就会自动开始,无需在流水线中增加额外步骤。
可以将视觉测试用于 Next.js 的 draft mode 功能吗?
是的,甚至推荐这样做。Next.js 的 draft mode 允许你预览来自 headless CMS 的未发布内容。视觉测试可以捕获 draft mode 下的页面,验证未发布内容在发布前不会破坏布局。这对于编辑和电商网站特别有用——这些网站的内容由非技术团队创建,他们对变更对渲染的影响缺乏可见性。
Delta-QA 能与 Next.js Middleware(重定向、rewrites、A/B testing)一起工作吗?
能。Next.js Middleware 在 Edge 服务器级别运行,在页面渲染之前。Delta-QA 捕获所有 Middleware 执行后的最终结果。如果 Middleware 重定向到另一个页面或修改了 headers,截图会反映该逻辑的结果。对于 A/B testing,你可以配置 Delta-QA 通过传递适当的 cookies 或 headers 来捕获两个变体,从而让你视觉验证实验的每个版本。
结论:Next.js 需要适应其复杂性的视觉测试
Next.js 是一个强大的框架,彻底改变了 React 开发。但这种强大以渲染复杂性为代价,大多数团队低估了这一点。SSR、ISR、App Router、Server Components、streaming、嵌套 layouts——每个功能都增加了一条可能产生意外视觉结果的渲染路径。
视觉测试对于 Next.js 应用不是奢侈品。它是必需品。而你选择的视觉测试工具必须能够捕获应用的真实结果——不是在人工环境中隔离的组件。
Delta-QA 正是为此而设计:在真实浏览器中捕获你的真实页面,在完整渲染后,当视觉发生变化时提醒你。无需代码,无需脚本维护,无需同步 Storybook stories。
如果你在使用 Next.js 开发但还没有视觉测试策略,你就是在盲飞。是时候纠正了。