React SSR 스타일링에 개고생하는 이유

Composite·2023년 2월 17일
13

Next.js 같은 SSR 지원 가능한 프레임워크를 쓰면서 가장 고생하는 게 바로 스타일링일 것이다.
물론 쌩으로 쓰는 건 지장 없는데 Styled components, emotion 등 스타일링을 리액트답게 체계적으로 관리할 수 있는 라이브러리를 사용할 경우 말이다.

사실 SSR 원리를 아는 리액트 개발자라면 볼 필요도 없는 뻔한 글이지만,
모르는 풋 사과 들에게 오늘 간단명료하게 설명하도록 하겠다.


이 글을 보는 너희들의 심정.jpg

리액트 SSR 특

리액트 SSR에 대해서는 별도로 검색해서 원리를 파악하도록. 여기서는 스타일링 라이브러리에 영향이 갈 수밖에 없는 특징만 설명하도록 하겠다.

사실 SSR, 즉, 하이드레이션(Hydration)을 간단하게 말하면, 리액트 엔진이 초기에 렌더링할 HTML 결과물을 서버 상에서 짠 다음에 그 결과물을 클라이언트에 뿌리는 게 가장 기본적인 원리다.
이건 다른 프론트엔드 라이브러리의 SSR 방식도 마찬가지니 그렇게 이해하면 되겠다.

여기서 또한가지, 리액트는 아주 기똥찬 발상을 적용했는데, 바로, 서버에서 그려진 HTML 결과와, 클라이언트에서 그린 HTML 초기 결과를 비교한다. 즉, SSR 결과와 CSR 결과를 비교하여 검증하는 과정을 거친다. 이유는 간단하다. 문서의 무결성을 위한 과정이다. 무언가가 오염되어 당신의 웹 앱이 공격 대상이 되는 문제를 해결하기 위한 최소한의 안전장치인 셈이다.

여기서 스타일링 라이브러리들이 초기엔 CSR부터 챙기다 보니, 이런 리액트의 특성을 놓쳐서 SSR 에서는 리액트 경고와 함께 초기 스타일 결과물을 원활하게 표시하지 못하는 문제가 생긴 것.
결국 스타일링 라이브러리들은 SSR 대응을 위해 초기에 스타일 태그의 렌더링 결과물을 SSR 수행 시 같이 그려서 교차 검증까지 대응하고 한시름 놓을 수 있었다.

그러나, 대역폭 최적화를 위한 리액트와 Next.js 둘이 합심해서 구현한 새로운 방식은 스타일링 라이브러리를 또다시 시련에 빠지게 만들었으니...

에다가 스트리밍 렌더링

초기에는 SSR 결과물을 그냥 문자열로 뙇 만들어서 뙇 뿌렸다. 이걸 담당하는 게 리액트의 renderToString 메소드다. 즉, 완성된 HTML 문자열 결과물을 한꺼번에 만들고 한꺼번에 뿌린 것이다.
이는 관리와 조작은 간단했으나, 내용이 많을 수록 그만큼 메모리 쳐묵하는 일도 많아지고,
특히 동접자가 많을 수록 메모리 튀는 현상이 증가할 수밖에 없어서,
이미 node.js 에서 제공하는 스트리밍 방식을 최대한 활용해서 렌더링 하는 방식을 택하게 시작했다.
이 방식 또한 리액트 뿐만 아니라 왠만한 프론트엔드 라이브러리들도 적극적으로 채택하는 방식이다.

그런데 왜 이게 스타일링 라이브러리들에게 또다른 시련인가 하면은,
스타일 라이브러리들은 리액트 자체에다가 스타일 결과물을 리액트가 렌더링하는 것처럼 하기가 어려웠기 때문에, React 가 최종적으로 렌더링한 초기 HTML 내용에다가 거기서 분석해서 그 사이에 껴서 렌더링하는 과정을 택했다. 그래. 과정이 더러워도 결과만 맞으면 됐었다.

하지만 스트리밍 방식은 결과물을 예측하기가 어렵다. 스트림에서 어느 시점에 껴야 하는지 판단과 예측이 쉽지 않다.
그래서 이를 해결하기 전까지는 스트림 방식 대신 문자열 렌더링 방식으로 바꿔 사용하도록 가이드했었다.

Emotion: 10 부터 부분 해결

일단 emotion 은 SSR 별도 세팅 없이 렌더링이 가능하도록 구현했다. renderToStringrenderToNodeStream 메소드 호출 무관하게 따르도록 되어 있다. 따라서 emotion 사용자들에게는 엄청난 편의성 개선이 이루어졌다.

