파란 바 없이

Without the Blue Bar 블로그 포스트 번역


어느 날 Jared Palmer일련의 트윗에서 GitHub를 개선하기 위해 커뮤니티가 할 수 있는 일이 무엇인지 묻는 것을 보고 완전히 빠져들었다.
나는 GitHub를 사랑하지만, 가장 큰 우려는 때때로 얼마나 느려질 수 있다는 것이다. 그 트윗에 대한 답글들을 보면 나 혼자만 이렇게 생각하는 게 아니라는 것이 분명하다.
사용자로서 GitHub 경험에 대한 나의 필수 요소들을 생각해보기 시작했다:
  • 콘텐츠가 크든 작든 경험이 빨랐으면 좋겠다.
  • 브라우저의 기본 검색을 사용하고 싶다. 나는 그것에 계속 의존하고 있기 때문이다.
첫 번째 포인트는 명백하다.
두 번째는 더 흥미롭다. 많은 성능 기법들이 뷰를 가상화하는 데 의존하지만, 주의할 점은 기본 브라우저 검색은 실제로 DOM에 존재하고 브라우저에 보이는(또는 최소한 "존재하는") 콘텐츠에서만 작동한다는 것이다. 그것이 정말로 거기에 없다면, Cmd+F는 그것을 찾지 못할 것이다.

휴대폰: 친숙한 예시

우리가 매일 상호작용하는 친숙한 것을 사용해보겠다: 휴대폰. 이 개념은 훨씬 전부터 존재했지만, iPhone이 나올 때까지 나는 정말로 그것이 어떻게 작동하는지, 왜 그렇게 부드러운 느낌이 드는지에 주목하기 시작했다.
컴퓨팅에서 그리기(Painting)는 비용이 많이 든다. 특히 리소스가 제한되어 있을 때 더욱 그렇다. 최신 Mac을 가지고 있을 수도 있지만, 브라우저는 여전히 단일 탭이 얻을 수 있는 리소스의 양을 제한한다. 따라서 강력한 머신도 한계에 도달한다.
그리고 휴대폰? 훨씬 더 제약이 많다.
첫 번째 iPhone이 출시되었을 때, 하드웨어 제약은 더욱 심했다. 좋은 경험을 제공하기 위해 Apple은 한 번에 거대한 UI 표면을 그릴 여유가 없었다. 그것이 UITableView가 나온 이유다.
휴대폰 예시로 돌아가자: 만약 당신이 만 개의 연락처를 가지고 있다면 어떨까? 모든 연락처를 그리는 것은 시간이 걸리고, 메모리를 낭비하고, 사용자 경험을 망칠 것이다. 대신 시스템은 화면을 채우는 단일 컨테이너를 그린 다음 제한된 수의 "셀"을 만든다. 이를 간단하게 유지하기 위해 10개의 보이는 셀과 위아래로 버퍼로 3개 정도를 더한다고 하자.
virtual list

12번 연락처로 스크롤 다운하면, 그 셀은 이미 그려져 있다. 1번 연락처는 화면 밖으로 스크롤되었고, 계속 렌더링할 이유가 없으므로 시스템은 이제 보이지 않는 셀을 뷰포트에 들어올 다음 연락처를 위해 재사용한다.
이런 식으로 셀을 재활용함으로써 OS는 거대한 목록을 시뮬레이션하면서 실제로는 약 16개의 요소만 렌더링한다.
이 패턴은 어디에나 있다: 타임라인, 채팅 앱, 피드 - 기본적으로 기기를 녹이지 않으면서 "무한"처럼 느껴지고 싶은 모든 것. GitHub의 일부도 정확히 이렇게 작동한다. 이슈나 커밋을 스크롤할 때 보통 이렇게 생각하지는 않지만 말이다.

GitHub의 도전 과제

GitHub는 기본 브라우저 검색을 보존하면서도 여전히 무거운 뷰를 가상화하고 싶어한다.
그들은 이미 큰 파일 blob에 대해 이를 수행하고 있다: 파일의 보이는 부분은 구문 강조되고 가상화되며, 그 위에 원본 코드를 포함하는 전체 높이의 투명한 <textarea>를 배치한다. 브라우저는 textarea를 검색하고, 사용자는 강조된 코드를 보며, 두 레이어 모두 완벽하게 동기화된 상태로 유지된다.
image

