모던 웹 프론트엔드의 이해

sejin kim·2023년 2월 6일
1
post-thumbnail

'모던 프론트엔드'의 의미

레거시는 무조건 '악'이고, 새로운 기술은 항상 바람직할까요? 당연히 그렇진 않습니다. 이렇게 단순하게 이분법적으로만 생각하는 것은 어리석은 접근일 것입니다. 비즈니스 환경과 개발자 경험, 요구사항, 난이도, 리소스 등 여러 상황을 복합적으로 고려하여 시의적절하게 최선의 도구와 수단을 선택해 최적의 결과를 내는 것이 중요하지, 트렌디한 기술을 사용한다는 것 자체로 우월한 것은 아닙니다. 이는 실제로 '적정 기술'을 추구한다거나 '기술 우선주의'와 같은 기조를 비판, 경계하는 식으로 진중하게 다뤄지고 있는 담론이기도 합니다.

다만, 나중에 나온 기술이나 아이디어는 대체로 이전의 그것에 비해 더 많은 문제를 해결한다거나, 훨씬 편리하고 추상화가 잘 되어 있는 등 여러모로 유리한 점이 존재하기 마련인 것은 사실입니다. 기술은 수렴 진화하고, 이전보다 어떤 면에서든 진보하지 않았다면 굳이 새로운 것을 만들 이유가 없었을 것이므로 이는 당연한 이치입니다.

그런 점에서 보면, 프론트엔드 생태계에서 '모던 프론트엔드'라는 개념을 통해 과거와 현재를 구분짓고 있다는 사실을 한 번 되짚어 볼 만합니다. 개발 환경이나 경향성, 맥락, 패러다임 따위가 유의미하게 변화하게 된 계기와 이유가 존재할 것이기 때문입니다.


"모던 프론트엔드"란 인터랙티브하고 사용자 친화적이며, 미학적으로 만족스러운 웹 인터페이스를 만드는 데 사용되는 최신 기술, 도구 및 관행을 포함하는 프론트엔드 개발의 현재 상황(경향)을 말합니다. 여기에는 반응형 디자인, 모바일 우선 개발 및 접근성과 같은 개념뿐만 아니라 HTML, CSS, JavaScript 및 React, Angular 및 Vue.js와 같은 프론트엔드 프레임워크를 사용하는 것도 포함됩니다.


개인적으로는, 회사에서 담당하고 있던 프로덕트를 처음부터 리라이팅하는 프로젝트를 진행하게 되었을 때 이 '모던 프론트엔드'라는 개념에 대해 다시 생각해본 적이 있습니다. 단순히 '그냥 리액트 쓰면 되는 거 아니에요?' 라는 접근은 그다지 바람직하지 않았기 때문입니다.

기존의 프로젝트는 서버 사이드 템플릿 엔진(JSP/PHP) 기반에 jQuery로 작성된, 그야말로 전형적이고 고전적인 웹이었습니다. 레거시는 어느 회사나 존재하는 것이라지만, 그래도 그렇지 회사를 대표하는 메인 서비스인데 장기간 방치된 코드 위에서 개발하고 있다는 사실이 달갑지가 않았습니다. 비즈니스 환경이나 조직 상황 등 여러 사연이야 있긴 하지만, 하여튼 많이 뒤처진 건 분명해 보였습니다.

그렇게 오랜 시간 누적되어 온 기술 부채였고, 마이너한 리팩토링 정도로는 큰 의미를 갖기가 어려워 무언가 돌파구가 필요한 상황이었습니다. 조금만 잘못 수정해도 에러가 발생하기 일쑤였고, 새로운 비즈니스 피처를 개발할 때마다 그야말로 누더기를 기우는 느낌이었습니다.

당장 실질적으로 생산성과 개발자 경험에 직접적인 악영향을 미치고 있는 상황인지라, 말 그대로 '부채'처럼 마음의 짐이 되기도 했습니다. 특히 성장이라는 측면에서 악영향이 크고, 동시대를 살아가지 못하고 있다 생각되어 더욱 그랬습니다.

