(번역) 프런트엔드의 미래 탐색하기

원문 : Navigating the future of frontend

최신 프런트엔드 메타 프레임워크를 이해하고, 과거와 현재의 기본 개념을 연결해 보세요.

프런트엔드 생태계는 전환기에 있습니다. 떠오르는 프런트엔드 개발자들은 경쟁하는 프레임워크와 개념들, 지지자들, 다양한 선호도 및 모범 사례로 이루어진 복잡한 환경을 탐색하고 있습니다.

오늘날, 가장 많이 사용되는 소프트웨어의 많은 부분이 웹과 웹 기술을 이용하여 실행됩니다.

자바 애플릿부터 플래시, 자바스크립트의 급속한 발전에 이르기까지 다양한 브라우저, 화면 크기, 디바이스 기능, 네트워크 상태, 높아지는 사용자 기대치를 관리하기 위한 수많은 도구가 개발되었습니다.

하지만 본격적인 소프트웨어 배포 플랫폼을 추구하는 과정에서 웹의 근본적인 제약 조건은 변하지 않았습니다.

이 글에서는 최신 자바스크립트 메타 프레임워크가 이러한 제약을 어떻게 극복하는지 살펴보겠습니다.

컴파일, 라우팅, 데이터 로딩 및 변경, 캐싱, 무효화와 같은 기본적인 요소들이 어떻게 결합되는지에 대한 높은 수준의 멘탈 모델을 구축할 것입니다. 이를 통해 리액트 서버 컴포넌트가 제공하는 기능을 포함한 새로운 아키텍처 패턴을 더 잘 이해할 수 있습니다.

세부 사항에는 항상 복잡하고 어려운 문제가 있지만, 프레임워크 사이에서 눈에 보이는 것보다 더 많은 융합을 보게 될 것입니다.

마지막에는 프레임워크의 흥망성쇠를 넘어 지속되는 기본적인 개념과 표면적인 API를 갖추고, 프런트엔드 생태계가 나아갈 방향을 더 잘 이해하게 될 것입니다.

프레임워크의 흥망성쇠

웹 플랫폼은 상위 도구들이 구축되는 포괄적인 계층이며 변화 속도가 느립니다. 시간이 지남에 따라 플랫폼이 개선되면서 더 높은 수준의 추상화가 필요하지 않게 됩니다.

하지만 플랫폼이 항상 필요한 기능을 제공하는 것은 아닙니다. 플래시가 사라지면서 자바스크립트 생태계가 풍부한 인터랙티브 경험을 구축하기 위한 기술로 그 공백을 메우고 있습니다.

지난 10년간의 기술 붐은 많은 조직이 웹을 고객과 직접 마주하는 통로로 활용함에 따라, 이전 세대의 웹 개발자와는 대조적으로 두꺼운 클라이언트 데스크톱 개발과 유사한 프런트엔드 전문화(specialization)가 이루어졌습니다.

오늘날 널리 사용되는 프레임워크인 앵귤러, 리액트, 뷰, 스벨트, 솔리드, 퀵(Qwik) 등은 클라이언트 측 상호 작용 문제를 해결하면서, 데이터 변화에 따라 일관되게 렌더링되는 컴포넌트를 구성할 수 있습니다.

세부 사항에 따라 서로 다른 장단점이 있기 때문에 여전히 논쟁의 여지가 있습니다. 예를 들어, 반응성을 다루는 최상의 방법, 기능보다 템플릿에 대한 선호도, 복잡성에 대한 선호도 등이 그 예입니다. 그러나 큰 틀에서는 유사한 개념으로 수렴되며 비슷한 기능을 제공합니다.

큰 주제 중 하나는 애플리케이션 수준의 프레임워크를 구축하는 메타 프레임워크의 복잡성이 증가하고 있다는 것입니다. 두 개의 보편적인 주기를 이해함으로써 우리의 조사를 시작해 보겠습니다.

기능성 vs 적합성

도구의 기능은 아이디어를 표현하는 능력입니다. 새로운 기능이 추가되면 도구는 더욱 복잡해집니다.

도구의 적합성은 도구가 주어진 상황에 적합한지 여부입니다. 조직적 맥락에서 이는 자주 함정에 빠지거나 문제가 발생하지 않으면서, 가능한 한 많은 사람들이 만족할 수 있는 것을 의미합니다.

능력을 향상시키는 혁신의 주기는 복잡성과 혼란을 초래하기도 합니다. 이로 인해 기능을 통제하거나 사용하기 쉽게 만드는 추상화가 구축되는 주기로 이어집니다. 이에 대한 대응으로 보다 간단한 접근 방식을 선호하는 반대 추세도 종종 나타납니다.

번들되지 않은 것의 번들링

자바스크립트 피로(fatigue) 현상은 여러 기술의 서로 다른 기능 사이에서 선택하고 함께 작동하도록 만들어야 하는 과정에서 비롯됩니다.

번들링은 이러한 기능을 하나의 제품으로 연결하여 더 쉽게 사용할 수 있도록 하는 것입니다.

번들링 추상화는 한 번에 많은 문제를 해결해야 합니다. 따라서 규모가 커지고 추상화 수준이 높아져 개발자를 불편하게 만드는 경우가 많습니다.