Pull Request에 관해서는, 가상화가 그렇게 많이 도움이 되지 않는다. 최소한 내가 diff 컴포넌트라고 부르는 것(파일 패치를 한 개 또는 두 개의 열로 보여주는 UI)에는 말이다.
이 컴포넌트들은 한 번에 약 100줄 이상을 렌더링하는 경우가 드물므로, 가상화하면 실제로 큰 PR에서 도움이 되기보다는 성능을 해칠 수 있다. 오버헤드가 가치가 없다.
그렇다면 GitHub가 PR 경험을 개선하기 위해 현실적으로 어떤 옵션이 있을까?
  1. 전체 PR diff를 한 번에 렌더링한다.(아마도 몇 년 전 GitHub의 원래 접근 방식)
  1. JavaScript를 통해 diff 컴포넌트를 지연 로드하고 스케줄한다.(클래식 UI와 새로운 UI 모두 이것의 버전을 수행한다)
  1. 빠른 비-DOM 그리기 레이어를 사용하여 diff를 렌더링한다.(Canvas/WebGPU. 하지만 이것은 브라우저 검색을 깨뜨리므로 GitHub는 검색, 강조, 전체 렌더링 스택을 다시 발명해야 한다.)
결국 GitHub는 아마도 옵션 3 같은 것이 필요할 것이다. 하지만 거기에 도달하려면 브라우저의 기본 검색을 능가하고, 코드를 인덱싱 가능하게 유지하면서(SEO와 AI를 위해) 커스텀 렌더러에서 구문 강조를 구현해야 한다.
그것은 긴 길이다.

파란 바

그리고 파란 진행 표시줄도 있다. 이 포스트의 제목이 바로 그것이다.
그것은 정말로 기능이 아니다. 그것은 증상이다. 라우터가 비용이 많이 드는 작업을 하느라 멈춰 있을 때 나타난다: 데이터 가져오기, 청크 로드, 로직 실행, 그리기. 당신이 그것을 더 자주 볼수록, 당신의 UI가 사용자를 불필요하게 차단하고 있다는 뜻이다.
목표는 바를 빠르게 하는 것이 아니다. 목표는 그것이 필요할 필요를 완전히 제거하는 것이다.
그래서 내가 완전히 빠져들었을 때, 가장 먼저 떠오른 아이디어는 옵션 1과 2의 혼합이었다. 하지만 한 가지 반전이 있었다:
  1. CSS와 JS를 위한 Baseline 2024
  1. JavaScript를 피한다 (꼭 필요한 경우 제외)
  1. 스트리밍할 수 있는 모든 것을 스트리밍한다
  1. 사용 가능한 가장 빠른 프레임워크를 사용한다
  1. 브라우저의 메인 스레드를 보호한다
#1과 #2에 빠르게 동의할 것이다.
#3에 도달할 때쯤이면 이미 #4에 대해 궁금해하고 있을 것이다. 그래서 방 안의 코끼리를 다루자.

React와 Next.js 선택

그것은 가장 빠른 것 중 하나이자 가장 검증된 UI 런타임 중 하나로 구축되었다. GitHub가 오늘날 내부적으로 사용하는 것과 동일한 것이다: React. 당신은 "가장 빠른"에 동의하지 않을 수도 있고, 그것은 공정하다. 하지만 여기 논쟁하기 더 어려운 것이 있다: React는 가장 큰 생태계, 가장 많은 문서, 그리고 가장 큰 개발자 풀을 가지고 있다.
Facebook이 그렇게 많은 해 동안 PHP를 고수한 이유가 있다. 보편성은 초강력이다.
"하지만 React는 다른 프레임워크에서도 사용된다…"
맞다. 하지만 모든 React 기반 프레임워크 중에서 Next.js는 React의 현재 및 미래 방향과 가장 잘 맞는 것이다. 그것을 선택하면 즉시 내 항목 #2와 #3을 확인한다 (스트리밍 + 서버 우선 사고방식).
그리고 그 우려를 제거함으로써, 나는 마침내 방 안의 다음 코끼리를 다룰 수 있다.

서버 컴포넌트