다만 작업에 앞서, 어떤 점들이 좋아지고 무엇을 얻을 수 있을 것인지는 한 번 되짚어 볼 필요가 있었습니다.

프론트엔드 프레임워크가 애저녁에 너무 당연하고 보편적인 것이 되었다곤 하지만, 그럼에도 '이 기술을 왜 사용하는지'를 이해하기 위해서이기도 했고, 동료들은 물론 개발 직군이 아닌 다른 관계자들과도 '다 갈아엎고 처음부터 다시 만드는' 작업에 대한 공감대와 합의를 형성할 필요가 있었기 때문입니다.

아래에서 이어나갈 내용 역시 과거 프로젝트를 처음 계획했을 당시, 사내 위키에 간단히 요약 정리하여 공유했던 글을 바탕으로 하고 있습니다.






어떤 점이 좋아질 수 있을까

1. 렌더링 퍼포먼스 개선 : Virtual DOM


브라우저가 화면을 렌더링하는 작업은 비용이 큽니다. 프론트엔드 개발자라면, 면접 단골 질문으로도 유명한 '브라우저의 렌더링 프로세스'에 관해 학습해 본 적이 있었을 것입니다.

DOM을 조작하여 어떤 변경이 발생하게 되면, 렌더 트리를 재구성하고(style), 노드의 위치와 크기를 계산한 다음(reflow/layout), 실제 픽셀로 변환하여 화면에 그려내고(repaint/rasterize) 합성하는(composite) 작업들이 유발되면서, 브라우저가 렌더링 파이프라인 - Critical Rendering Path(CRP)를 반복하게 됩니다.

복잡하고 인터랙티브한 웹을 구현하려면 아무래도 DOM 조작 역시 잦아지기 마련인데, 이럴 경우 아무리 브라우저 레벨에서 적극적인 최적화가 이루어지고 있다고는 하나 성능 이슈가 발생할 가능성이 큽니다. 요컨대 DOM을 직접 조작한다는 행위 자체가 잘못되었다기보다는, 시의적으로 패러다임과 어긋날 여지가 생기게 된 셈입니다.

그래서 이러한 프로세스를 보다 효율적으로 접근해보자는 차원에서, Virtual DOM이라는 아이디어가 React 진영의 주도로 제시됩니다. 메모리에 가상의 DOM을 구현해 놓고, 변경이 발생했을 때 이것을 먼저 조작한 다음 실제 DOM과의 변경점을 비교(diffing)하여 반영(patch)하는 패턴입니다.

Virtual DOM에서의 변화는 실제 렌더링을 유발하지 않으니 연산 비용이 낮고, 다수의 변경을 그룹화하여 한 번에 처리할 수도 있으므로(buffering) 효율적입니다. 또한 실제로 변경이 발생한 부분만 적절히 업데이트할 수 있어, DOM 트리의 연쇄적인 변경에서 비롯되는 비효율성도 최소화할 수 있습니다.

특히 DOM을 추상화했다는 점에서 브라우저와의 종속성도 없어져 확장에도 용이해지고, 개발자가 일일이 직접 하기 부담스러운 명령적인 DOM의 조작 및 관리 작업 자체를 자동화하여 라이브러리/프레임워크에게 위임할 수 있게 된다는 점이 핵심입니다.



다만, Virtual DOM이라는 아이디어가 그 자체로 우월한 것은 아닙니다. 고도화된 알고리즘과 성숙된 기술을 통해 어디까지나 통상적인 상황에서의 퍼포먼스를 개선하는 원리이며, 항상 절대적으로 빠른 것은 아닙니다.

