Vanilla Extract 的 CSS 不是运行时生成的,因其所有样式调用均在构建时被插件解析为静态 CSS 文件和类型声明,实现零运行时样式计算与完整 TypeScript 类型安全。

Vanilla Extract 在 Next.js 中无法真正“零开销”,但能实现编译时 CSS 提取 + 完整 TypeScript 类型安全,关键在于正确配置 css 和 recipes,并避开 createTheme 的运行时泄漏。
为什么 vanilla-extract 的 CSS 不是运行时生成的
Vanilla Extract 的核心机制是:所有 style、globalStyle、keyframes 调用都在构建时(即 next build 阶段)被 Babel 插件或 SWC 插件解析,直接输出静态 CSS 文件(如 Button.css),同时生成类型声明(Button.css.d.ts)。这意味着:
- 没有运行时样式计算,不注入
style标签,不依赖useEffect或useState管理类名 - 类名是哈希化的字符串字面量(如
"_123abc"),TypeScript 能精确推导,支持自动补全和重命名重构 - 如果在组件中用了
import { sprinkles } from './sprinkles.css'却没在next.config.js里配好@vanilla-extract/next-plugin,开发时会报错Cannot find module '*.css' or its corresponding type declarations
Next.js 13+ App Router 下必须启用插件
默认的 Next.js 构建流程不认识 .css.ts 或 .css.js 文件。不加插件会导致两个问题:CSS 不打包、TS 类型不识别。解决方式只有一种:
- 安装
@vanilla-extract/next-plugin(注意不是vanilla-extract本体) - 在
next.config.js中包裹withVanillaExtract,且必须放在最外层导出位置 - 插件会自动处理
css、createContract、recipe等导出,但不会处理createTheme—— 后者若用于服务端渲染(SSR),仍可能触发运行时对象创建,破坏零开销承诺
示例配置:
const { withVanillaExtract } = require('@vanilla-extract/next-plugin');
/** @type {import('next').NextConfig} */
const nextConfig = {
// 其他配置...
};
module.exports = withVanillaExtract(nextConfig);
sprinkles 和 recipes 的类型安全差异
sprinkles 是原子化工具,适合快速组合;recipes 更接近传统组件 API,两者都提供完整类型检查,但行为不同:
-
sprinkles接收一个对象,键必须严格匹配定义的条件(如color、padding),值必须是预设值之一("blue"、"sm"),拼错会立即报 TS 错误 -
recipes支持更复杂的条件嵌套(如variants: { intent: { primary: { ... } } }),但如果你在defaultVariants里写了不存在的 variant,TS 不报错——只有运行时类名为空字符串,容易漏测 - 两者都会生成
className类型,但recipes的返回值还包含className和css属性,后者可用于动态覆盖,此时失去类型保护(css接收的是任意对象)
推荐写法:
const buttonStyle = sprinkles({
padding: 'md',
borderRadius: 'sm',
color: 'textPrimary'
});
// ✅ 类型安全,拼错 'paddding' 会立刻报错
主题切换需用 defineTheme 替代 createTheme
createTheme 返回一个运行时对象,常被误用于 SSR 组件内,导致每个请求都新建主题实例,破坏 CSS 提取前提。真正符合“零开销”的做法是:
- 用
defineTheme声明主题结构(仅类型 + 静态值),配合getCssText在构建时生成多套 CSS - 通过
data-theme属性切换(如),所有主题变体都提前编译进 CSS,无运行时开销 - 不要在组件中调用
createTheme({ ... }),尤其不要把它放在useEffect或事件回调里——这会让 Vanilla Extract 退化为运行时 CSS-in-JS
容易忽略的一点:defineTheme 本身不生成 CSS,必须搭配 globalStyle 或 sprinkles 中的条件引用,否则主题变量只是死代码。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