이러한 것들이 너무 느리거나 제한적이라고 인식되면, 각 부분을 분리하여 독립적으로 혁신하는 새로운 주기가 시작됩니다.

시간이 지나면서 이러한 지속적인 반복과 함정의 고통을 경험함으로써 우리는 무엇이 중요하고 무엇이 중요하지 않은지 알게 됩니다.

클라이언트 측 컴포넌트 프레임워크에서 수년간의 반복을 통해 각기 다른 도구와 모범 사례를 묶는 자체적인 “메타” 프레임워크가 생겼습니다. 리액트의 많고 새로운 API는 이러한 상위 수준의 애플리케이션 프레임워크 중 하나에 통합하기 위해 분리된 기능을 의도한 것입니다.

기본으로 돌아가기

“폭 넓게 길을 이해한다면, 모든 것에서 그것을 볼 수 있을 것이다” - 미야모토 무사시

컴퓨터 시스템의 두 가지 주요 구성 요소는 연산과 저장입니다.

시스템의 성능은 데이터를 계산하는 연산과 스토리지에서 데이터를 가져오는 등의 입출력(I/O) 작업에 의해 제약됩니다.

웹에서는 이러한 근본적인 제약이 자바스크립트의 단일 스레드, 사용자와 서버 간의 네트워크 지연 시간으로 나타납니다.

웹의 모든 연결에는 요청-응답 패턴을 따르는 클라이언트와 서버가 포함됩니다. 서버는 항상 첫 번째 호출 포트입니다(CDN에서 정적 파일을 제공하는 경우에도 마찬가지입니다). 이러한 작업을 서버와 클라이언트 간에 어떻게 분산하는 방식에 따라 서로 다른 장단점이 발생합니다.

클라이언트에서 연산을 수행하면 빠른 상호 작용이 가능하지만, 너무 많으면 메인 스레드가 응답하지 않게 됩니다. 서버에는 이러한 제약이 없지만 서버에 작업을 요청하면 네트워크 지연이 발생합니다. 웹에서 인터랙티브한 경험을 제공하려면 이러한 제약 조건의 균형을 맞추는 것이 중요합니다.

읽기 및 쓰기

또 다른 필수 요소는 데이터를 읽고 쓸 수 있는 능력입니다.

웹은 읽기 전용 정적 문서로 시작되었습니다. 결국 데이터를 지속하고 동적으로 HTML을 생성하기 시작했습니다. 신뢰할 수 있는 폼(form) 요소를 사용하여 이제 쓰기 기능을 수행할 수 있게 되어, 새로운 종류의 애플리케이션을 위한 웹의 기능을 확장할 수 있게 되었습니다.

이 모델에서는 클라이언트에서의 사용자 상호 작용이 동기식 HTTP 요청으로 변환됩니다. 쓰기 후 경험에 대한 업데이트는 서버가 새로 생성된 문서로 응답함에 따라 대략적으로 이루어집니다(coarse-grained). 브라우저는 이를 표시하기 위해 자체적으로 다시 로드합니다.

결국, 더 많은 기능을 사용할 수 있는 XMLHttpRequest를 도입했습니다. 이제 사용자 작업은 페이지의 관련 부분만 업데이트되는 세분화된 비동기식 업데이트가 가능해졌습니다.

이 두 가지 경우 모두, 렌더링되는 내용의 데이터 출처는 서버에 의해 구동되는 애플리케이션 상태입니다.

이제 우리는 이 이야기를 잘 알고 있습니다. 시간이 지남에 따라 템플릿과 애플리케이션 상태는 점점 더 클라이언트 측으로 이동했고, 애플리케이션 상태는 클라이언트 주도로 바뀌면서 빠른 낙관적 쓰기가 가능해졌고, 이로 인해 기저 네트워크를 숨기게 되었습니다.

이는 지난 10년간 업계에 진입한 많은 사람들이 이 모델을 채택한 주된 이유입니다. 속도가 생명인 업계에서 기능이 늘어날수록 모든 코드를 저장할 곳은 단 한 곳뿐입니다.

단일 머신을 사용하는 접근 방식이 성능에 제약이 생기면 이를 포기할 수 있습니다. 그렇지 않으면 분산 시스템의 영역으로 들어가게 됩니다.

분산된 시스템 프런트엔드

클라이언트 전용 접근 방식의 멘탈 모델은 백엔드와 비동기적으로 동기화되는 장기 실행(long-running) 데스크톱 애플리케이션과 같습니다.

서버 기반 애플리케이션 상태로의 전환은 대부분의 “프런트엔드 뒤쪽” 코드가 서버로 다시 이동하기 때문에 가장 중요한 멘탈 모델 변화 중 하나입니다.

다른 서버 기반 애플리케이션 프레임워크와의 차이점은 풍부한 클라이언트 측 상호 작용과 안정적인 탐색 기능이 유지된다는 점입니다.

동일한 프레임워크와 제품 내에서 서버 중심 패턴의 성능 특성과 클라이언트 중심 접근 방식의 기능을 언제 어떻게 활용해야 하는지 알 수 없기 때문에 혼란이 발생합니다.