오히려 'Virtual DOM이나 diffing 역시 뭔가가 더 추가되는 거 아닌가?'라는 의문을 가질 수도 있는데, 실제로 Svelte 같은 경우에는 'Virtual DOM이 빠르다는 것은 환상'이며, 그 자체로 오버헤드라고 보고 Virtual DOM을 구현하지 않습니다. 대신 컴파일 및 빌드 과정을 거쳐 실제 DOM을 조작하면서도 높은 효율성을 가진 명령형 코드로 변환하여 우수한 퍼포먼스를 달성한다고 알려져 있습니다.

그래서 사실 Virtual DOM을 렌더링 퍼포먼스에만 초점을 두고 이해하기보다는, 선언적(Declarative)이며 상태(State) 중심으로 UI를 개발할 수 있도록 하는 메커니즘을 구현하는 데에 활용하는 디자인 패턴 중에 하나라는 점에 무게를 두는 편이 더 적절합니다.


📖 더 읽을거리 :



2. 패러다임의 전환 : Declarative UI, Reactive Web

'선언형(Declarative)' 이라는 개념이 프로그래밍에서 이전에 없던 새로운 것이라거나 하는 건 아니지만, 과거의 웹은 주로 '명령형(Imperative)'으로 개발하였다는 점에서 가장 뚜렷하게 대조가 되는 부분입니다. 패러다임의 변화이기 때문에, 이전 방식에 익숙한 고전적인 웹 개발자들이라면 말 그대로 '생각을 달리 해야 하는', 멘탈 모델이 달라져야 하는 부분이어서 직접적으로 체감이 되는 점이기도 합니다.


선언형과 명령형의 개념은 stackoverflow의 한 답변에서 적절한 예시를 들어 아주 잘 설명하고 있기도 합니다. 요컨대 명령형은 '프로그램이 어떤 방식으로 목적을 달성해야 하는지'를, 선언형은 '프로그램이나 상태가 무엇과 같아야 하는지'를 설명하는 것이라고 정의할 수 있습니다.


개발자가 직접 화면을 그리는 것이 아니라, 데이터가 화면을 그린다는 점에서 취할 수 있는 여러 이점이 있습니다. 가장 대표적으로는 더 이해하기 쉽고, 더 짧은 코드를 작성할 수 있게 하여 빠른 개발이 가능하도록 한다는 것입니다.

데이터가 변경될 때, 명령형이라면 명시적으로 UI가 어떻게 변경되어야 하는지 직접 코드를 작성해 주어야 하므로, 코드베이스의 규모가 커질 수록 스파게티 코드가 되면서 복잡하고 유지보수하기 어려워질 가능성이 높습니다. React의 공식 문서를 짧게 인용해 보겠습니다.


Manipulating the UI imperatively works well enough for isolated examples, but it gets exponentially more difficult to manage in more complex systems. Imagine updating a page full of different forms like this one. Adding a new UI element or a new interaction would require carefully checking all existing code to make sure you haven’t introduced a bug (for example, forgetting to show or hide something).

UI를 명령적으로 조작하는 것은 위와 같이 고립된 예제에서는 충분히 잘 작동하지만, 더 복잡한 시스템에서는 관리하기가 기하급수적으로 어려워집니다. 다양한 form으로 가득한 페이지를 업데이트해야 한다고 생각해 봅시다. 새로운 UI 요소나 상호작용을 추가하려면, 기존의 모든 코드를 주의 깊게 살펴 버그가 발생하지 않는지(예를 들어 무언가를 표시하거나 숨기는 것을 잊어버리는 등) 확인해야 합니다.


반면 선언형에서는 라이브러리 또는 프레임워크가 UI 변경 작업을 위임받고, 선언된 UI의 상태와 일치하도록 관리해 주므로 개발자는 상대적으로 더 단순하고 적은 코드만 작성할 수 있게 됩니다. 내가 원하는 결과를 명시(선언)하기만 하면, 구체적으로 그 결과에 도달하기 위해 수행해야 하는 렌더링 동작들은 알아서 잘 된다는 것입니다. 이 부분은 '제어의 역전(Inversion of Control)'을 말하는 것이기도 합니다.

