为什么 NextJS 一定要与 Tailwind 深度绑定
过去,React 生态系统之中,CSS-in-JS,CSS Modules 等方案,几乎是占据了半壁江山,但是随着 NextJS 这几年的持续火爆与发展,这两个方案开始逐渐被抛弃
在如今的 NextJS 项目中,风向已经完全被改变,自从 NextJS 13 开始,随着 App Router 的成熟与逐渐被大家所接受,tailwindcss 成为了事实上的唯一宠儿,不管是官方团队还是开发者,都默认了这一事实
为什么会出现这种情况呢?
这并非 Vercel 团队的的一时兴起,也不仅仅是因为 tailwindcss 写起来很快。这是由于 RSC 架构变革所导向的必然结果。
1、什么是 RSC
RSC 是 React Server Components 的缩写,表示为仅在服务端运行的 React 组件. 这一理念其实并不常规,因为在过去,被前端开发者广为认知的是同构组件:既要在服务端运行,也要在客户端运行
而仅能在服务端运行,就给 NextJS 的发展提供了巨大的想象空间。例如,我们可以直接在函数式组件中,访问数据库并获得数据
// app/users/page.js (服务端组件) |
把运行逻辑留在服务端,把运行结果传递给客户端。这是 RSC 最核心的特性之一。得益于这样的特性,那么,我们就有机会,把项目中大量的内容,仅在服务端处理
到了客户端这里,就直接是静态处理的结果,而无需再次运行 。因此,我们就可以在理论上做到客户端 0 JS 逻辑
例如,在我们常见的博客系统中,代码块的渲染往往需要一个比较庞大的渲染器和主题配色,那么,我们可以把这些逻辑,通过服务端组件,在项目构建时处理好,只需要交给客户端一个渲染好的 HTML 即可。
而无需把代码渲染器、大量配色颜色主题等内容全部发送到客户端。
也正因为如此,RSC 在这几年的强势普及,彻底重构了 CSS 方案的评估标准。
Runtime CSS-in-JS 的崩塌:性能与架构的互斥
要理解 Next.js 为什么“抛弃”了 Styled Components 或 Emotion 这类传统的 CSS-in-JS 方案,我们必须深刻理解 RSC 的**序列化(Serialization)**机制。
在 RSC 架构中,服务端组件渲染的结果并非直接是 HTML 字符串,而是一种特殊的序列化数据结构(类似于 JSON),这种结构描述了组件树的样子,然后通过网络发送给客户端(浏览器)。客户端的 React 拿到这份数据后,将其还原为真实的 DOM。
在这个过程中,存在一个核心冲突:RSC 无法序列化函数。
传统的 CSS-in-JS 方案(如 Emotion)严重依赖运行时(Runtime)JavaScript。当你写下 styled.div 时,实际上是在客户端运行一段 JS 逻辑:
计算 Props 和 Theme 变量。
动态生成哈希类名。
将 <style> 标签注入到文档的 <head> 中。
这在纯客户端时代(CSR)没有问题。但在 RSC 时代,这意味着:任何使用 Runtime CSS-in-JS 的组件,都必须强制标记为 'use client'。
// ❌ 这是一个痛苦的妥协 |
这就导致了一个巨大的悖论:为了样式,我们被迫放弃了服务端组件带来的所有性能红利。
如果你执意在 Next.js 14+ 的项目中使用 Styled Components,你会发现原本应该是服务端直接输出 HTML 的页面,变成了大量的客户端 Hydration(水合)任务。你的 JS Bundle 体积不仅没有因为 RSC 变小,反而因为引入了庞大的样式运行时库而变大了。
这就是架构层面的“互斥”。
Tailwind CSS:原生契合 RSC 的“零运行时”
反观 tailwindcss,它的工作原理与 RSC 的理念可谓是天作之合。
Tailwind 本质上是一个编译器。它在**构建阶段(Build Time)**扫描你的代码,提取出所有用到的类名(如 flex, text-center, p-4),然后生成一个静态的 .css 文件。
当 RSC 在服务端运行时:
- React 组件执行,生成包含
class="p-4 text-center"的 HTML 结构。 - 服务端不需要执行任何样式计算逻辑。
- 服务端不需要注入任何样式库的 JS 代码。
- 生成的 HTML 携带轻量的 class 字符串。
这是真正的 Zero Runtime(零运行时)。
// ✅ 完美的服务端组件 |
在这个场景下,发送给客户端的不仅没有样式库的 JS,甚至连 CSS 也是高度优化的(只包含用到的类)。浏览器拿到 HTML 后,通过标准的 <link rel="stylesheet"> 加载 CSS,这是浏览器渲染引擎最擅长、最高效的原生行为,完全不占用 JS 线程。
Next.js 追求极致的首屏加载速度(FCP)和累积布局偏移(CLS)优化,而静态 CSS 文件 + 流式 HTML 是实现这一目标的最佳路径。
流式渲染(Streaming)与样式闪烁之战
Next.js App Router 的另一个杀手级特性是 Streaming(流式渲染)。
传统的 SSR 是“瀑布式”的:服务端必须等所有数据都准备好,生成完整的 HTML,然后一次性发给客户端。 而 Streaming 允许服务端由上至下,生成一部分 HTML 就发送一部分。比如导航栏先发过去显示,数据库还在查询的内容用 <Suspense> 包裹,等查完了再推送到浏览器。
这种机制下,CSS-in-JS 方案面临着巨大的 FOUC(Flash of Unstyled Content,无样式内容闪烁) 风险。
如果样式是依靠 JS 动态注入的,当浏览器的 HTML 流已经到达并渲染时,如果对应的 JS 还没加载或执行完毕,用户就会看到内容裸奔,紧接着样式突然生效,页面剧烈抖动。为了解决这个问题,CSS-in-JS 库需要极其复杂的“样式收集”逻辑(Style Registry),在服务端收集样式标签并尝试插入到流中。这不仅极其脆弱,而且难以维护,React 团队本身也并不推荐这种做法。
而 Tailwind CSS 生成的是一个全局的静态 CSS 文件。在 HTML 流的最开始(<head> 部分),浏览器就已经加载了这个 CSS 文件。无论后续 HTML 片段如何分块到达,只要它带有 class 名,样式就会立即、准确地生效。
在流式渲染的战场上,Tailwind 是正规军,而 CSS-in-JS 只能算是在补丁上打补丁的游击队。
原子化 CSS:组件化时代的最后一块拼图
抛开底层架构,从开发体验(DX)的角度来看,Next.js 极力推崇的组件化架构也与 Tailwind 的原子化理念高度共鸣。
在 CSS Modules 时代,我们经常面临“起名困难症”。styles.wrapper? styles.container? styles.contentInner? 并且,我们往往需要在 .js 文件和 .module.css 文件之间频繁切换。当一个组件被删除时,开发者往往不敢轻易删除对应的 CSS 代码,生怕影响到其他地方(即使是 Modules 也有漏网之鱼),导致项目充满了“死代码(Dead CSS)”。
Tailwind 将样式与结构共置(Co-location)。
<button className="px-4 py-2 bg-blue-500 rounded hover:bg-blue-600 transition"> |
这种写法在 Next.js 的 Server Components 模式下优势被无限放大:
- 复制粘贴即复用:RSC 鼓励我们将 UI 拆分为极细粒度的组件。你可以直接把这段代码复制到另一个项目中,它依然能完美工作,不依赖任何外部 CSS 文件。
- 死代码自动消除:当你删除这个
<button>组件时,所有的样式(class 字符串)随之消失。如果项目中再也没有用到bg-blue-500,Tailwind 的编译器会在下一次构建中自动从 CSS 文件中剔除这个规则。 - 设计系统的约束:Next.js 项目通常承载着大型应用。Tailwind 充当了设计系统的“真理之源(Source of Truth)”。开发者不再随意手写
margin: 13px,而是被约束在m-4(16px) 的网格系统中。这对于多人协作的 Next.js 项目至关重要. 当然,我还可以根据团队的需求,在 tailwind 默认的规则之上,挑选出符合自己团队的更严格的设计约束。
Next.js 与 Tailwind 的双向奔赴
如果你关注过 Vercel (Next.js 及其背后的公司) 的动作,你会发现这不仅仅是技术选择,更是一种战略结盟。
- create-next-app 的默认选项:现在初始化 Next.js 项目,Tailwind 是默认选中的,你甚至很难把它关掉。
- Turbopack 的优化:Next.js 用 Rust 重写构建工具(Turbopack)。Tailwind 团队也在开发基于 Rust 的 Oxide 引擎(即 Tailwind v4)。两者的目的完全一致:毫秒级的增量编译。并且,Vercel 正在确保 Next.js 的编译器能对 Tailwind 的类名解析做最深度的优化
- V0.dev:Vercel 推出的 AI 生成 UI 工具 V0,生成的代码完全基于 React + Tailwind + Shadcn UI。这表明在 Vercel 定义的未来开发范式中,Tailwind 就是 UI 的标准描述语言,就像 HTML 是结构的描述语言一样。
不仅仅是快,而是“正确”
回到最初的问题:为什么 Next.js 必须与 Tailwind 深度绑定?
因为在 Server-First(服务端优先) 的新时代,旧的规则被打破了。
CSS-in-JS 是客户端时代的产物,它试图用 JS 的灵活性来解决 CSS 的局限性。但在 RSC 时代,JS 的执行成本变得极其昂贵,而HTML 的传输和流式渲染变得极其重要。
Tailwind CSS 并不是一个简单的“样式库”,它代表了一种**“预编译、原子化、静态提取”**的构建思想。
- 它不需要服务端运行 JS,所以它适配 RSC。
- 它生成静态文件,所以它适配 Streaming。
- 它利用编译器优化,所以它适配 Next.js 的构建管线。
在 Next.js 的未来蓝图中,前端开发正在回归本质:发送最纯粹的 HTML 和 CSS 给浏览器,把复杂的逻辑留在服务端。 而 Tailwind,正是实现这一愿景最锋利的那把剑。
所以,这不仅仅是风向的改变,这是前端工程化架构演进的必然选择。在过去我们还要犹豫是否应该选择 tailwind,但是现在这几乎成为了一个必选项




