외부 라이브러리 하나를 추가할 때마다 우리는 번들 크기, 버전 충돌, 네이티브 바이너리 빌드 실패라는 세 개의 시한폭탄을 함께 심는다. 그런데 흥미롭게도, 최근 실무 개발자들이 공유한 사례 세 건을 나란히 놓고 보면 하나의 패턴이 선명하게 떠오른다. 브라우저와 런타임이 이미 제공하는 API만으로 체감 UX를 눈에 띄게 끌어올릴 수 있다는 것이다. React Navigation의 preload, WebGL 셰이더 기반 Liquid Glass, 그리고 Canvas API 파비콘 생성—세 사례 모두 '의존성 제로'에 가까운 접근으로 성능과 시각적 완성도를 동시에 잡는다.
타이밍을 바꾸는 것이 곧 성능이다 — React Navigation preload
velog에 올라온 사례는 React Native 앱에서 바텀 탭 전환 시 발생하던 약 150ms 블로킹 문제를 다룬다. lazy: true 설정 덕분에 초기 로딩은 빠르지만, 무거운 탭을 처음 눌렀을 때 NativeStackNavigator와 수십 개의 Provider가 JavaScript 싱글 스레드에서 동기적으로 마운트되면서 전환 애니메이션이 뚝뚝 끊기는 전형적인 문제다.
React Navigation 7부터 제공되는 preload API는 이 문제를 정면 돌파하지 않는다. 대신 타이밍을 옮긴다. navigation.preload('GiftshopStack')은 화면을 포커스 없이 미리 마운트해 두는데, 핵심은 이걸 언제 호출하느냐다. InteractionManager.runAfterInteractions 안에 감싸면, 현재 진행 중인 애니메이션과 터치 이벤트가 모두 끝난 유휴 구간에 마운트 작업이 실행된다. 식당 비유가 딱 맞다. 손님이 메뉴를 보는 동안 주방이 재료를 미리 손질해 두는 것이다.
실제 결과는 React Native DevTools Profiler로 확인됐다. 167ms였던 첫 마운트 커밋이 탭 탭 직후가 아닌 홈 진입 후 유휴 구간에 찍히고, 실제 탭 전환 시점의 커밋은 사실상 사라진다. 여기서 놓치지 말아야 할 트레이드오프가 있다. preload는 총 작업량을 줄이지 않는다. useFocusEffect는 실제 진입 전까지 실행되지 않지만, useEffect는 미리 돌아간다. 세션 내 한 번도 방문하지 않는 탭이라면 메모리를 낭비하는 셈이다. 무거운 탭이 추가될 때마다 PRELOAD_TAB_NAMES 배열만 수정하면 되도록 설계한 점은 실무 확장성 측면에서 배울 만하다.
DOM 클론의 한계를 픽셀 샘플링으로 넘다 — WebGL Liquid Glass
dev.to에 공개된 WebGL Liquid Glass 실험은 다른 결의 질문을 다룬다. 기존 Liquid Glass 구현 대부분은 유리 뒤의 DOM 콘텐츠를 클론한 뒤 블러와 색수차를 입히는 방식이다. 텍스트, 이미지, 레이아웃은 클론할 수 있지만 WebGL 캔버스의 실제 픽셀 버퍼는 복사할 수 없다. 캔버스는 정적인 DOM 서브트리가 아니라 매 프레임 바뀌는 프레임버퍼이기 때문이다.
이 구현의 핵심 아이디어는 렌더 파이프라인 안으로 유리 굴절을 끌어들이는 것이다. WebGL 수면 씬과 2D 꽃 캔버스를 하나의 숨겨진 소스 캔버스에 합성한 뒤, 유리 패널 셰이더가 그 소스의 UV를 오프셋해 굴절처럼 보이게 만든다. 패널 중앙은 상대적으로 조용하고, 가장자리로 갈수록 왜곡이 강해지는 부드러운 법선 추정으로 물리적 두께감을 살린다. DOM을 건드리지 않고 순수하게 픽셀 레벨에서 효과를 구현한다는 점에서 브라우저 네이티브 그래픽 파이프라인을 가장 공격적으로 활용한 사례다. 물론 이 방식이 프로덕션 레벨 범용 솔루션이 되려면 텍스트, 비디오, 스크롤, 트랜스폼 등 DOM 렌더링 경로 전체를 아울러야 한다는 과제가 남아 있다.
50줄로 의존성 하나를 지우다 — Canvas API 파비콘 생성
세 번째 사례는 가장 즉시 적용 가능하다. dev.to에 공개된 이 접근은 favicons 패키지가 끌어들이는 sharp, 그리고 M1 맥에서 네이티브 바이너리 빌드가 터지던 경험에서 출발한다. Canvas API의 Path2D 생성자가 SVG의 d 속성 문자열을 그대로 받는다는 특성을 이용해, SVG 에디터에서 디자인한 아이콘 패스를 파싱 없이 바로 캔버스에 그린다. 그리고 toDataURL()로 PNG를 추출해 16×16부터 512×512까지 여섯 가지 크기를 한 번에 내보낸다.
toBlob() 대신 toDataURL()을 택한 이유도 명쾌하다. 파비콘 크기(최대 512×512)에서 메모리 효율보다 코드를 동기적으로 유지하는 가독성이 더 가치 있다는 판단이다. ICO 포맷은 PNG <link> 태그로 대체하고, 레거시 브라우저를 위한 최소 ICO 인코더는 30줄이면 충분하다. Next.js 프로젝트 기준으로 sharp를 지우면 node_modules가 가벼워지고, npm install 시 네이티브 리빌드가 사라지며, 디자이너가 아이콘을 바꿀 때마다 반복되던 '내 컴퓨터에선 되는데' 상황도 함께 사라진다.
세 사례가 함께 가리키는 방향
세 가지 사례를 관통하는 공통 논리는 하나다. 브라우저와 런타임이 이미 제공하는 API의 특성을 정확히 이해할수록 외부 의존성 없이 더 나은 UX를 만들 수 있다는 것이다. preload는 마운트 타이밍을 제어하고, WebGL 셰이더는 픽셀 레벨에서 굴절을 계산하며, Path2D는 SVG와 Canvas 사이의 변환 비용을 없앤다. 각각이 해결하는 문제의 결은 다르지만, 접근 방식의 철학은 동일하다.
특히 주목할 점은 이 패턴들이 AI 코드 생성 도구와 궁합이 좋다는 것이다. Cursor나 Claude에 InteractionManager.runAfterInteractions 패턴이나 Path2D 기반 캔버스 렌더링을 설명하면, 프로젝트 맥락에 맞게 빠르게 구체화해 준다. 브라우저 네이티브 API는 스펙이 명확하고 MDN 문서가 풍부해 AI 모델의 환각이 상대적으로 적다. 즉, 의존성을 걷어내는 선택이 개발 속도와도 충돌하지 않는다.
앞으로의 방향을 보면, View Transitions API와 preload 패턴의 결합, WebGPU로 Liquid Glass 연산을 GPU에 더 깊이 위임하는 시도, 그리고 Canvas API를 활용한 동적 OG 이미지 생성 같은 확장이 자연스럽게 이어질 것이다. 결국 프레임워크와 라이브러리는 브라우저 API 위에 쌓인 추상이다. 추상이 제공하는 편의와 맞바꾼 비용이 있을 때, 그 비용을 직접 지불할 의지와 능력이 있는 팀은 더 가볍고 예측 가능한 스택을 가져갈 수 있다.