리액트 서버 컴포넌트는 서버와 클라이언트를 통합하여 조합 가능한 컴포넌트의 통합된 작성 경험을 추구하며 나아갑니다. 이는 업계에서 가장 지배적인 프레임워크에 대한 중요한 변화입니다.

다른 언어 생태계에서도 비슷한 개념을 모색하고 있습니다. 예를 들어 C#의 Blazor, Clojure의 Electric, Rust의 Leptos도 비슷한 아이디어를 추구합니다.

복잡한 세부사항을 살펴보기 전에, 왜 지금 이런 방향으로 나아가는지 이해해 봅시다.

성능 향상 외에도 자바스크립트 생태계에서 웹 개발의 새로운 방향으로 나아가게 된 몇 가지 핵심 요소를 이해해 보겠습니다.

- 하나의 언어로 모든 것을 다루기

웹의 공용어로서, 자바스크립트만큼 보편적인 언어는 없습니다.

Node.js가 등장했을 때 우리는 클라이언트와 서버 모두에서 실행되는 동형(isomorphic) 코드를 작성할 수 있었습니다. Meteor와 같은 선구적인 초기 풀스택 프레임워크는 이러한 기능을 적극적으로 수용했습니다.

Meteor는 브라우저와 서버가 매우 다른 환경이라는 사실을 추상화하여 풀스택 반응성과 RPC를 갖춘 동형 자바스크립트 프레임워크의 초기 예시였습니다.

당시 이 올인원 접근 방식은 최소한의 뷰 라이브러리인 리액트와 같이 개입이 적고 유연한 접근 방식에 밀려 업계에서 점유율을 잃었습니다.

그 이후로 타입스크립트는 엄청난 영향력을 발휘하여 많은 개발자와 조직에서 사실상 표준으로 자리 잡았습니다.

tPRCT3 스택과 같은 도구는 동형 타입스크립트를 사용하여 코드, 타입, 스키마 및 실행 모델을 동일한 리포지토리에 통합함으로써 엔드투엔드 타입 안전성을 제공합니다.

- 차세대 컴파일러 및 번들러

컴파일러는 작성한 코드를 나중에 실행할 수 있도록 변환, 준비 및 최적화하는 프로그램으로 생각할 수 있습니다. 대규모 프런트엔드 프로젝트를 빌드하고 제공하기에서 웹에서 이러한 컴파일러가 어떻게 작동하는지를 다룹니다.

번들러와 컴파일러 기술이 꾸준히 발전하면서 처음부터 다시 작성된 빠른 차세대 번들러가 반복적으로 개발되어 자바스크립트 모듈 그래프 관리에 매우 능숙해졌습니다.

이러한 기능을 통해 프레임워크는 서로 다른 런타임에 실행되는 클라이언트 및 서버용 모듈 그래프를 분리할 수 있습니다.

이러한 코드 추출 아이디어는 통합된 클라이언트-서버 저작 환경의 원동력이 되며, 백그라운드에서 일어나는 많은 마법과도 같습니다.

- Suspense는 끝났습니다.

Suspense가 제공하는 기능을 이해하는 것은 리액트 프레임워크에서 떠오르는 서버 우선 멘탈 모델을 파악하는 데 핵심입니다. 솔리드, , 프리액트, 아스트로와 같은 다른 프레임워크에서도 이와 유사한 변형이 모색되고 있습니다.

사용자 경험 관점에서의 핵심 인사이트는 데이터 집약적인 경험의 로딩 단계를 보다 의도적으로 설계할 수 있다는 것입니다.

성능 관점에서의 핵심 인사이트는 Suspense가 리소스 로딩과 렌더링의 병렬화를 제공한다는 점입니다.

Facebook의 BigPipe 개념에서 영감을 얻은 이 기능은 브라우저가 유휴 상태인 동안 서버가 데이터를 가져오고 HTML을 렌더링하는 동기식 대기 시간을 줄여줍니다.

대신 클라이언트는 브라우저에서 HTML을 파싱할 때 해당 태그를 발견하면 글꼴, CSS, JS와 같은 리소스를 다운로드하기 시작할 수 있습니다. 이 과정은 서버가 병렬로 데이터를 로드하는 동안 진행됩니다.

이렇게 하면 순수 서버 중심의 ‘페치 후 렌더링’ 모델에서 TTFB(Time to first byte)에 대한 영향을 줄이고 가장 큰 콘텐츠 페인트(Largest Contentful Paint, LCP)의 속도가 느려집니다.

하지만 단순히 <head>를 빠르게 비워서 모든 것을 클라이언트에서 비동기 로딩하는 것과 비교하여, 단계적 로딩 단계를 세밀하게 제어할 수 있습니다. 데이터와 코드가 폭포처럼 로드되면서 페이지 안팎으로 원치 않는 로딩 아이콘(Throbber)이 ‘팝콘처럼’ 확산되어 누적 레이아웃 변화(Cumulative Layout Shift, CLS)를 초래하는 것과는 대조적입니다.

초기 페이지 로딩 외에도 RSC는 직렬화된 가상 DOM을 다운로드 할 수 있게 합니다. 기술적 관점에서 볼 때 얻을 수 있는 인사이트는 Suspense가 렌더링이 비동기식이고 순서가 없이 스트리밍될 때, 일관된 렌더링 문제를 해결한다는 것입니다.