대부분의 렌더링과 로직을 서버로 이동할 수 있다면, 사용자 경험이 즉시 개선된다. 메인 스레드를 해제하고 클라이언트 측 JavaScript가 실제로 필요한 순간을 위해 예약한다.
RSC(React Server Component)는 모두의 취향은 아니다. 나는 프로덕션에서 훌륭한 결과로 그것을 사용했고, 내가 보는 대부분의 반발은 그것을 전통적인 SPA처럼 사용하려고 시도하는 것에서 온다. 보통 오래된 패턴, 오래된 React 버전, 또는 이 모델을 위해 설계되지 않은 라이브러리를 사용한다.
그것은 패러다임 시프트다. 마치 예전에 서버 측 렌더링이 그랬던 것처럼 (그리고 사람들은 여전히 그것에 대해 싸운다).
일부 프로젝트의 경우, 일반 SPA가 절대적으로 더 잘 수행할 것이다. 그것은 괜찮다. 하지만 이 POC(개념 증명)의 경우, RSC는 내가 원했던 것과 완벽하게 맞는다: 스트리밍, 서버 주도 UI, 그리고 불필요한 작업으로 질식하지 않는 메인 스레드.
서버 컴포넌트를 먼저 생각하는 것은 우리 개발자들에게 언제 뭔가가 정말로 클라이언트 컴포넌트여야 하는지 정말로 질문하도록 강요한다.
그것만으로도 사용자에게 엄청난 승리다. 모든 클라이언트 컴포넌트는 브라우저가 해야 할 추가 작업이기 때문이다. 대부분의 경우, 정직한 답변은: "직렬화될 수 없는 경우에만."
그것이 또한 기존 SPA를 RSC로 포팅하는 것이 어려운 이유다. 그것은 PHP나 다른 전통적인 서버 측 시스템이 아니지만, 클래식 SPA도 아니다. 그것은 하이브리드다. 두 세계의 최고의 아이디어 중 일부를 가져가고, 스트리밍을 추가한 다음, JavaScript가 실제로 어디에 속하는지 신중하게 생각하도록 요청한다.
Next.js는 완벽하지 않고, 나는 별도의 포스트에서 그 문제들을 다룰 것이지만, 이런 종류의 실험을 위해 RSC는 내가 필요한 정확한 기초를 제공했다.

content-visibility와 contain-intrinsic-size

최신 Baseline의 가장 멋진 추가 사항 중 하나는 content-visibility 속성이다. 엄격한 의미에서 가상화는 아니지만, 전체 가상 목록 구현을 요구하지 않고도 비슷한 성능 이점을 제공한다.
"만 개의 연락처" 예시로 돌아가자: content-visibility는 브라우저에 다음과 같이 말한다:
"이 서브트리가 실제로 화면에 보이지 않는 한 그리기를 신경 쓰지 마."
요소는 여전히 DOM에 존재하고 레이아웃 풋프린트는 보존되지만, 브라우저는 필요할 때까지 모든 무거운 렌더링 작업을 건너뛴다.
그것은 실제 가상화기만큼 강력하지는 않다. 요소 재사용이 없고, 모든 것이 여전히 메모리에 존재한다. 하지만 성능 향상은 상당할 수 있다. 그리고 진정한 가상화와 달리, 화면 밖의 콘텐츠는 여전히 기본 브라우저 찾기 도구로 검색 가능하다. 이것은 GitHub 같은 것에 중요하다.
content-visibility

물론 여전히 최적이 아니다. 왜냐하면 그 모든 연락처가 여전히 DOM에 존재해야 하기 때문이다. 하지만 스트리밍 덕분에 우리는 그것들을 점진적으로 삽입하고 React의 <Suspense>를 통해 폴백 뷰를 표시할 수 있다.
이것은 메인 스레드를 자유롭게 유지하고 "모든 것이 한 번에 렌더링된다" 병목을 피한다.
그렇다면 왜 content-visibility를 모든 곳에 적용하지 않을까?

청킹의 중요성

브라우저가 콘텐츠의 전체 블록을 그려야 하는지 결정하는 것이 수십 개 또는 수백 개의 작은 개별 요소를 하나하나 평가하는 것보다 훨씬 쉽다. 청킹은 예측 가능한 동작과 더 나은 성능을 제공한다.

레이아웃 시프트 방지

