Single Page Application 소위 SPA 라고 불리는 웹 사이트를 만들기 위한 기술의 사실상의 de facto standard 가 React 라는 걸 부정할 수 없을것이다. React “대항마”로 나온 라이브러리들 조차도 React 의 직간접적인 영향을 많이 받았음을 느낄 수 있다. React 가 초기에 다른 라이브러리들과 근본적으로 달랐던 점이 Component 기반이라는 점인데, polymer 나 angular 에서 이루지 못했던 대중성을 view library 라는 형태로 React 가 획득했음은 기정 사실이다.

개발자들은 재밌는 library 를 장난감처럼 생각하는 것 같다. 이 React 라는 library 로 우리는 뭘 할 수 있을까 여러단계로 실험하다가, angular 보다 더 개발하기 편리한 spa 를 실현할 수 있지 않을까? 하는 접근을 하기 시작했고 react-router 와 flux 그리고 redux 가 등장하며 프론트엔드 개발자 전성기가 열렸다고 해도 과언이 아닐 정도로 5~6년간 Javascript 생태계에서 가장 중요한 존재 중 하나가 되었다.

전성기는 쇠퇴의 시작이라고, 이제 그만 역사속으로 사라질때가 되었다.

1. React is A JavaScript library for building user interfaces

React is A JavaScript library for building user interfaces

React 웹사이트 에 접속하면  “React 는 User Interface 를 만들기 위한 Javascript 라이브러리입니다” 라고 React 를 설명하고 있다. 어? 맞다. React 는 시작부터 View 라이브러리 였고, 어떤 시스템적인 구성을 강제하지 않았다. root 로 취급되는 DOM Element 가 있으면 어디에서나 적용할 수 있었고, 실제로 나도 쏘카에서 처음 React 를 도입할때 그렇게 접근했다. 자유도가 높은 Calendar 를 만들어야 했는데 jquery 로 하는 건 너무 끔찍하게 괴로웠고, vanilla 는 더더욱 싫었다. 때문에 React 로 만든 컴포넌트를 부분적으로 시스템에 적용했고, 의도한대로 작동했다. React 는 opinionated framework 가 아니었고, 그 때문에 매력적이었다.

그런데 이걸 우리가 SPA 의 일부로 사용하면서 문제가 시작되었다. React 가 SPA 로 인기가 있었던 이유는 내가 볼때는 SSR 이 가장 컸다. 당시 angular로 만든 프로젝트는 SSR 이 불가능했기 때문에 javascript 가 로드되는 시간동안 로딩화면을 보여줄 수 밖에 없었고, 당시 google 로봇 또한 SPA 를 이해하지 못했다. React 는 javascript 로 dom tree 를 만들고 이를 브라우저에 렌더링 하는 형식이라 SSR 에 유리했고, 초기 React 의 인기는 여기에서 유래했다고 해도 과언이 아니다. “SEO 가 가능한 SPA 라고?”

CRA 의 등장으로 React 의 SPA 화는 더욱더 가속되었다.

결국 React 는 View 라이브러리 라기보다는 React SPA 라는 거대한 톱니바퀴의 중심으로서 개발되기 시작되었고, redux 와 react-router 가 주목을 받으며 완전히 SPA 프레임워크(?) 로 정착하고 있다. Create React App 이 나오면서 React 를 View 라이브러리로 받아들이는 사람은 많이 사라진게 사실이다. CRA 덕분에 설정파일을 건드리기 어려워진데다가 eject 해본 사람만 안다 진짜로 build 후에 기존 php 등의 web-app 에 적용하긴 더더욱 어려워졌다.

프론트엔드 개발자들이 아무리 자신의 일을 복잡하게 설명하려고 해도 데이터를 화면에 잘 그리는 것이 첫번째로 해야할 일이다. Design system 이 대두되면서 UI Component 레벨의 디자인 및 개발의 중요함이 커진 것은 사실이지만, 여전히 페이지 렌더링이 가장 기본적으로 해야할 일이다. 어느정도 사이즈를 넘어가는 웹사이트는 컴포넌트화 시킬수 있는 부분이 한정되어 있고, (없다는게 아님) React 가 아니더라도 최근에 나타나는 웹 기술들은 대부분 component 를 지원하고 있다. React 생태계가 매우 따뜻한(?) 것은 인정하나, 그렇다고 해서 딱히 못하는 것도 아니다.

React 가 SPA 레벨로 격상(?)되면서, React SPA 로 말미암은 자잘한 버그는 많아지고, React 기반 package 에 대한 의존성이 커지며, Javascript 파일 사이즈가 점차적으로 증가하고 있다.

2. hooks 는 좋지 않은 선택이었다.