* 더 자세히 알아보기
프레임워크의 반응성 시스템이 해결하는 핵심 문제는 시간이 지남에 따라 데이터가 변경될 때 사용자 인터페이스가 일관되게 렌더링되도록 하는 방법입니다.

일관성(Consistency)이란 표시되는 내용이 현재 소스를 정확하게 반영한다는 의미입니다. 이를 통해 동일한 데이터를 사용하는 다른 요소에서 UI가 다른 데이터를 표시하지 않도록 보장합니다.

가상 DOM과 시그널(signals)은 이를 위한 두 가지 접근 방식입니다. 차이점을 간단히 설명하자면, 가상 DOM은 “뷰를 변경”하므로 대략적으로 작동하는 반면, 시그널은 “모델을 변경”하므로 세분화되어 있다는 것입니다. 각 접근 방식에는 서로 다른 장단점이 있습니다.

Suspense는 네트워크를 통해 컴포넌트 트리가 로드될 때 리소스가 I/O 바운드 작업으로 비동기적으로 로드될 때 렌더링 일관성 문제를 다른 각도에서 해결합니다.

즉, 서로 다른 데이터 소스의 응답을 스트리밍할 수 있고, 네트워크를 통해 도착하는 것이 순서에 상관없이 일어날 때 수동으로 경합 조건을 관리하고 플레이스홀더 DOM 콘텐트를 최종 DOM 콘텐트로 교체할 필요가 없습니다.

또한 빌드 타임 컴파일러와 영리하게 결합하여 부분 사전 렌더링과 같은 새로운 렌더링 방법을 만들 수도 있습니다.

- 인프라의 발전

이러한 기능이 자바스크립트 생태계에서 발전하는 동안 웹을 구동하는 클라우드 인프라도 빠르게 진화했습니다.

서버 런타임에서 브라우저로의 스트리밍과 같은 기능을 사용하려면 이를 지원할 수 있는 백엔드 인프라가 필요합니다.

현재 많은 서버 인프라 제공업체가 이러한 기능을 지원하고 있습니다. 서버리스 및 엣지 컴퓨팅의 인기와 함께 엣지 및 서버리스 환경에서 빠르게 시작하고 실행할 수 있도록 구축된 Deno, Bun, txki.js, LLRT와 같은 새로운 런타임이 등장하고 있습니다. 그리고 fetchReadableStream과 같은 웹 표준 API를 구현하고 있습니다. 모든 기본 인프라의 복잡성을 추상화하는 솔루션을 갖춘 ‘프런트엔드 클라우드’ 제공업체가 등장하고 있습니다.

모든 계층에서의 라우팅

“산 정상으로 가는 길은 하나가 아니라는 것을 이해해야 합니다.” - 미야모토 무사시

라우팅은 다양한 계층에 걸쳐 기초적인 역할을 합니다. 인터넷과 웹은 일련의 라우터로 볼 수 있습니다. 라우터는 모든 프레임워크의 중추이기도 합니다. 라우터는 빌드 시 컴파일러의 첫 번째 진입점이자 초기 요청의 시작점이며 이후 많은 사용자 상호 작용의 목적지입니다.

URL(및 QR코드)의 편의성과 공유 가능성은 소프트웨어 배포 메커니즘으로서 웹의 성공에 있어 기본이 됩니다. 라우터는 로드해야 하는 코드와 데이터에 대한 URL을 연결하는 연결고리입니다.

라우터를 URL의 상태 관리자로, 라우트를 애플리케이션 내에서 공유 가능한 대상으로 생각하는 것도 한 가지 방법입니다. URL은 표시할 레이아웃과 로드해야 할 코드 및 데이터에 대한 기본 입력이므로 라우터는 필수적인 작업입니다.

라우터는 데이터 페칭 및 캐싱, 변경 및 재검증과 같은 중요한 작업과 연결되어 있습니다. 따라서 애플리케이션 라우터의 위치와 작업 수행 위치는 프런트엔드 아키텍처의 기본입니다.

클라이언트 vs 서버

기존의 서버 중심 접근 방식에서는 라우터가 요청을 URL에 매핑하여 데이터를 가져오고 HTML 템플릿을 렌더링합니다. URL 간 전환 시 새 문서가 생성되며 브라우저를 새로 고침해야 합니다.

클라이언트 중심 접근 방식에서는 라우터의 코드를 브라우저에 다운로드되어, 모든 것이 부트스트랩된 후 링크 클릭 및 뒤로/앞으로 탐색 이벤트와 같은 브라우저 히스토리 변경을 수신하기 시작합니다.

여기에서 새로운 문서를 요청하는 대신 URL에 대한 변경 사항을 기존 문서에 다시 렌더링하는 클라이언트 컴포넌트 코드에 매핑합니다.

클라이언트 측 라우팅은 SPA 아키텍처의 핵심입니다. 라우팅 전환은 클라이언트의 현재 상태를 유지하므로 기존 JS, CSS 및 기타 리소스를 다시 평가할 필요가 없습니다. 전환이 이루어지기 전에 코드와 데이터를 미리 로드할 수 있습니다. 또한 라우팅 전환 사이에 애니메이션과 같은 경험을 제공할 수 있습니다(이제 유사한 UX 패턴이 플랫폼에 내장되어 있습니다).