브라우저가 요소를 아직 그리지 않았다면, 그것의 치수를 모른다. content-visibility를 무분별하게 적용하면 사용자가 스크롤할 때 브라우저가 갑자기 요소의 실제 높이를 발견하면서 예상치 못한 레이아웃 점프(CLS)가 발생할 수 있다.
다행히 Baseline 2023은 contain-intrinsic-size를 도입했다. 이것은 우리가 미리 "가짜" 높이/너비를 제공할 수 있게 해준다. 기본적으로 브라우저에 다음과 같이 말한다:
"실제로 그릴 때까지 이 블록이 X 높이라고 가정해."
이것은 content-visibility의 렌더링 절감을 제공하면서도 레이아웃 시프트를 제거한다.

GitHub의 설계 시스템 분석

나는 매일 GitHub를 사용하지만, 지금까지 정말로 그 DOM을 파고들지는 않았다.
많은 해 동안 GitHub는 jQuery와 서버 측 템플릿에 의존하여 페이지를 렌더링했다. 제품이 성장하면서 그 접근 방식은 확장하기 어려워졌고, React는 UI를 단순화하고, 일관성을 강제하고, 적절한 설계 시스템을 강화하는 자연스러운 방법이 되었다.
그 설계 시스템을 Primer라고 부르며, 두 가지 주요 부분으로 나뉜다:
  • Primitives (인터페이스 전체에서 사용되는 공개 및 비공개 컴포넌트)
  • Octicons (아이콘 라이브러리)
두 라이브러리 모두 아름답게 제작되었고 매우 잘 문서화되어 있다. 하지만 내가 구축하고 싶었던 것을 위해서는 그것들을 직접 재사용할 수 없었다. 그것들은 클라이언트 측 정신 모델로 설계되었지, 서버 컴포넌트 모델이 아니었다.
React 19는 클라이언트 컴포넌트를 가져와서 가능한 곳에서 서버를 통해 제공할 수 있을 정도로 똑똑하지만, Primer Primitives는 훅을 많이 사용한다. 심지어 간단한 DOM 요소를 기대할 곳에서도 말이다. 이것은 그것들이 클라이언트 컴포넌트로 컴파일되도록 강제하며, 이것은 서버 우선 아키텍처와 맞지 않는다.
GitHub가 설계 시스템을 성숙시키면서, 그들은 설계 토큰을 표준화했고 UI 전체에서 CSS 변수를 사용하여 간격, 색상, 타이포그래피를 일관되게 유지한다. 이름 지정은 소프트웨어에서 가장 어려운 문제 중 하나로 유명하므로, 그들은 또한 토큰이 코드에서 어떻게 참조되어야 하는지에 대한 명확한 규칙을 정의했다. 이것은 시스템을 확장하고 유지하기 쉽게 만든다.
image

그들은 Primitives를 위해 CSS Modules를 선택했다. 캡슐화와 재사용성을 선호하기 위해서다. 나는 이 포스트의 뒷부분에서 이것으로 돌아올 것이다.
내가 발견한 그렇게 예쁘지 않은 것 - 그리고 그것은 또한 빠르게 반복하고 클라이언트 측 관점에서 생각하는 증상이다 - 반응성이 항상 CSS에서 완전히 처리되지는 않는다는 것이다. 대신, 일부 컴포넌트는 React 훅을 사용하여 브라우저 이벤트나 요소 크기 관찰자를 구독하여 요소를 어떻게 그릴지 결정하는 지름길을 취한다.
설계 시스템이 성장하면서, 완전히 반응형이 되는 것은 선택 사항이 아니라 필수가 된다. 때때로 디자이너가 중단점을 잘못 얻을 수 있고 크기 전체에서 완전히 다른 레이아웃을 제시할 수 있다. 그런 경우, 유일한 대안은 모바일에서만 렌더링되는 요소와 데스크톱에서만 렌더링되는 요소를 가지는 것이다.
하지만 내가 검토한 컴포넌트에서는 그런 경우가 자주 없었다.
image

당신은 완전한 반응성이 CSS 수준에서 처리될 것으로 기대할 것이다. 하지만 일부 컴포넌트는 중단점 특정 DOM 복제에 의존한다.
다른 것들은 React 훅을 사용하여 요소 크기나 스크롤 위치를 구독한다.
github resize