또한 '상태 관리'와 'UI 렌더링'의 문제를 분리한다는 점을 주목할 만한데, 적절한 관심사의 분리 덕분에 UI가 어떻게 동작할지를 더 쉽게 예측/추론할 수 있고, 디버깅도 더 용이하며, 퍼포먼스를 개선하고자 할 때도 무언가를 더 시도해볼 만한 여지가 많아집니다.


📖 더 읽을거리 :



3. 컴포넌트 주도 개발 방법론의 도입 : 관심사의 분리, 재사용성 향상


컴포넌트 주도 개발(Component-Driven Development, CDD)이란 관심사, 역할에 따라 작은 단위로 분리된 '컴포넌트'를 중심으로 UI 빌드 프로세스를 구축하고 웹 애플리케이션을 구조화하려는 개발 방법론입니다. 컴포넌트에서 시작하여, 페이지 또는 화면 수준에 이르는 '상향식'으로 전체적인 View를 구성하고자 하는 접근입니다.

사실 소프트웨어 엔지니어링에서 작게 분리된 코드 조각들 - 즉 모듈을 조합하여 하나의 완성된 소프트웨어를 만드는 것은 이미 너무나 흔한 아이디어이자 기본적인 원칙이었습니다. 커다란 문제를 작게 분할하여 정복해 나간다는 전략으로, 프론트엔드에서도 마치 블록을 조립하듯 컴포넌트를 결합하여 복잡하고 거대한 UI를 구성하고자 한 것입니다.

레거시에서는 대개 View를 구현하는 코드들이 비즈니스 로직과 혼재된 형태로 작성된 경우가 아주 많습니다. 그래서 어떤 특정한 부분을 수정하려고 한다면, 1. 먼저 수많은 코드 뭉치(?) 속에서 해당 UI를 구성하는 코드를 찾아내야 하고 2. 그나마도 다른 모듈과 강결합되어 있는 경우가 많아 부주의하게 수정하면 무관계한 요소들이 잘못 동작한다거나 전반적인 코드 실행 흐름 자체가 망가지기도 했습니다.

CDD였다면, 컴포넌트가 이미 특정한 기준(관심사, 도메인 등)에 따라 분리되어 있는 데다 제각기 특정한 기능/역할을 지엽적으로 담당하고 있기 때문에, 그 부분만 찾아 수정하면 다른 곳은 거의 신경쓰지 않을 수 있게 되니 생산성은 상승하고, 오류 가능성은 낮아질 수 있습니다.

이외에도 CDD의 장점을 열거해보자면 아래와 같습니다.


  • 재사용성 (Reusability) : 잘 분리된 컴포넌트는 여러 부분에서 유연하게 재사용될 수 있습니다. 따라서 중복 코드가 감소하고, 개발 시간이 단축되며 유지보수성이 개선됩니다.
  • 모듈성 (Modularity) : 컴포넌트 단위의 독립적인 개발이 가능해지므로, 격리된 범위에서 테스트를 보다 쉽게 수행할 수 있습니다.
  • 효율성 (Efficiency) : UI가 컴포넌트 단위로 분리되어 있기 때문에, 다른 작업자들과 쉽게 공유할 수 있으며 개발과 디자인을 병렬적으로 처리할 수 있습니다.
  • 확장성 (Scalability) : 컴포넌트가 서로 낮은 결합도로 구성되어 있다는 것은, 추후 새로운 컴포넌트를 추가하는 방식으로 쉽게 확장해나갈 수 있다는 것을 의미하기도 합니다.

여기에 '디자인 시스템'을 도입해 이러한 방법론을 더욱 견고하게 보완하고 발전시킬 수 있다는 점, 그리고 흔히 말하는 '애자일' 개발 방법론을 적용하기에 보다 적합하다는 점 등도 장점이라고 할 수 있을 것입니다.