나는 솔직히 class Button extends React.Component 가 그립다. hook 과 functional component 가 간결하다는 것은 분명히 인정한다. 간결하다는 것이 항상 구조적으로 단단하다는 의미는 아니고, functional 에 너무 집착해서 오히려 functional programming 에서 멀어진 것이 현재 React 의 hook 컨셉이라고 본다.

웹을 구성하는 렌더링 요소의 대부분은 Component 로 만들어야 할 필요가 없다. 아니 Component 의 장점은 분명히 존재하는데, 그게 꼭 라이프 싸이클을 가진 Reactive 컴포넌트일 필요는 없다는 말이다. 대부분의 경우 class 네임으로 처리가 가능하고, React 가 필요할만한 복잡한 컴포넌트는 프로젝트에서 비중이 높지 않다. 아니 오히려 React 화 되면서 괜히 더 복잡해지고 어려워진 면이 없지 않다. Component 기반의 개발이 유용한 것은 하나의 페이지에 복잡한 일을 하는 UI 요소들이 존재할 때, 이 상태를 트래킹하며 UI 를 렌더링 하는 동작을 구현하기 어렵기 때문이다. 이를 vanilla javascript 로 하면 더더욱 그렇다. 한 page 를 여러개의 component 로 분리하고 개별 component 안에서 변경되는 데이터를 단순화하면 테스트 하기도 편하고 각 요소당 써야하는 코드의 양이 줄어들기 때문에 훨씬 구조적인 어플리케이션을 개발하기 좋다.

어떤 data 가 하나 변경될때마다 해당 dom 을 찾아서 그 값을 바꾸는 방식은 매우 귀찮고 고된 일이었고, 이에 대한 부담을 거의 없애버린 React 는 당연히 매력적인 컴포넌트를 만드는 방식이었다. 어떤 복잡한 일련의 프로세스를 컴포넌트에서 처리해야 하는 경우일수록 React 는 매력적이었다. 그런데 지금은? hooks 의 장점을 살리려면 side effect 에 의존한 state sync 방식으로 코딩을 해야 하는데, 이는 솔직히 매우 가독성이 떨어지고 버그를 양산할 수 밖에 없다. 반면 Component 의 Lifecycle 메소드들은 이해하기 쉬웠고, 절차적으로 처리해야 하는 경우 코드의 흐름을 파악하기 적당히 용이했다. (솔직히 이전에도 편하진 않았다.)  물론 React.Component 에서도 문제가 있다. 특히 React 가 발전하면서 나온 getDerivedStateFromProps 는 그 중에도 가장 끔찍한 녀석이라고 보는데, 이제 props 가 변경되고 난 이후 컨트롤 조차도  React state 에 던져버려야 했다.  componentWillReceiveProps 가 혼란스러운 네이밍인건 사실이었지만, 그렇다고 그보다 더 애매한 놈을 만들 이유가 있었나 싶다.

수많은 hook 중 useEffect 는 그 중에서도 가장 별로라고 본다. 애초에 코드 네이밍부터가 side effect 를 권장하고(?) 있다. setState 를 비동기 함수로 만든 것 까지는 좋은데, 그마저도 순차적으로 작동하지 않아, 여러 스테이트를 동기적으로 업데이트 해야는 코드를 작성할때 까다롭다. 이게 뭐 별거냐 싶지만 가장 기본적인 loading -> data | error -> loading 의 순서로 업데이트 하는 일반적인 Ajax 처리가 고통스럽다.

const [state, setState] = React.useState({ loading: false, data: null });

React.useEffect(() => {
  behave()
  
  async function behave() {
    setState((prev) => ({ ...prev, loading: false }));

    try {
      const data = await fetchDataById(id);
      setState({ loading: false, data })
    } catch (e) {
      setState((prev) => ({ ...prev, loading: false }))
    }
  }
}, [id]);

이런 코드를 쓰다보면 뭔짓인가 싶다. 그리고 각종 추천 lint 를 적용하다보면 dependency value 에 대한 수정 요청이 매우 많이 나오는데 이 또한 매우 피곤하다. hook 은 React 최악의 설계 실패라고 생각한다. Svelte 는 React 의 functional component 와 비슷한 구조이지만 그냥 onMount 라는 함수를 제공한다. beforeUpdate, afterUpdate 같은 hook 비스므리한 lifecycle 함수는 이해하기도 쉽고 명확하다. React 도 비슷하게 꾸밀수야 있지만 네이밍부터가 use 를 강제하는 hook 이라서 가독성이 시궁창에 빠진다. 아니 애초에 use 를 붙여서 hook 을 만들어야 한다는 사실 자체가 넌센스다.

let loading = false, data = null;
$: behave(id)

async behave() {
  loading = true

  try {
    const result = await fetchDataById(id);
    data = result;
  } finally {
    loading = false
  }
}