그런데 왜 부분 해결이냐, 안타깝게도 모든 스타일 100%를 적용하지 못했다고 한다.
Server Side Rendering - Emotion 문서에 따르면, n번째 자식을 선택하거나 그 비슷한 선택자를 사용할 경우 경고가 표시된다고 한다.
그 이유는 간단하다. 만약 중간에 리액트 컴포넌트 중 무언가가 요소를 가로채는 등 요소의 변화가 몰래 발생할 경우, Emotion은 이를 감지하지 못하고 그저 단순하게 n번째 선택자를 엉뚱하게 선택한 스타일을 렌더링할 것이고, 이로 인한 경고가 발생한다는 것이다.
만약 이런 선택자를 안 쓴다면, 기본 사용법을 쓰라고 권고하니, 가능하면 n번째 자식을 선택자로 쓰는 일이 없다면 큰 문제는 없을 거라 한다.

이는 Emotion 기반의 UI 컴포넌트 라이브러리와 궤를 공유한다.

Styled Components: 미해결

Server Side Rendering - Styled Components

Styled Components 의 경우, Babel 에 상당한 의존성을 가지고 있다.
그래서 SWC 를 주력으로 밀고있는 Next.js(12부터) 에게는 힘겨운 세팅이 될 것이다.
따라서 아직까지는 renderToString 메소드를 사용하는 방법으로 수동 세팅하는 방법이 제공되고 있으며, Babel 의존성 문제를 해결하지 않는 이상 스트리밍 방식의 해결은 요원해 보인다. 그렇다고 가만히 있는 개발자들은 아니니 지켜보기 바란다. 아니면 걍 emotion 써도 되고...

이는 Styled Components 기반의 UI 컴포넌트 라이브러리와 궤를 공유한다.

Stitches: 사용 가능

Server Side Rendering - Stitches

한국인들에게 좀 생소한 놈인데, 이녀석은 React 전용이 아니라 Vue, Svelte 도 사용 가능한 범용 CSS-in-JS 라이브러리다. 하지만 React 에게는 친숙한 API를 제공하는데, 사용하는 방법이 위와 비슷하다고 보면 된다.

이녀석은 SSR을 쉽게 적용할 수 있는데, 대신 좀 찝찝한 방법을 쓴다.
<style> 태그와 같이 dangerouslySetInnerHTML 메소드를 사용하는 방법인데.
얘는 범용인 특성을 이해하면 어쩔 수 없는 선택이지만, 대신 renderToPipeableStream 까지 해결 가능하다는 장점이 있다. 위에 링크 있으니 어떻게 적용하는지 보면 쉽게 파악이 가능할 것이다.

공통

주력 스타일 라이브러리들은 renderToPipeableStream 메소드에 아직 대응을 못 했다. 찝찝하지만 확실한 Stitches 빼고.

마치며

이 글 쓰고나니 다음 React 하게 되면 Stitches 가 마음에 드니 Stitches 써야겠다는 생각이 들기 시작했다.
범용이고, React 사용자에게도 친숙하며, SSR 세팅은 찝찝하지만 간단하게 해결 가능하니.

아무튼, React 개발하면서 가장 고충인 스타일링, 특히 SSR에서 개고생 "했던" 이유를 알아보았다.
다들 알다시피 React 발전속도와 Next.js 발전속도는 빠르다.
이제 서버단 컴포넌트를 챙겨야 하는 시대도 멀지 않았다. 난 이 기능에 많은 기대를 품고 있다.
하지만 스타일링은 이를 따라가는게 쉽지 않다는 고충이 있다.
따라서 만약 가능하다면 사용해보면서 여러 특이사항을 이슈로 공유하고 발전해 나가면 더욱 빠르고 더 좋은 개선안이 나와 React 스타일링에 대한 불만이 사그러들 날을 손꼽아 보자.

비록 한국은 React 점유율이 Vue를 이기지는 못했지만, 국내 React 만족도는 꽤 높고, 게다가 React의 한국 기여도가 꽤 높은 것으로 알려져 있다. 특히 SWC 개발자가 한국인이고, 많은 이들이 문서 번역과 이슈 공유, 해결을 같이하고 있다. 따라서 앞날도 창창하다.

하지만 한국 프론트엔드 시장에서 많은 비율을 차지하고 있는 Vue의 경우는 한국 기여도가 암울해졌다. 오히려 줄었다고 봐도 좋을 정도. 일본은 Vue 이용률이 생각보다 많고, 기여도 많이 하고 있다. 물론 React도 쓰긴 하지만, Vue가 더 많다고. 중국은 뭐 말 안해도 알지?
근데 한국이 Vue 기여도 낮은 이유가... 개발자층이... 하아... 기여를 기대하기 어려운 상황.
그 이유는 내 글에서 SI만 찾아보면 바로 답이 나오니 한번 찾아서 보기 바란다.

그럼 나는 누군가 좆같이 코딩했던 AS-IS Vue 2 소스 보러 이만...

끗.

profile
지옥에서 온 개발자

1개의 댓글

comment-user-thumbnail
2023년 2월 19일

감사합니다!

답글 달기