📖 더 읽을거리 :



4. 전략적인 렌더링 방식/아키텍처 선택 : SSR & CSR, MPA & SPA


페이지의 렌더링 방식은 어디에서 수행되는지(주체가 누구인지)를 기준으로 구분됩니다. Server-Side Rendering(SSR)은 서버에서 HTML을 렌더링하여 완성된 페이지를 클라이언트에게 내려주고, Client-Side Rendering(CSR)은 클라이언트에서 JavaScript 코드를 실행하여 동적으로 HTML을 생성하여 렌더링합니다.

SSR은 전통적으로 페이지를 렌더링하는 방식이었던 반면, CSR은 새로고침의 최소화, 상호작용성의 향상, App-like한 사용자 경험 등을 목적으로 구현하는 방식입니다.

하지만 이미 많이 연구되고 알려진 것처럼, 각각의 방식은 제각기 일장일단이 있어 무엇이 항상 우월하다고 할 수는 없고, 비즈니스 환경이나 요구사항, 사용자 경험 등에 따라 전략적으로 선택하게 됩니다.

어느 한 가지 방식으로만 구현해야 하는 것도 아니기 때문에, 실제로 많은 웹 사이트들은 두 가지 방식의 장점을 선택적으로 혼합하는 방식을 채택합니다. 초기 페이지는 서버에서, 이후부터는 클라이언트에서 렌더링하는 방식인데, 두 방식을 결합하는 형태여서 hybrid, 또는 Universal Rendering이라는 용어로 정의하기도 합니다.



통상 프론트엔드 프레임워크를 활용하는 경우가 많은 것은, Universal Rendering을 보다 추상화된 수단으로 쉽게 구현하려는 목적이 큽니다.

직접 구현하는 것도 당연히 가능은 하겠지만, 서버를 구축하고 렌더링 로직이나 hydration을 구현하는 등의 작업들이 매우 장황하고 어렵기 때문에, 현실적으로는 크게 의미를 가지기 어렵습니다.

그래서 NextNuxt는 말할 것도 없고, SvelteKit이나 fresh 등 프론트엔드 프레임워크라면 기본적으로 서버 렌더링 자체를 전제하면서 기능을 지원하고 있습니다.

한편 Isomorphic(Universal) JavaScript, 즉 서버와 클라이언트 양 쪽에서 동일한 JavaScript 코드를 실행하고 통합하는 수단으로도 프레임워크가 유용하게 활용됩니다. 별도의 서버 사이드 언어를 사용하지 않고, 서로 같은 컨텍스트를 공유할 수 있다는 점은 개발자의 부담을 덜어 주며 생산성 측면에서 강점을 가질 수 있습니다.



Single Page Application(SPA)는 고전적인 웹 페이지로 대표되는 Multiple Page Application(MPA)와 대비하여 '모던'한 아키텍처이자 패러다임으로 구분되지만, SEO가 어렵다거나 무거운 JavaScript 번들로 인한 초기 구동 속도와 관련한 문제 등이 있습니다.

그래서 초기 페이지에 한해 서버에서 렌더링하여 절충적으로 단점을 상쇄하려는 것이고, 이러한 솔루션을 제공해줄 수 있는 프레임워크가 각광받게 된 것이라고 할 수 있겠습니다.


📖 더 읽을거리 :






마치며

이렇게 정리하다 보면, 아래와 같은 생각을 할 수 있게 됩니다.


  1. 이러한 문제와 필요가 있었기에 지금의 기술과 도구들이 등장했구나
  2. 그런데 그것도 자세히 보니 과거의 변주이고, 예전부터 하던 것들이구나