똑같은 코드를 svelte 에서는 이렇게 간결하게 쓸 수 있다. $:useEffect 와 비슷한 역할을 하는데 일단 plain javascript 에 가까운 코드로 reactivity(?) 를 확보할 수 있다는 점에서 훨씬 읽기 쉽다고 본다. 이것이 가능한 점은 svelte 가 library 가 아니라 compiler 이기 때문인데, 이 부분은 좀 더 사용하고 공부하면서 새로운 포스팅을 해볼까 한다. 지금은 React 를 까야 하기 때문에

3. 여전히 SSR 은 터프하다.

초기 React 가 angular 와 차별받던 포인트가 SSR 이었다는 것을 이미 언급했다. 그런데 두가지 이슈가 발견되었다.

1. SSR 은 매우 느리다.

React SSR 그 고통의 기록 에서도 많이 언급했지만, SSR 은 관리하기도 어렵고 퍼포먼스도 떨어진다. 때문에 React 의 장점이면서 동시에 단점으로 작용하고 있다. SSR 이 어렵다보니 Next.js 가 주목받으며 opinionated 되지 않았다는 React 의 장점이 빛을 바래고, 적극적으로 SSR 을 해야하는 시스템에서는 cache invalidation 문제가 생겼다.

여전히 문제가 없다는 시선이 있다는 것도 사실이지만, 페이지가 많은 곳에서는 SSR 이 여전히 터프하고 그렇지 않은 곳에서는 SSR 의 의미가 크게 없다. SEO 만을 위해서 페이지 캐싱을 해야하는건지 의문이 들고, 비용적인 측면에서도 아쉽다. Scalability 포인트가 늘어나는 것도 걱정스러운 지점이다. 물론 QWER.GG 에서는 로그인 처리까지 SSR 에서 해줘야 한다는 이상한 고집으로 SSR 캐싱이 더 힘들어진 포인트가 있었는데, 이제 그 집착을 버릴때가 된거 같긴 하다.

2. 검색엔진이 똑똑해졌다.

Naver 는 솔직히 개발자 입장에서 SEO 의 의미가 없다고 봐도 된다. Sitemap 도 제대로 읽지 못하고, google 에서는 몇십만 페이지도 잘 쪼개서 크롤링하여 검색에서 보여주는데, naver 는 2년이 지나도 하루에 50 페이지씩 똑같은 페이지를 크롤링한다. (가끔 달라지긴 하는 것 같다.) 참고로 QWER.GG 는 naver 에 sitemap 상으로 3만개 정도 페이지를 열어놨다. 사족이고, 어쨋든 google 은 이제 SPA 를 잘 크롤링한다. 물론 아쉬운 점이 없는 것은 아니지만, 검색엔진은 점점 더 똑똑해질거고 SPA 라는 문제가 크게 작용하진 않을 것으로 보인다.

그러면 React 의 SSR 자체가 오히려 빛을 바래게 된다. 게다가 React 이후로 나오는 프론트엔드 프레임워크는 기본적으로 SSR 을 다 장착하고 나온다. React 만의 장점들이었던 것들이 많이 사라진게 사실이고, SSR 역시 의미가 퇴색되고 있다.

4. 좋은 대체품들이 많이 나왔다.

요즘 밀고있는 Svelte!

Svelte 는 가장 훌륭한 대체품 중 하나라고 생각한다. Next.js 와 유사한 Sapper 라는 녀석도 있는데 아직은 쓰기 좀 애매하다고 생각한다. 전에 Sapper 로 만들다가 뭐가 안되어서 Next.js 로 rewriting 했던 적이 있었는데 이유가 잘 기억이 나지 않는다. 뭔가 별거 아닌 단순한 문제였는데 sapper 로 해결하기 난해한 부분이 있었다. sapper 자체의 문제보다는 시간이 촉박했었는데 해결할 여유가 없었던 것 같다.

언젠가 써보고 싶긴한데, 결국 안쓰겠지..

Elm 도 괜찮은 시도라고 본다. 아직 SSR 은 안되는 것으로 알고 있는데, 코드가 좀 낯설어서 그렇지 여러모로 의미있는 실험이고 개발자 경험이 매우 좋은 것으로 알고 있다.

Elixir 의 Phoenix 기반 LiveView 도 괜찮은 시도라고 본다. 이미 Laravel 에서도 Livewire 이라는 이름으로 만들어지고 있다. 아직 깊이 파보진 않아서 정확한 동작은 모르겠지만 websocket 을 통해 서버에서 브라우저로 어떤 부분을 변경하라는 식의 메시지를 내려주는 것으로 보인다. 서버의 부담을 크게 늘리는 어프로치 일 수 있다보니, 동시처리가 좋은 Erlang 기반의 Elixir 에서 처음으로 시도된게 놀라운 일은 아니다. 솔직히 LiveView 만으로 인터렉티브한 앱을 만들기는 어렵다. DOM 이벤트 조차도 서버로 보내야 하는 문제가 존재하기 때문에, 적당히 프론트엔드 프레임워크와 섞어 쓰는 편이 좋을 것 같다. 그렇게 어렵지 않다.