대부분의 메타 프레임워크는 클라이언트 측 라우팅의 전반적인 기능을 유지하면서 서버 중심 애플리케이션 상태를 조합하여 제공합니다. 클라이언트와 서버의 구분은 Qwik과 RSC 아키텍처가 취하는 접근 방식에 따라 희미해지기 시작합니다.

워터폴(waterfall)의 역사

한동안 동적 클라이언트 측 라우팅은 일반적인 패턴이었습니다. 즉, 라우터가 트리의 어느 곳에서나 라우트를 컴포넌트로 렌더링하는 방식이었습니다.

이 설계는 런타임에 <Redirect /> 컴포넌트 렌더링과 같이 매우 유연하고 동적인 기능을 제공할 수 있습니다. 하지만 어떤 코드와 데이터를 로드할지 알기 위해서는 컴포넌트 트리를 렌더링하여 경로를 결정해야 합니다.

실제로 이 패턴을 사용하는 많은 클라이언트 중심 컴포넌트 아키텍처는 상당한 양의 클라이언트-서버 네트워크 워터폴에 부딪히게 됩니다.

워터폴은 일련의 순차적인 네트워크 요청으로, 각 요청은 이전 요청의 완료 여부에 의존적입니다. 워터폴은 네트워크 탭에 숨어 있는 조용한 성능 저하 요인입니다.

메타 프레임워크 라우터는 정적으로 정의된 라우트 정의에 수렴합니다.

파일 시스템 기반 라우팅은 폴더 구조를 URL에 매핑한 다음, URL을 해당 폴더의 특정 파일에 매핑하는 직관적인 방법입니다. 여기서 컴파일러는 파일 시스템을 탐색하여 라우트를 생성합니다.

구성 파일에서 모든 라우트를 정의하는 방법은 간단하고 타입 안전성을 갖춘 또 다른 접근 방식입니다.

이러한 라우트 정의는 자연스럽게 계층적 트리 구조를 형성합니다. 대부분의 라우터는 URL 세그먼트를 해당 구성 요소 하위 트리에 매핑하는 중첩된 라우트 트리를 생성합니다. 이것이 왜 중요한지, 그리고 URL을 활용하는 것이 많은 서버 우선 데이터 로딩 패턴에서 어떻게 핵심적인지 다음에서 살펴보겠습니다.

프런트엔드의 새로운 백엔드

URL을 컴포넌트 트리에 매핑한 후에는 코드와 데이터를 로드해야 합니다. 지금까지 살펴본 것처럼 메타 프레임워크의 큰 주제는 컴포넌트 기반의 풍부한 클라이언트 측 접근 방식의 기능을 포기하지 않으면서 서버 중심 애플리케이션 상태의 성능 이점을 조화시키는 것입니다.

코로케이션은 컴포넌트 모델의 큰 부분이며 쉽게 구성할 수 있는 기능입니다. 여기에는 자체 데이터 종속성을 관리하는 컴포넌트의 이식성, 즉 자체 데이터를 가져오는 방식에는 트레이드 오프가 있습니다. 하지만 구성 시 불필요한 워터폴이 발생할 위험이 있습니다. 반면에 페칭을 통해 프롭(또는 프로미스)으로 데이터를 받는 것은 라우트 수준까지 끌어올려집니다.

앞서 Relay가 뛰어난 기능을 갖춘 ‘프런트엔드 뒤쪽’ 클라이언트 측 라이브러리의 예라고 말씀드렸습니다. 컴포넌트와의 데이터의 코로케이션을 허용하지만, 페칭 기능을 사용합니다. 이 기능은 복잡성과 번들 크기 비용이 발생하며 GraphQL이 필요합니다. 클라이언트 페칭 라이브러리를 번들로 제공하거나 GraphQL을 사용하지 않고 서버로 이동할 때 이러한 장단점을 어떻게 해결할 수 있는지 알아봅시다.

영원한 베스트 프렌드

프런트엔드를 위한 백엔드(Backend For Frontend, BFF)는 서비스 지향 백엔드 환경에서 익숙한 디자인 패턴입니다.

기본 아이디어는 맞춤형 백엔드 서비스가 각 클라이언트 플랫폼(웹, 모바일, CLI 등) 바로 뒤에 위치하여 해당 프런트엔드 애플리케이션의 특정 요구 사항을 충족한다는 것입니다.

예를 들어 HTMX와 같은 서버 중심 접근 방식을 사용하면 백엔드는 AJAX 스타일 업데이트를 수행하는 얇은 클라이언트에게 HTML 부분으로 응답합니다.

RSC의 경우, 환경에 따라 직렬화된 컴포넌트 트리를 얇은 클라이언트 또는 다이어트 중인 두꺼운 클라이언트에 반환하는 맞춤형 백엔드입니다.