개별적으로, 이 지름길들은 작다. 하지만 같은 뷰에서 수십 번 재사용되면, 그것들은 누적된다. 특히 크기 조정 중에 눈에 띄는 지연을 도입한다.
당신은 창 크기를 조정할 때 UI가 "밀어낸다"는 것을 말 그대로 느낄 수 있다.

API와 데이터 소스

GitHub를 렌더링하기 위해 플랫폼은 공개 및 비공개 데이터 소스의 혼합에 의존한다. 그것은 내가 사용해본 가장 신중하게 설계된 GraphQL 엔드포인트 중 일부를 가지고 있으며, GraphQL만으로는 커버할 수 없는 작업을 위해 REST API 세트와 쌍을 이룬다. 다시 말해, 공개 및 비공개 모두.
주목할 가치가 있는 것은 GitHub의 UI를 복제하는 데 필요한 모든 데이터를 노출하는 단일 API가 없다는 것이다. GraphQL은 뷰에 필요한 정확한 필드를 가져오는 데 탁월하지만, Markdown을 HTML로 변환하거나 Pull Request에 대한 패치 데이터를 검색하는 것과 같은 작업을 위해서는 여전히 REST가 필요하다.
실제로 GitHub는 보이는 것처럼 통합된 경험을 제공하기 위해 뒤에서 여러 API를 함께 연결한다.
비공개 API가 존재한다는 한 가지 단서는 웹 UI의 작고 즐거운 기능인 단순화된 경로다. 당신의 저장소에 이렇게 깊게 중첩된 디렉토리가 포함되어 있다면:
image

…웹 인터페이스는 그 폴더 계층을 축소하여 파일로 바로 이동할 수 있다:
image

이것은 HTML에 직접 포함된 직렬화된 메타데이터에 의존한다:
image

불행히도, 이 기능은 GitHub 모바일 앱에 존재하지 않는다. 거기서는 폴더 트리의 모든 레이어를 탭해야 한다. 아마도 네이티브 앱이 공개 GraphQL API에만 의존하기 때문일 것이다. 이것은 단순화된 경로를 계산하는 데 필요한 데이터를 노출하지 않는다.
folders


기술 스택 구축

프레임워크가 제자리에 있고 캐시 컴포넌트를 실험하기로 결정한 후, 나는 데이터 가져오기 레이어로 시작했다. 나는 GraphQL을 선택했고 작업을 위해 Relay를 선택했다.
3년 전에 처음 RSC를 채택했을 때, Relay는 서버 컴포넌트를 어느 정도 지원하는 유일한 라이브러리였다. 통합은 거의 문서화되지 않았지만, GitHub 이슈를 파고들어 작동하는 접근 방식을 찾았다.
Relay는 여전히 최고의 DX(개발자 경험) 중 하나를 제공한다: 그 컴파일러는 쿼리와 프래그먼트를 최적화하고, IDE 확장은 훌륭한 자동 완성을 제공한다. 그 워크플로우에 익숙해지면, 돌아가기 어렵다.
이 전체 POC에서 가장 도움이 된 도구는 ChatGPT 5.1이었다. 나는 UI 스크린샷을 붙여넣고 뷰를 복제하는 데 필요한 GraphQL 쿼리를 요청할 수 있었다. 전체 GitHub 스키마를 암기한 누군가와 페어 프로그래밍하는 것처럼 느껴졌다.
스타일링을 위해 나는 Tailwind의 최신 버전을 사용하고 있다. 개인적인 선호도지만, 이 프로젝트에 완벽하게 맞는 것으로 판명되었다. Tailwind v4는 CSS 변수로 구축되어 있어서 GitHub의 기존 설계 토큰을 재사용하기 쉬웠다.
GitHub의 현재 CSS Modules 접근 방식에 비한 또 다른 장점은 Tailwind의 원자적 특성이다: 컴포넌트 라이브러리가 성장하면서, 최종 CSS 번들은 본질적으로 같은 크기로 유지될 수 있다. GitHub의 일관된 간격 척도(4의 배수)와 결합하면, 전체 설계 시스템이 약 20KB 이하로 편안하게 앉을 수 있다.
더 나은 성능을 원한다면, 루트 레이아웃에 인라인할 수 있고 CSS 다운로드를 완전히 건너뛸 수 있다.
설정이 준비되면서, 나는 GitHub의 토큰을 Tailwind로 포팅하기 시작했다. 처음에는 순진하게 모든 것을 마이그레이션하려고 시도했지만, 필요할 때 토큰을 증분적으로 추가하는 것이 더 낫다는 것을 빠르게 깨달았다. 나는 단일 테마(다크 모드)로 시작했다. 추가 테마는 나중에 변수 값을 교환하기만 하면 계층화될 수 있기 때문이다.
설계 기초가 안정화되면서, 다음 단계는 저장소 레이아웃을 완전히 반응형 방식으로 재생성하는 것이었다. 원본 구현에서 일반적인 중단점 특정 표시/숨기기 패턴에 의존하기보다는 말이다.
ezgif-5a6031401fbac4bc