짤봇 베타 에 Phoenix LiveView 와 Svelte 를 함께 쓰는 형태로 개발하고 있는데 개발 만족도가 높다. 아무래도 사용한지 얼마 안되다보니 빌드 세팅과 같은 소소한 이슈가 있어서 폰트 등이 로드가 안되고 있는데, /beta 를 때면 간단히 해결되는 거라 신경은 별로 안쓴다. Socket 으로 가볍게 메시지를 주고 받는 점도 만족스럽고, 좀 더 사용하다보면 나름의 문제가 생길 것 같긴 하지만 아직까지는 괜찮다. 단점이라면 역시 Phoenix, Svelte 둘 다 생태계가 아직은 부족하고, 해결해야할 문제가 여러모로 산재하고 있다.


React 는 초기 컨셉이었던 뷰 라이브러리로서도 많은 문제를 가지고 있다. setState 의 변태성으로 인한 피곤함, functional 이라면서 side effect 를 장려하는 기묘한 컨셉, lifecycle method 들이 기피대상이 되고 hook 이 새로 등장하며 난해해진 컴포넌트 가독성 등 설계부터 잘못된 부분이 있다고 본다. React 만이 가지고 있던 장점은 이미 후발주자들이 훨씬 진보된(?) 형태로 보여주고 있고, 웹 개발 씬에서 SPA 자체에 대한 회의가 점점 커지고 있다는 것도 무시할 수 없다. GraphQL 은 React 와 별개로 존재하는 것이 어렵지 않고, redux 는 GraphQL 과 궁합이 매우 안좋다보니 벌써 사라져간다.

Redux 와 GraphQL 의 궁합은 최악이다

React 에 대한 관심은 jQuery 에 대한 열기보다 더 빠르게 식을수도 있다고 생각한다. 물론 SPA 가 가진 가장 큰 장점은 Frontend 와 Backend 의 분리라고 보고 있긴 하다.

2013년 5월에 React 가 공개된 것을 생각해보면, 프론트엔드 개발 그 자체와 함께 발전했다고 볼 수 있다.

React 의 대중화와 함께 Frontend 개발자 라는 분야가 독립할 수 있었고, API 기반의 복잡한 인터렉션 유저 인터페이스에 대한 부담을 Backend 에서 분리할 수 있었다. 큰 회사일수록 SPA 에 대한 메리트가 있다고 생각하고 최근 개발되는 많은 웹 어플리케이션들은 작건 크건 어느정도 SPA 성격을 가지고 있다. Youtube 의 경우도 과거엔 polymer 를, 현재는 SPFJS 라는 것을 만들어서 사용하고 있다.

앞으로도 frontend 에 해당하는 개발 영역은 줄어들지 않을거라고 생각한다. Laravel 과 같은 고전 프레임워크에서도 DB 엑세스를 직접 하지 않고 Grpc 를 통해 내부 API 서버와 연동되어 frontend 형태로 개발을 하게 될 수도 있다. Component 기반의 웹 어플리케이션 개발 방식 또한 SPA 와 별개로 발전할 것이다. 충분히 기술적인 저변이 깔렸고, (즉, 될건 다 된다) 이제 React 에게는 쇠퇴만이 기다리고 있다.

7년전 React 의 pros/cons 를 말할때 “커뮤니티 기반이 부족함” 이라는, 지금은 생각할 수 없는 꼬리표가 요즘에 나오는 모던 프레임워크에게도 빠지지 않는다. 개발자들은 귀찮은 것과 복잡한 걸 싫어하고 유행에 민감하다. 생각보다 빠르게 커뮤니티는 성장할 수 있고 아마 그렇게 될 것이다. 이러한 다양한 좋은 라이브러리가 나오다보니, Microfrontend 라는 새로운 패러다임 또한 필연적인 현상일 것이다. 당장 내가 짤봇 베타 에서 하고 있는 것이 바로 마이크로 프론트엔드의 일종 이라고도 볼 수 있겠다. 비록 Svelte 만 쓰지만 다른 프레임웍을 사용하기 어려운 구조가 아니기 때문에.

짤 올리기 부분이 Svelte 로 나머지는 Phoenix LiveView 로 만들어진 짤봇 베타

결론! React 보다 더 좋고 쉬우며 편한 기술이 많이 있는데, 굳이 React 로 모든걸 만들어야할 필요가 있을까?