서버에서 이 계층을 실행할 때와 비교하여 몇 가지 이점을 이해해 보겠습니다.

  • 클라이언트 번들을 간소화하기 위해, 날짜 서식, 국제화 등과 같은 무거운 변환 라이브러리와 함께 브라우저로 전송되는 코드의 토큰 또는 비밀을 포함하여 대부분의 데이터 페칭 및 데이터 변환 로직을 서버에 유지할 수 있습니다.
  • 여러 데이터 요구 사항을 구성하고 데이터를 정리하여 과도한 페칭을 방지하세요. Suspense에서 살펴본 것처럼 느린 API 호출은 렌더링을 차단하지 않고 Suspense 경계로 스트리밍할 수 있습니다.
  • 제품 개발자가 경험에 대한 정확한 데이터 요구 사항을 지정할 수 있도록 하여 GraphQL 솔루션과 유사한 DX를 프런트엔드 개발자에게 제공합니다.
  • URL 상태 활용 - 반응형 시스템에서 상태 관리의 황금률은 최소한의 상태 표현을 저장하고 이를 사용하여 추가 상태를 도출하는 것입니다.

    우리는 이 원칙을 URL에 적용할 수 있습니다. 여기서 개별 URL 세그먼트는 컴포넌트 서브 트리와 그 내부의 컴포넌트의 현재 상태에 매핑됩니다. 예를 들어, 쿼리 매개변수는 현재 검색 필터 또는 현재 선택된 옵션에 매핑될 수 있습니다.

    성능 관점에서 이러한 방식으로 상태를 관리하면 이 애플리케이션 계층이 데이터가 있는 서버와 가까운 곳에서 모든 코드와 데이터를 미리 가져올 수 있습니다.

    따라서 대부분의 경우 클라이언트에서 실행할 때쯤이면 클라이언트에서 서버로 다시 요청할 필요 없이 필요한 모든 정보를 미리 확보할 수 있습니다. 이는 초기 로딩과 이후 전환에 모두 유리한 위치입니다.

    또한 URL을 통한 공유 가능한 링크를 통해 표시되는 콘텐츠의 일관성을 유지하고 공유 시 다른 사람들도 선택한 필터, 오픈 모델 등을 볼 수 있도록 하는 배포 메커니즘으로서 웹의 힘을 최대한 활용하고 있다는 의미이기도 합니다.

    이를 염두에 두고 코드나 데이터를 미리 가져올 수 있는 경우, URL은 특정 종류의 클라이언트 상태를 저장하기에 좋은 장소입니다.

캐시가 모든 것을 지배합니다

이러한 계층을 클라이언트 밖으로 옮기면 더 많은 작업을 수행하고 서버에 더 많은 캐시를 저장할 수 있습니다. 성능의 기본 원칙은 덜 하는 것입니다. 이를 위한 한 가지 방법은 가능한 한 많은 작업을 미리 수행하고 그 결과를 캐시에 저장하는 것입니다.

캐시에는 여러 유형이 있으며 캐시 내에는 더 깊은 계층이 있으므로 높은 수준에서 이해하는 것이 중요합니다.

퍼블릭 캐시는 데이터가 민감하거나 개인화되지 않은 작업의 결과를 저장합니다. 예를 들어 서버 빌드의 HTML 출력이 캐시되는 퍼블릭 CDN을 들 수 있습니다.

프라이빗 캐시는 개별 사용자(또는 별도의 사용자 그룹)만 액세스할 수 있습니다. 예를 들어 원격 데이터의 클라이언트 측 인메모리 캐시 또는 브라우저의 네이티브 HTTP 캐시를 들 수 있습니다.

모든 시스템에서 복잡성의 주요 원인 중 하나는 상태 관리입니다. 프런트엔드에서는 프런트엔드와 상호작용하는 원격 데이터와의 동기화를 관리하는데, 이는 사실상 일종의 캐시 관리입니다.

새로운 원격 데이터 캐시

앞서 살펴본 것처럼 브라우저에 인메모리 캐시를 뷰의 데이터 출처로 사용하면 빠른 상호 작용을 위한 낙관적인 쓰기를 가능하게 합니다. 캐시가 있을 때마다 캐시가 어떻게 무효화되는지 이해해야 합니다. 클라이언트 측 캐시와 상호 작용하는 다양한 방법을 살펴보겠습니다.

  • 수동 캐시 관리: 여기에는 Redux와 같은 상태 관리 도구를 사용하여 정규화된 캐시를 수동으로 관리하는 것이 포함됩니다. 응답이 돌아오면 다시 업데이트하는 낙관적 업데이트를 위해 필수적으로 직접 캐시를 업데이트해야 합니다.
  • 키 기반 무효화는 수동 관리의 필요성을 제거합니다. 이를 위한 최고의 도구의 예로는 까다로운 캐시 관리 문제를 처리하는 React Query가 있습니다. Apollo나 Relay는 모든 것이 내부에서 처리된다는 점에서 비슷한 접근 방식을 취합니다.

이 계층을 서버로 옮긴다는 것은 뷰(view)의 기본 소스를 옮긴다는 의미입니다. 클라이언트 모델에서 캐시 관리가 어떻게 수행되는지 살펴보았으니 이제 서버 우선 모델에서 캐시 관리가 어떻게 수행되는지 이해해 보겠습니다.

캐시 무효화 및 서버 액션

“전통적인” 요청-응답 모델에서는 업데이트 후 브라우저가 새 문서를 렌더링해야 하므로 업데이트 서버 상태의 쓰기가 탐색과 연관됩니다. 일반적인 패턴은 POST, redirect, GET 요청 흐름입니다.