그것이 완료되면, 나는 색상이 있는 자리 표시자 블록을 실제 UI로 교체했다. 나는 Sketch를 정렬 가이드와 함께 사용하여 내 레이아웃을 실제 GitHub 인터페이스와 비교하고 픽셀 완벽한 Tailwind 포트를 향해 밀어붙였다.
image

앞서 언급했듯이, React Server Components를 사용한 내 접근 방식은 간단하다: 정말로 그렇지 않을 이유가 없는 한 서버에서 모든 것을 렌더링한다. 이것은 결국 클라이언트 컴포넌트가 되거나 더 나은 "도넛 컴포넌트"가 될 레이아웃의 모든 부분을 건너뛰는 것을 의미했다.
좋은 예는 파일 타임스탬프다: 서버는 ISO 날짜가 있는 <time> 요소를 렌더링할 수 있고, 클라이언트는 사용자의 시간대를 기반으로 지역화하기 위해 작은 도넛만 수화할 수 있다.
하지만 이 POC의 경우, 나는 빠르게 움직이고 싶었으므로 거의 모든 클라이언트 컴포넌트를 피했고 전체 UI를 서버 렌더링으로 유지했다. blob과 tree 뷰의 스티키 헤더에 있는 "맨 위로 스크롤" 버튼을 제외하고.
나는 또한 클라이언트 측 JavaScript 없이 CSS만으로 완전히 수행할 수 있는 여러 GitHub 상호작용을 포팅했다:


모바일에서 보이는 파일 수를 숨기거나 표시하기. <input> + <label> 트릭만 사용한다.
expand



동일한 순수 CSS 기법으로 커밋 세부 정보를 전환한다.
expand view



스티키 헤더 동작 구현 - 그것이 "고정"될 때 테두리 반경 제거 포함 - JS 대신 오래된 학파 CSS 기법을 사용한다.
expand



GitHub의 버전은 JS를 사용하고 스티키 헤더가 활성화될 때 눈에 띄는 "점프"가 있다.
expand


스켈레톤과 스트리밍

다음 단계는 이미 가지고 있는 뷰에 대한 스켈레톤을 구축하는 것이었다.
나는 간단한 불투명도 전환을 사용하여 그것들을 애니메이션했다. 그래서 그것들은 렌더링 파이프라인의 "합성" 단계만 건드린다. GitHub가 하는 것처럼 mask-image를 애니메이션하는 대신. 그것은 페인트 + 합성을 강제하고 눈에 띄게 더 무겁다.
skeleton opt

목표는 간단했다: 동작의 인식을 유지하고, 가능한 한 많은 렌더링 비용을 제거한다.
스켈레톤이 제자리에 있으면서, 스트리밍을 구현할 시간이었다.
Next.js 16은 캐시 컴포넌트를 도입했다. 이것은 데이터 의존성을 명시적으로 만들고 더 많은 작업을 서버 런타임으로 밀어낸다. 이 플래그가 활성화되면, Next.js는 컴포넌트가 <Suspense>로 래핑되지 않고 비동기 데이터를 읽거나 동기 경계가 부분 렌더링을 차단할 때마다 경고한다.
이것은 중요하다. 왜냐하면 최근 버전부터 Next.js는 의도적으로 대부분의 런타임 데이터를 비동기로 취급하여 더 빠른 스트리밍을 해제하기 때문이다. 캐시 컴포넌트가 활성화되지 않으면, 이 문제들은 조용히 삼켜진다. 기능이 켜져 있으면, 프레임워크는 그것들을 표면화하여 최대 병렬성을 위해 트리를 구조화할 수 있다.
실제로, 그것은 React가 기대하는 방식으로 컴포넌트를 설계하도록 강요한다:
  • 비동기 데이터가 필요할 때 비동기
  • 연기할 수 있는 곳에서 스트리밍 가능
  • 무엇이 차단을 허용하는지에 대해 명시적
