(변경 )stylexjs, 23년 12월05일 릴리즈, SSR지원
현재, Nextjs 14
버전이 출시 되었음에도,
styled-components
와 같은 CSS-in-JS
형식의 스타일은 server component
를 지원하지 않는다.
지원되지 않는 몇가지 원인 들이 있는데 간단히 살펴보면 다음과 같다.
Next.js 13
이상은 React18
을 사용중인데, 이 버전에서는 이전의 renderToString
을 사용하지 않고, renderToPipeableStream
(Node 환경), renderToReadableStream
(Edge 환경)을 사용한다.
이는 React18
의 Suspense
를 통해 HTML
을 한꺼번에 브라우저로 보내는 대신 선택적으로 Hydrate
후 조각 별로 클라이언트로 전송한다.
이때 head
는 body
보다 먼저 렌더링 되는데, 일반적으로 css-in-js
는 body
가 렌더링이 되어야 style
을 렌더링 할 수 있다. 여기에서 렌더링 순서상 충돌이 발생하게 된다. body
를 렌더링 하면서 head
에 style
을 추가하게 되면 서버와 클라이언트간 수화 불일치(hydration mismatch
)가 발생한다.
해결 방법이 없을까?
- styled-components의 기본 로직을 대충 흉내내면 다음과 같다.
'use client' // nextjs 13이상 app router 사용시
import { ElementType } from 'react'
import React from 'react'
import { v4 } from 'uuid'
const styled =
<T extends ElementType>(tag: T) =>
(styles: TemplateStringsArray) => {
// class 이름을 랜덤으로 생성해서 적용..
const StyledComponent = (props: React.ComponentProps<T>) => {
const uuid = 'custom-' + v4().substring(0, 8)
// head에 style 태그를 추가해서 적용
const style = document.createElement('style')
document.head.appendChild(style)
style.innerHTML = `.${uuid} { ${styles[0].replace(/\n/g, ' ')} }`
// tag에 className을 추가해서 적용
return React.createElement(tag, {
...props,
className: `${props.className ?? ''} ${uuid}`,
})
}
return StyledComponent
}
const TestDiv = styled('div')`
color: red;
`
export default function FakeStyled() {
return (
<>
<TestDiv>hello red world</TestDiv>
</>
)
}
// 결과 - <div class="custom-4478c3cb">hello red world</div>
// .custom-4478c3cb {
// color: red;
// }
css-in-js
는 본연의 특유의 캡슐화가 가능해, 많은 개발자들이 이용해왔다. 하지만, 현재와 같은 방식을 react
가 고수한다면, 깔끔한 해결책은 아직 까지 없는 상황이다.
vanilla-extract
만이 별다른 오류없이 적용 가능한데, 사실 근본적인 문제를 해결한 것이 아니고 구조적으로 파일 기반으로 동작하기에 (body
가 렌더링 되기 전에 미리 지정된 형식(~.css.ts)의 파일을 읽어 처리하는 방식) 자유로운 캡슐화는 힘들다고 볼 수 있다. vanilla-extract
는 javascript
로 css
를 작성할 수 있을 뿐, 동작 방식은 css module
과 다르지 않다고 할 수 있다.
Nextjs
가 엄격하게 제한하는 것과는 다르게 Remix
와 같은 프레임워크는 동작이 가능하도록 열어두었는데, 그 또한 권장 하지는 않고 다음과 같은 주의사항을 포함하고 있다.
Most CSS-in-JS approaches aren't recommended to be use in Remix because they require your app to render completely before you know what the styles are. This is a performance issue and prevents streaming features like defer.
(대부분의 CSS-in-JS 접근 방식은 스타일이 무엇인지 알기 전에 앱이 완전히 렌더링되어야 하기 때문에 Remix에서 사용하지 않는 것이 좋습니다. 이는 성능 문제이며 지연과 같은 스트리밍 기능을 방해합니다.)
결론
CSS-in-JS의 편리함은 많은 개발자들이 입증해 왔지만, 이 기술이 가지는 문제들로 인한 오버헤드는 사용자가 감내해야 하는 부분이다. React 18의 스트리밍 기능으로 인해 SSR에서는 CSS-in-JS의 사용이 제한적이 되었지만, 클라이언트 측 컴포넌트에서는 여전히 잘 작동한다. 따라서 스타일링 방식을 선택하는 것은 개발자의 몫으로 남아있다.
23/12/12 업데이트
23-12-05일자로 페이스북의 stylex가 릴리즈 되었습니다.
babel사용이 강제되긴 합니다만, 서버컴포넌트에도 적용 가능합니다.