<!-- 브라우저는 "action"에 전달된 URL로 양식 데이터를 전송합니다. -->
<form action="form_action.php" method="post">
  <!-- 필드 -->
</form>

대부분의 프레임워크는 이 패턴을 기본으로 쓰기 작업을 수행하는 것으로 수렴합니다. 이렇게 하면 SPA(PESPA)를 점진적으로 향상되기 쉽도록 만듭니다.

폼의 액션(action) 속성은 브라우저에서 전송한 폼 데이터를 수신하는 엔드포인트의 URL을 사용합니다. Remix 및 Sveltekit과 같은 프레임워크는 폼 데이터를 라우팅 수준 서버 액션으로 전송합니다. 반면 Next와 SolidStart는 컴포넌트 트리의 어느 곳에서나 서버 액션을 호출할 수 있으므로 RPC와 더 유사합니다.

서버 상태(데이터베이스 및 모든 서버 캐시)에 쓰기가 완료되면 클라이언트 프레임워크는 새 문서를 반환하는 대신 반응성 시스템을 사용하여 응답을 다르게 하고 페이지를 그 자리에서 업데이트합니다.

단순한 데이터 대신 뷰로 인코딩된 데이터를 반환할 때 얻을 수 있는 한 가지 이점은 브라우저가 뷰를 업데이트하기 위해 리디렉션을 받은 후 또 다른 GET을 수행할 필요 없이, 단일 서버 왕복으로 업데이트된 UI를 반환할 수 있다는 것입니다. 이는 다음에 살펴보게 될 리액트 서버 컴포넌트가 가진 이점이기도 합니다.

이 접근 방식은 클라이언트 측 캐시를 수동으로 관리하는 것에 비해 훨씬 간단하며 데이터 페칭 라이브러리를 번들링할 필요도 없습니다. 하지만 앞서 살펴본 것처럼 요청-응답 모델은 라우트(또는 중첩된 라우트) 수준에서 대략적인 업데이트를 수행합니다.

이는 대부분의 경험에 적합한 기본값입니다. 하지만 특정 기능의 경우 세분화된 캐시 관리 및 클라이언트 측 데이터 로딩의 이점이 필요할 수 있습니다.

예를 들어 폴링을 하거나 대략적인 요청-응답 흐름이 빌드 중인 내용과 잘 매핑되지 않아, 쓰기 시 서버 컴포넌트나 loader 함수를 다시 실행하지 않으려는 경우 등이 이에 해당합니다.

모듈 그래프에서 어디서든 사용할 수 있는 서버 액션의 장점은 의미 있는 접근 방식을 혼합하여 사용할 수 있다는 것입니다. 예를 들어 서버 액션의 결과로 클라이언트 측 캐시를 하이드레이트할 수 있습니다.

// RPC 스타일 서버 액션을 사용한 클라이언트 페칭 및 캐싱
useQuery({
  queryKey: ['cool-beans'],
  // 프로미스를 반환하는 어떠한 함수
  queryFn: () => myServerActionThatReturnsJson(),
});

이 영역에는 훨씬 더 많은 미묘한 차이가 있으므로 더 많은 시간이 필요합니다. 서버 액션과 결합된 리액트 서버 컴포넌트가 제공하는 몇 가지 새로운 기능과 다른 새로운 기술과의 교차점을 이해하는 것으로 마무리하겠습니다.

다차원 컴포넌트

리액트 서버 컴포넌트는 중요한 패러다임의 전환입니다. 초기 단계에서는 개념화하는 방법이 다양하기 때문에 따라가기 어려웠습니다.

아일랜드 아키텍처의 관점에서 보면, Nuxt나 Deno의 Fresh와 같은 다른 프레임워크 생태계에서도 리액트와는 다른 다양한 형태의 서버 컴포넌트가 탐구되고 있습니다.

리액트의 모든 트레이드 오프는 컴포넌트 모델과 그와 함께 제공되는 컴포넌트의 구성 능력을 보존하기 위한 것에서 발생합니다. 아키텍처 수준에서 이를 이해하는 또 다른 방법은 컴포넌트화된 BFF로 이해하는 것입니다.

클라이언트의 관점에서 RSC는 정적 빌드 중 또는 클라이언트가 실행되기 전에 서버에서 미리 실행되는 컴포넌트입니다.

간단한 멘탈 모델은 직렬화된 컴포넌트라고 생각하면 됩니다. 컴포넌트의 출력을 직렬화하여 메인 스레드에서 리액트를 실행하는 아이디어는 어느정도 시간이 지난 후에 등장한 것입니다.

이 새로운 기능을 통해 리액트는 다양한 아키텍처 스타일을 표현할 수 있습니다.