가령, SSR은 이미 너무나 익숙한 방식입니다. 동적이든, 정적이든 서버에서 HTML을 렌더링하고 서빙해 주는 것은 그동안의 웹 개발에서 항상 해 왔던 것일 뿐입니다. CSR은 과거 JavaScript의 발전 과정에서 SPA를 구현하는 아이디어로 부각되었지만, 무조건적으로 우월하다거나 모든 문제를 해결할 수 있는 것은 아니어서 절충적으로 과거의 방식을 결합해야 할 필요가 있었고 결과적으로 Universal의 형태가 되었습니다.

CDD도 마찬가지입니다. 유용하기는 하지만, 컴포넌트 자체는 전혀 새로운 아이디어가 아닙니다. 그저 '프론트엔드에서도 이렇게 하는게 좋겠다'라는 접근일 뿐이었습니다.


한편, 예전엔 잘못된 것이었는데 지금은 괜찮은 것도 있습니다. 가령 이벤트 핸들러를 인라인으로 바인딩하는 것처럼, 관심사가 뒤섞이고 프레젠테이션과 로직이 결합되는 패턴은 대놓고 '그렇게 하지 말라'는 지침도 있었지만, 지금은 흔하게 사용되고 있는 것이 그 예입니다.

물론 그때와는 환경이 같지 않고, 라이브러리나 프레임워크에서 효율적으로 잘 처리해주기 때문에 그렇게 하게 된 것이기는 합니다만, 일단 코드 자체에서 나타나는 패턴으로만 보면 전형적인 커플링을 나타냅니다. CSS-in-JS 같은 패턴도 마찬가지입니다.


ReactNext.jsPHP로부터 영향을 받았고, 그런 React로부터는 Preact 같은 파생 관계의 프로젝트가 탄생하기도 했습니다.

Vue 또한 AngularJS에 기원을 두고 있고, 그런 Vue의 핵심 특징인 Composition APIReactHook과 서로 많이 닮아 있는 등, 계속해서 새로운 정의와 용어나 도구가 등장하지만 그 본질은 큰 틀에서 크게 변하지 않는 것처럼 보이기도 합니다.


그런 점에서 보면, 프레임워크 같은 도구에 너무 매몰되지 말라는 조언도 이해가 가게 됩니다. 물론, 현실적으로 당장 회사에서 업무를 하기 위해서는 그런 도구의 사용법을 깊이 이해하고 숙련되는 것도 중요하긴 합니다. 당장 아무 프론트엔드 개발자 채용 공고 - JD만 보아도 React를 언급하지 않는 경우는 거의 없으니 말입니다.

그러나 프론트엔드 생태계는 잦은 변화로 인해 개발자들이 피로감을 호소하는 경우가 자주 보이기도 하다 보니, 특정한 도구를 잘 다룬다는 것만으로는 언젠가 한계가 있을 것임은 분명해 보입니다.

현재 React는 거의 글로벌 스탠다드, 전공필수와 같은 위상이지만, React에 대한 회의나 비판이 없는 것도 아닌 만큼 미래는 어찌될 지 알 수 없는 일입니다. 가령 실제 사용률이 그다지 높지는 않아도, 개발자마다 찬사를 아끼지 않을 만큼 뛰어난 DX를 자랑하는 Svelte가 대안이 될 수도 있고, solid, astro, quik, fresh 등 온갖 도전적이고 걸출한 프레임워크가 즐비한 것이 프론트엔드 생태계이니 말입니다.

그러니 정말 중요한 것은, 결국 뻔한 말이긴 하지만 지금 내가 선택한 기술이 어떤 문제를 해결하기 위한 수단이고, 무엇을 위해 왜 사용하는 것인지를 본질적으로 이해하는 것이 중요하다고 할 수 있겠습니다.

그냥 보편적으로 많이 쓰는 도구여서가 아니라, '우리는 이런 문제가 있으니, 이것을 해결하고 어떠한 이득을 얻기 위해 이런 스택으로 개발해야 한다'며 프로젝트를 진행하는 것이 엔지니어에게 어울리는 생각일 것입니다.

profile
퇴고를 좋아하는 주니어 웹 개발자입니다.

0개의 댓글