싱페어 (SPA) 의 피로감

React is great, React SPA is not

개발 경력의 꽤 많은 시간을 함께해 온 React, 다른 라이브러리도 여전히 많이 존재하지만  de facto standard 가 되었다고 해도 과언은 아니다. 10여년을 해오다보니 여러가지 피로감이 가득하게 된 것이 사실이다. 사실 딱히 React 의 문제라기 보다는 전반적인 SPA 의 개발 경험이라고 생각한다.

Server Side Rendering is AWEFUL

React 가 초기에 Angular 등과 비교해서 절대적인 우위를 차지하던 것이 SSR 이었다. SPA 이라면 근본적으로 가지고 있는 한계점인 (지금 와서는 꼭 그렇지도 않지만) SEO 를 한발자국 더 나아가게 해줄거라는 기대감이 었다. 지금와서도 여전히 SSR 이 가능한 점은 매력적이지만 그렇다고 해서 딱히 잘 된 다는건 아니다.

첫번째로 React SSR 은 그 자체로 매우 느리다. 그도 그럴 것이 JS 코드로 만들어진 컴포넌트를 서버측에서 DOM String 으로 그리는 건데 기본적으로 오래 걸릴 수 밖에 없다. 게다가 하나의 서버에서 여러 리퀘스트를 처리하는 구조상 단시간에 커넥션이 몰리게 되면 TTFB 를 줄이기가 현실적으로 매우 어렵다.

상당히 간단한 게시판 페이지임에도 불구하고 TTFB 가 2초나 되다보니, 커넥션이 늘어나면  CPU 는 둘째치고 빈약한 Node.js 의 메모리가 가득차버린다. 물론 Cache 나 Static 페이지를 만드는 것과 같은 다양한 bypass 가 있는 것도 사실이지만, 기본적으로 bypass 이고 단순히 화면을 미리 그려주는 역할을 하는 frontend 서버가 api 서버 처럼 사이즈가 커지는 기 현상이 발생하게 된다.

따지고 보면 SSR 서버가 하는 일은 고작 첫 접속때 화면을 그려주는 일 뿐인데, 이것 치고는 관리도 귀찮고 비용도 많이 들며 Scalability 의 포인트만 늘어나게 된다. 개발자 생산성을 생각하면 SPA 가 답인듯도 한데 막상 프로덕션에 올라가면 페이지 생길때마다 SSR 을 고려해야 하고, 그렇다고 따로 그리자니 React 가 SSR 된 페이지에 event 를 바인딩할때 여러가지 문제도 생기고 신경쓸 것이 한두개가 아니다.

이제와서 생각하면 어차피 N사를 비롯한 국내 검색 엔진에서 SEO 는 사실상 무의미하고 구글은 SPA 라도 컨텐츠를 읽을 수 있으니 첫 로딩의 경험만 제외하면 굳이 SSR 이 필요한 건가 하는 생각도 든다.

State 주입

SSR 자체도 속도가 느리지만, State 주입에 비하면 아무것도 아니다. Computer Engineering 에서 가장 느린 것이 File I/O 이고 이 보다 더한놈이 Network 다. Redux 는 그 구조상 SSR 시 state 주입이 매우 까다로운 것이 사실이고, 이를 다소 편하게 해결해주는 것이 GraphQL 인데 역시 지옥길이다.

일단 크게 두 가지 문제가 발생한다.

1. API 엑세스

Network 는 느리고 HTTP 는 더 느리고, HTTPS 는 최악이다. 내가 생각하는 SPA SSR 의 한계는 관리 포인트가 늘어난다는 점이다. Cache 나 DB 직접 Access 등의 방법이 있다는 것을 모르는 것이 아니라 그것이 비용을 상승 시키고, 개발 리소스를 잡아먹으며, 복잡도를 증가시킨다.

Redux 로 대표되는 Global state 는 Application 은 서버 렌더링을 위해서 필요한 state 를 server 에서 그려줘야 한다. 때문에 이 state 를 가져오기 위해서 특정한 패턴을 따라 컴포넌트를 만들어서 API 를 액세스 하거나 혹은 아예 직접 DB 에서 가져와 주입시켜주게 된다. 어떤 데이터는 또한 이전 상태를 필요로 하는 경우가 있고 (로그인 이라거나) 그럴 경우에는 또다시 절차적으로 data 를 fetching 해야 하기 때문에 역시 속도가 느려진다.

이를 해소하기 위한 좋은 옵션 중 하나가 GraphQL 인데, query 자체는 한방에 때릴 수 있고 page 별로 필요한 data 를 선택적으로 로드 할 수 있다는 것이 매우 큰 장점이다. 문제는 graphql 로 SSR 하기 위해서는 React component tree 를 따라가면서 하나하나 query 를 실행해줘야 한다는 점이다. 실제로 현재 QWER.GG SSR 에서 가장 느린 부분이 @apollo/react-ssrgetDataFromTree 함수다. apollo 코드를 한땀한땀 뜯어보지 않고서는 퍼포먼스를 개선하기 어렵다.

2. 개인화

특히 SSR 에서 희생할 수 밖에 없는 부분이 개인화 된 컴포넌트이다. 굳이 그거까지 ? 라고 말한다면 할말은 없지만, 한 페이지가 로드 된 이후 여러 항목이 다시 한번 로딩해야 된다면 사용자 경험이 희생된다. 특히 로그인 처리된 글로벌 Navigation 부분이라거나, API 로딩 후 업데이트 되는 일종의 깜삑임 현상이 아쉬운게 나 뿐만은 아닐 것이다.

