vanilla-extract의 Turbopack 지원
목차
시작
오래전부터 관심있게 지켜보던 github에 올라온 issue가 몇시간전 closed로 변경되었습니다.
바로 Turbopack에서 Vanilla Extract를 사용할 수 없었던 이슈가 해결된것입니다.
Next.js와 vanilla-extract를 자주 사용하고있었는데, 비슷한 환경에서 서비스를 개발하고있는 개발자들에게도 큰 소식이 될 것 같습니다.
어떤 문제로 함께 사용할 수 없었는지, 그리고 어떻게 해결이되었는지 정리해보고자 합니다.
vanilla-extract란?
근래 프론트엔드 환경에서는 스타일링 방식 중 zero-runtime CSS-in-JS 방식이 크게 선호되고 있습니다.
기본적으로 런타임에 javascript를 실행하여, css를 생성하는 runtime-css 방식인 emotion과 같은 라이브러리와 달리, 빌드 단계에서 css를 미리 생성하여 브라우저에서 추가적인 javascript 실행 없이 스타일을 적용할 수 있기 때문입니다.
이는 초기 렌더링 속도를 개선할 수 있고, SSR 환경에서 발생할 수 있는 Hydration missmatch를 방지하며, 런타임 오버헤드도 줄일수도 있습니다
이러한 여러 장점들로 인해 StyleX, Tailwind, vanilla-extract와 같이 정적 css를 최대한 활용한다는 철학을 가진 라이브러리들이 자주 사용되고있습니다
그중에서도 vanilla-extract는 TypeScript 기반으로 타입 안정성과 함께 빌드타임에 css를 추출할 수 있어 사용성이 좋은 라이브러리입니다. 그리고 디자인 토큰, 테마, variants와 같은 패턴을 type safety하게 구성할 수 있어, 일관된 스타일 구조를 구성할 수 있는 장점이 있습니다
심지어 사용법도 간단합니다.
// styles.css.ts
import { style } from "@vanilla-extract/css";
export const button = style({
background: "blue",
color: "white",
padding: "8px 12px",
});
import * as styles from "./styles.css";
export function Button() {
return <button className={styles.button}>Click</button>;
}
.button_abc123 {
background: blue;
color: white;
}
Turbopack에서는 사용할 수 없었다
Next.js에서 vanila-extract를 정상적으로 동작하게 하기 위해 빌드 플러그인 설정이 필요합니다.
그리고 이 @vanilla-extract/next-plugin 플러그인은 css를 추출하기위해, 내부적으로 mini-css-extract-plugin과 같은 Webpack plugin을 사용합니다.
// next.config.ts
import type { NextConfig } from 'next';
import { createVanillaExtractPlugin } from '@vanilla-extract/next-plugin';
const withVanillaExtract = createVanillaExtractPlugin();
const nextConfig: NextConfig = {};
export default withVanillaExtract(nextConfig);
하지만, vanila-extract와 Turobopack을 함께 사용시 다음과 같은 오류가 발생했습니다.
turbopack은 webpack을 대체하기 위해 Rust로 새로 만들어진 번들러이며, 이때문에 기존 webpack 방식의 플러그인은 그대로 사용할 수 없습니다. 실제로 turbopack 문서에서도 webpack loader는 지원하지만, webpack plugin을 지원하지않는다는 내용이 기술되어있습니다.
https://nextjs.org/docs/app/api-reference/turbopack#webpack-plugins
즉, vanilla-extract는 webpack plugin 기반인데 turbopack은 webpack plugin을 지원하지 않았기 때문에 사용을 할 수 없었고,
이로인해 Next.js v16의 기본 번들러인 turbopack을 사용하지못하고 next dev --webpack 과 같이 cli 옵션을 통해 webpack을 사용해야했습니다.
vanila-extract의 빌드 과정
앞서 언급했듯 vanila-extract는 webpack plugin + webpack css pipeline에 의존하고 있었습니다.
vanila-extract의 *.css.ts 파일은 일반적인 TypeScript 모듈이 아니라, 빌드 단계에서 미리 처리되어야 하는 선언파일 입니다.
예시로 아래와 같은 코드가 있습니다.
// style.css.ts
import { style } from "@vanilla-extract/css";
export const button = style({
background: "blue",
});
이 코드는 빌드 단계에서 다음 작업을 수행합니다.
- CSS 규칙 생성
- CSS 파일로 추출
- JS 모듈에는 클래스 이름만 남김
이후 실제 결과는 다음과 같은 형태가 됩니다.
button.css → 실제 CSS
button.js → "ve_xxxxx" 같은 클래스 문자열
결과적으로 javascript에는 class name 문자열만 남고, 실제 스타일은 CSS 파일로 분리됩니다.
이 과정이 정상적으로 동작하려면 번들러가 *.css.ts를 특별한 방식으로 처리해야합니다.
vanila-extract는 style()이 호출되었을 때 어떤 파일에 속하는지에 대한 정보(file scope)를 바탕으로 file기반 class name hash 생성, class name 생성, css file 생성, css 충돌 방지 등을 수행합니다
webpack 환경에서는 vanilla-extract의 webpack loader와 plugin이 *.css.ts 파일을 빌드 단계에서 분석하여 CSS를 추출하고, javascript module에는 class name만 남도록 변환하도록 빌드용 선언파일로 처리됩니다.
반면, turbopack에서는 vanilla-extract를 처리하는 전용 전처리(loader / plugin)가 존재하지 않아 style()이 런타임에 직접 직접 호출되었습니다.
즉 *.css.ts가 스타일을 선언해놓은 파일이 아닌 일반 javascript module로 실행이되고 style()이 어떤 파일에 속한지 알 수 없어 오류가 발생했습니다.
Styles were unable to be assigned to a file
github issue
https://github.com/vanilla-extract-css/vanilla-extract/issues/1367
2024년 3월 관련 이슈가 처음 등록되었습니다. 당시 Next.js의 latest 버전은 15 버전이였고 turbo dev는 개발환경에서는 빠르고 실용적이었지만, stable 된지 얼마 지나지 않은 시점이였습니다. 특히 webpack loader는 일부만 지원하고, webpack plugin은 미지원, 그리고 next build —turbo는 아직 alpha 단계였던 과도기였습니다.
시간이 흘러 Next.js 16 버전이 릴리즈되고, turbopack이 Next.js의 기본 번들러로 변경되었습니다.
그 사이 production build 지원, webpack loader 지원 확대 등 프로젝트에서 turbopack을 사용해도 될 명분이 많이 생겼습니다.
이에 따라 열려있던 vanila-extract turbo 관련 이슈에 여러 개발자들의 관심이 생겼고, 2025년 10월쯤 turbo 지원을 위한 개발이 어느 개발자에 의해 시작되었습니다.
그리고 오늘 master branch에 PR이 merge되었고, @vanilla-extract/turbopack-plugin 0.1.0을 사용한, next-plugin@2.5.0 next-plugin@2.5.1이 릴리즈되었습니다.
변경된 내용
https://github.com/vanilla-extract-css/vanilla-extract/pull/1639 위 PR에서는 turbopack을 지원하기위한 몇가지 변경사항이 존재합니다
-
webpack plugin 기반의 구조를 turbopack에서도 동작하도록 turopack-plugin 추가
기존 구조에서*.css.ts파일을 처리하기 위해 webpack loader와 plugin이 반드시 필요했습니다.
하지만 turbopack에서는 이 경로가 존재하지않아 발생한 문제였으며, 이를위해 turobpack loader 형태로 동작하며,*.css.ts파일을 처리하는@vanilla-extract/turbopack-plugin이 추가되었습니다. 이는 기존 webpack plugin의 동작을 turbopack loader 형태로 재구현한것입니다. 이 과정에서 turbopack에서도 일반 javascript module로 직접 실행되지 않고, vanila-extract가 기대하는 빌드 단계 스타일 선언 파일로 처리됩니다. -
virtual CSS module 도입
PR에서는vanilla.virtual.css라는 가상 CSS 모듈 추가 이 모듈은 실제 CSS 파일이 아니라 turbopack CSS 파이프라인으로 연결하기 위한 entry point 역할을 합니다. -
Next.js plugin 내에 turbopack 설정 추가 next.config.js에 turbopack 설정을 자동적용하도록 변경되었습니다
turbopack: {
rules: {
"**/*.css.ts": {
loaders: ["@vanilla-extract/turbopack-plugin"]
}
}
}
결론
@vanilla-extract/next-plugin이 Turbopack을 지원하도록 변경되면서 이제 Next.js의 기본 번들러 환경에서도 vanilla-extract를 사용할 수 있게 되었습니다.
기존에는 webpack plugin 기반 구조 때문에 Turbopack에서 사용할 수 없었지만, 이번 변경으로 .css.ts 파일을 Turbopack loader를 통해 처리할 수 있게 되었습니다.
다만 현재 Turbopack 지원은 unstable_turbopack 형태로 제공되고 있어 아직 실험적인 단계입니다.
프로덕션 환경에 바로 적용하하는것보단, 신중하게 도입하는 것을 권장합니다