모든 것이 정렬되면, 훨씬 더 나은 부분 렌더링과 거의 즉각적인 경로 전환을 얻는다.

대용량 파일 처리

어떤 뷰도 가상화하지 않고 있으므로 - 그것은 클라이언트 측 기법이다 - 나는 content-visibility, contain-intrinsic-size, 그리고 <Suspense>의 조합을 사용하여 blob 뷰에서 그 이점 중 일부를 시뮬레이션하는 다른 접근 방식을 취했다.
GitHub는 오늘날 큰 blob에 대해 비슷한 것을 한다: 강조된 줄만 가상화하고 기본 텍스트 선택과 브라우저 검색을 보존하기 위해 투명한 textarea를 오버레이한다. 나는 그 정신을 유지하고 싶었지만, 서버 스트리밍 환경에 적응시키고 싶었다.
이 POC의 경우, GitHub는 구문 강조 API를 노출하지 않지만, Markdown을 HTML로 변환하는 엔드포인트를 노출한다. 그래서 파이프라인은 다음과 같다:
  1. 원본 blob 텍스트를 가져온다.
  1. 파일 확장자로 백틱으로 감싼다.
  1. HTML 강조 출력을 위해 Markdown API로 보낸다.
  1. 줄로 분할; 각각을 <div>로 감싼다.
  1. content-visibility: auto를 사용하여 줄을 청크로 그룹화한다.
  1. 브라우저가 그리기 전에 공간을 예약할 수 있도록 줄 수를 기반으로 각 청크에 결정론적 contain-intrinsic-size를 제공한다.
  1. <Suspense>를 사용하여 청크가 스트리밍되는 동안 <pre> 폴백을 표시한다.
결과는 작은 파일에서는 감지할 수 없다. 정확히 당신이 원하는 것이다. 하지만 큰 파일에서는 극도로 눈에 띈다: 부드러운 스크롤, 빠른 검색, 그리고 크게 감소된 메인 스레드 압력.
large file


캐싱과 프리페칭

캐시 컴포넌트는 또한 'use cache' 지시문을 도입한다. 이것은 모든 것을 수동으로 연결하지 않고도 스마트하고 세분화된 캐싱을 활성화할 수 있게 해준다. React의 최신 Activity 프리미티브와 결합하면, 링크를 이제 사전 렌더링할 수 있다. 경로 전환 전체에서 컴포넌트 상태를 보존하고 전체 다시 로드의 전형적인 미묘한 "재설정" 느낌을 제거한다.
이것을 활성화하고 인터페이스의 많은 링크에 프리페치를 추가하면서, 네비게이션 경험은 본질적으로 즉각적이 되었다.
당신은 이것이 "너무 많은 프리페칭"을 초래한다고 주장할 수 있지만, 그것은 실제로 바람직하다:
a) 인식된 성능을 극적으로 개선하고, b) 실제 GitHub 시나리오에서, 이 요청의 대부분은 이미 전역적으로 캐시되었을 것이고, GitHub는 파일이 변경될 때마다 훅으로 그것들을 쉽게 무효화할 수 있다.
결과는 웹사이트보다는 네이티브 앱에 더 가깝게 느껴진다. 그런데도 그것은 여전히 HTML, CSS, JS일 뿐이다.

결론

나는 여전히 Pull Request 기능을 구축하지 않았다. 그 부분은 절반만 완료되었다.
바라건대, 이 저장소는 또한 RSC와 캐시 컴포넌트를 탐색하는 다른 개발자들을 위한 유용한 참고 자료가 될 것이다. 또는 파란 바를 보고 다음과 같이 생각한 누구든지:
"이것이 여기에 있을 필요가 없다면 어떨까?"
0
10

댓글

?

아직 댓글이 없습니다.

첫 번째 댓글을 작성해보세요!

Inkyu Oh님의 다른 글

더보기

유사한 내용의 글