Cookie 에 로그인 정보를 가지고 있더라도, 개인화된 컴포넌트 렌더링을 위해서는 여러 API 에 접근해야 하는데, 상기한 바와 같이 GraphQL 이 가지고 있는 장점을 살리려다 보면 선택적인 데이터를 DB 쿼리를 통해서 직접적으로 주입하기 어렵기 때문에 역시 손이 묶이게 된다.

이러한 부분 외에도 서버와 브라우저에서 공통으로 쓸 수 있는 유일한 데이터 소스인 Cookie 는 사이즈 제한이 있기 때문에 window.__state__ 같은 이상한 형태로 JSON 을 내려줘야한다. 때문에 보안 (이라기 보다는 굳이 보여주고 싶지 않을걸 보여주어야 한다거나) 문제라거나 html 다운로드에 시간이 길어지는 등 state 를 공유하기 위해서 생기는 많은 문제점이 발생한다.

그래서 Opinionated FS JS Frameowrk?

이러한 피로감에 최근에 대두되는 것이 Opiniated Full-stack JS 프레임워크 라고 볼 수 있다.  대표적인 것이 Next.js  나, Gql, Prisma, React 를 한데 엮은 Redwood.js 다.

If not SPAs, What? - macwright.com

하지만 근본적인 부분이라고 볼 수 있는 SPA 라는 점에서 극복은 하고 있지 못하고, 자유도를 줄이는 대신 개발 세팅 과정에서의 불편함을 줄이는 방향으로 접근하고 있다. 다만 이 역시 SPA 가 가지고 있는 한계점은 극복하지 못하고 있다. 윗 글에서의 LiveView 부분이 상당히 전도유망해 보이고, Turbolinks 와 같은 어프로치도 좋아보이는 것이 사실이다. 둘다 써본적이 없어서 뭐라 말하긴 어려우나, SPA 가 가지고 있는 단점을 상쇄하기 위한 많은 시도가 있다고 보면 될 것이다.

SPA 가 별로라고?

우리 개발자들이 종종 착각하게 되는 것 중 하나가 user 의 입장에서 좋은 web application 이란 무엇인가? 라는 점이라고 생각한다. 내 의견은 그렇다. 주소창 혹은 검색을 통해 어떤 웹사이트에 접속했을때 빠르게 내가 원하는 것을 보여주는 것 그것이 바로 좋은 어플리케이션 그리고 웹서비스 다. 못생긴 사이트라고 유저들이 떠나는 것이 아니고, UI 가 미려하다고 해서 원치않는 컨텐츠를 봐주지 않는다. TTFB 보다 더 중요한 경험은 WEB 에서 존재하지 않는다고 말하고 싶다.

결국 React 를 위시한 SPA 들이 증명한 것은 SPA 보다는 component drive development 라고 생각한다. 기존(?) 의 개발이 url route 를 기본으로 한 page 기반의 development 였다면, 이제는 컴포넌트를 만들고 page 를 component 의 합으로 만든다는 개념으로 프론트엔드 개발이 변화해왔다. 따라서 style 도 global 하게 적용하는 일은 많이 줄었고, component 레벨로 격리시키는 다양한 방법이 시도되고 있다. 최근의 UI 디자인 트랜드인 Design System 과 연결되면 이러한 컴포넌트 중심 개발의 장점이 다시한번 부각된다. separation of concern 을 실직적으로 구현할 수 있게 만드는 좋은 트렌드라고 생각한다.

Should we SPA ?

단언컨데 웹 개발 트렌드에 있어서 SPA 의 주도권은 곧 사라질 것이다. 문제가 너무 많다.

SPA 는 개발만 쉽고 유지보수는 어렵다. Web 이 가진 근본적인 이상과도 거리가 멀고, 검색엔진에 불친절하다. 로드되면 빠르다고 하나, 컴포넌트 단위별 퍼포먼스 최적화는 불가능에 가깝고, 로드 되기 전에 유저는 떠나버리는데 큰 의미도 없다. 게다가 웹사이트가 발전함에 따라 번들 사이즈는 커져만 가고, 분리할수록 느려진다. 잘만들면 SPA 도 문제 없다? MPA 도 잘만 만들면 충분히 빠르다.

Can I use… Support tables for HTML5, CSS3, etc

MS Edge 가 손을 들고 Chromium 을 품었으니 Browser 의 web-component 의 도입도 가속화 될 것이다. SPA 는 분명 의미있는 시도였고, Web 인터렉션 및 복잡한 스테이트 관리 분야에 큰 도약을 가져왔지만, 얼마나 더 지속될 수 있을지는 모르겠다. React component 기반의 MPA 로 토이 프로젝트를 해볼까 했었는데 거슬리는 부분이 한두가지가 아니다. 이젠 React 를 놓아줄때가 된게 아닌가 하는 생각도 든다.

Phoenix LiveView: Interactive, Real-Time Apps. No Need to Write JavaScript. - DockYard

개인적으로는 Elixir Phoenix 의 LiveView 가 좋아보인다. Erlang 은 동시성 처리에 특화되어 있는 만큼 websocket 연결이 많아지더라도 잘 핸들링할 것 같고, 기존 web 의 철학을을 따르는 component driven web development 의 새로운 시대를 열어갈 것 같다. 아니 적어도 나라도 믿고 좀 써봐야겠다.