미리 구축된 정적 사이트, HTMX 스타일의 AJAX 업데이트를 사용하는 서버 중심 아키텍처, 점진적으로 개선된 SPA, 단일 진입점을 가진 순수하게 클라이언트 렌더링된 SPA 등 다양한 아키텍처를 표현할 수 있습니다. 또는 특정 환경에 따라 동일한 애플리케이션에서 모두 사용할 수도 있습니다. 잠재적인 성능 이점 외에도 이러한 유동적인 아키텍처의 몇 가지 흥미로운 잠재적 이점을 살펴보겠습니다.

  • 네트워크를 통한 합성(Composition)

    서버 컴포넌트는 전체 스택 기능의 공유와 합성 및 새로운 종류의 프런트엔드 작성 경험을 제공합니다.

    이는 조직 내에서 프런트엔드 팀과 백엔드 팀을 분리하는 모델을 풀스택 수직 슬라이스 또는 스틸 스레드로 작업하는 팀에 맞게 조정하는 새로운 방식입니다.

    표준화된 인프라를 갖춘 대규모 조직의 경우, 제품 팀에서 사용하고 합성할 수 있는 풀스택 플랫폼 컴포넌트를 보유하는 것이 타당한 사용 사례입니다. 연합된 모델(federated model)에서 RSC의 출력을 합성하는 기능도 새롭게 떠오르는 또 다른 기능입니다.

    이것이 에코시스템 수준에서 어떻게 작동할지는 명확하지 않지만, 컴포넌트 API 디자인에 흥미로운 변화를 가져올 것은 분명합니다. 예를 들어, 패키지는 서버 워터폴을 피하기 위해 라우트 수준까지 끌어올릴 수 있는 프리로드 함수를 내보낼 수도 있습니다. 이것은 새로운 패러다임이기 때문에 아직 많은 모범 사례를 탐구해야 하며, 함정을 찾아야 합니다.

  • 서버 기반 UI

    이 개념은 AirBnbUber와 같은 일부 대형 조직에서 네이티브 모바일 프런트엔드의 서버 기반 렌더링을 보다 세밀하게 제어하기 위해 활용하고 있습니다.

    react-strict-dom의 도입은 리액트 네이티브와 RSC를 조합하여 이러한 아이디어를 웹을 넘어 AR 및 공간 사용자 인터페이스와 같은 새로운 플랫폼을 포함한 플랫폼 전반에 보다 쉽게 활용할 수 있게 합니다.

  • 생성형 AI UI

    생성형 AI의 미래가 이 분야에서 어떻게 전개될지 예측하기는 어렵습니다. 하지만 이 기술은 앞으로 계속 발전할 것입니다. 이 모델에서 새롭게 떠오르는 기능은 고도로 개인화되고 풍부한 인터렉티브 경험을 실시간으로 생성하는 기능입니다.

    보다 현실적인 예로 렌더링할 컴포넌트를 파악하기 전에 데이터가 필요한 상황을 들 수 있습니다. 이 경우 여러 가지 유형의 인터랙티브 컴포넌트를 미리 번들로 묶어야 합니다. 이런 경우 UI 컴포넌트의 수는 무한정 늘어날 수 있기 때문에(예: CMS 콘텐츠 유형), 이러한 유형의 동적 렌더링은 모든 코드를 클라이언트로 보내야 하거나 클라이언트에서 여러 컴포넌트 유형을 지연 로드할 때 지연이 발생할 수 있습니다.

    컴포넌트를 엔드투엔드로 사용하면 대규모 번들을 늘리지 않고도 컴포넌트를 스트리밍할 수 있음을 의미합니다. 여기서 흥미로운 탐구AI 함수 호출을 사용하여 서버 액션의 유연성과 함께 직렬화된 인터렉티브 컴포넌트를 반환하는 것입니다.

프런트엔드의 미래

이 글에서는 웹 애플리케이션 프레임워크의 기본 레이어 중 일부에 대해서만 다루었습니다. WebAssemblyWebGPU와 같은 기술이 예상치 못한 방식으로 작용할 수 있다는 것은 말할 것도 없고, 대형 자바스크립트 프레임워크 외부의 다른 생태계에서 상태를 가진(stateful) 서버 접근 방식이나 로컬 우선 개발의 등장과 함께 트레이드오프가 있을 수 있습니다.

이러한 모든 기술의 최전선에 있다는 것은 매우 흥미로운 일입니다. 하지만 압도당하기 쉽기도 합니다.

개발에 필수적인 기술은 문제의 내재적 복잡성과 그 문제에 대한 해결책에서 발생하는 부수적인 복잡성을 식별하는 것입니다. 프런트엔드 신입들에게는 기본적이고 변하지 않는 개념에 초점을 두는 것을 의미합니다.

엔지니어링(그리고 인생)의 큰 부분은 결정을 내리고 한 방향으로 나아가는 것입니다. 사용자와 팀의 요구 사항에 대해 더 많이 알수록 더 나은 절충안을 도출할 수 있고 결정에 대한 확신을 가질 수 있습니다.

기술의 발전을 이해하는 데 있어 중요한 인사이트는 기술의 발전이 항상 모든 것에 대한 진보를 의미하지는 않는다는 것입니다.

혁신의 한 단계에 있는 기능이나 접근 방식이 여러분이 만들고자 하는 것에 딱 맞을 수 있으며, 그렇다면 계속 구축해 나가며, 행운이 여러분과 함께 하기를 바랍니다.

참조 및 리소스


🚀 한국어로 된 프런트엔드 아티클을 빠르게 받아보고 싶다면 Korean FE Article(https://kofearticle.substack.com/)을 구독해주세요!


Written by@[Ykss]
고이게 두지 않고 흘려보내는 개발자가 되자.

GitHubInstagramLinkedIn