#Frontend

항공 프론트엔드 구축기 (4/10): “앱이냐”와 “좁냐”는 다른 질문이다

항공 프론트엔드 구축기 (4/10): “앱이냐”와 “좁냐”는 다른 질문이다
01

Summary

앱뷰와 반응형 웹, 이제 'isWebview' 플래그 하나로 대충 떼우지 마세요

여기어때 개발팀이 한 벌의 코드로 하이브리드 앱과 반응형 웹의 디바이스 경계를 허문 실무 가이드

본 아티클은 모바일 하이브리드 앱과 데스크톱 웹 환경을 단 하나의 프론트엔드 코드베이스로 다루며 마주한 레이아웃 설계와 상태 제어의 한계를 깊이 있게 고찰합니다. 화면 크기, 플랫폼 유형, 포인팅 디바이스 유무라는 독립적인 세 가지 기준을 정립하여 복잡한 분기 비용을 낮추고, SSR 환경의 최대 난제인 초기 렌더링 CLS 문제를 해결하기 위한 구체적인 방법론을 공유합니다.

  • 01단일 변수를 넘어 '앱 유무', '화면 크기', '마우스 지원 여부'로 분기 축을 분리하여 설계적 확장성 확보
  • 02React 18 useSyncExternalStore API를 활용해 외부 브라우저 창 크기의 안전한 상태 동기화 메커니즘 수립
  • 03터치 디바이스에서 마우스 호버 효과가 유실되거나 고착되는 인터랙션 버그를 미디어 쿼리 변형자로 우아하게 해결
  • 04컴포넌트를 새로 마운트하는 대신 Prop 상태 전송 기법을 사용해 레이아웃 변형 중에도 활성화 상태 완벽 보존
  • 05SSR 레이아웃 흔들림(CLS) 방지를 위해 제시된 CSS 트릭과 예약 빈 상자 방식의 실무 관점 트레이드오프 비교

RECOMMENDATION

하이브리드 앱과 반응형 웹 서비스를 동시에 단일 코드베이스로 관리하고 계신 프론트엔드 개발자분들과, SSR 적용 이후 디바이스 감지 및 레이아웃 누수 현상에 대한 안정적인 해결책을 고민하시는 시니어 설계자분들께 적극 추천합니다.

The Problem

웹과 모바일 앱(웹뷰) 환경에서 플랫폼 형태와 화면 너비를 구별하지 않고 단일 환경 변수로 혼용하여 UI 분기 처리에 한계가 있었으며, SSR(서버 사이드 렌더링) 시 화면 너비를 선제적으로 알지 못해 초기 로딩 단계에서 레이아웃이 흔들리는(CLS) 문제와 디바이스 전환 시 입력 상태가 초기화되는 문제가 발생했습니다.

The Solution

환경 판단 플래그를 플랫폼 유형, 화면 너비, 입력 장치 사양의 세 가지 독립된 질문으로 세분화하고 React 18의 useSyncExternalStore 훅을 도입해 안정적으로 윈도우 크기를 구독하도록 조치했습니다. 또한 레이아웃 이중 처리(CSS 방식)와 영역 예약(Mounted 방식)을 상황에 맞춰 트레이드오프하고 컴포넌트를 언마운트하지 않은 채 Prop 전달을 통해 렌더링 형태만 제어하도록 설계했습니다.

The Result

사용자가 브라우저 창 너비를 임의로 조정하더라도 드롭다운이나 팝업창 등 컴포넌트의 활성화 상태와 사용자 입력값이 온전히 유지되는 향상된 반응형 UX를 구현했으며, 하나의 공통 컴포넌트 코드로 웹과 하이브리드 앱의 다양한 화면 규격을 효율적으로 제어하게 되었습니다.

Trade-off

SSR 환경에서 CLS를 피하기 위해 CSS로 이중 렌더링 시 보이지 않는 DOM의 렌더링 비용이 발생하며, 마운트 지연(자리 예약) 방식을 택할 시 고정 마크업 높이값을 유지보수 단계에서 수동으로 싱크 맞춰야 하는 관리 공수가 따릅니다. 또한 컴포넌트를 유지하는 전제 하에 기존에 바인딩되어 있던 스크롤 핸들러나 DOM 캐시 요소가 오작동하지 않도록 꼼꼼한 라이프사이클 핸들링이 추가로 요구됩니다.

03

Key Concepts

Concept · 01

useSyncExternalStore

React 18에 탑재된 API로 외부의 데이터 소스나 브라우저의 전역 상태(예: window.innerWidth)를 리액트 생명주기와 동기화하여 안전하게 구독할 수 있게 돕는 훅입니다.

  • 사용자가 창 크기를 조절하는 시점(resize)에 브라우저가 제공하는 화면 너비를 실시간으로 수집하기 위해 도입되었습니다.
  • 서버사이드 렌더링 단계에서 해당 외부 윈도우 객체에 안전하게 대응할 수 있도록 서버용 기본 데이터를 명시하는 반환 경로로 설계되었습니다.
Concept · 02

SSR Hydration과 CLS 방지

서버가 브라우저 해상도를 모르는 상태에서 렌더링된 초기 HTML이 클라이언트에 도달해 Hydration 될 때 레이아웃 구조가 튕기듯이 변하는 현상을 극복하기 위한 최적화 기법입니다.

  • 첫 로딩 화면부터 올바른 레이아웃을 표출하기 위해 DOM을 두 벌 렌더링해 CSS로만 분기 처리하는 방법을 제시했습니다.
  • 무거운 레이아웃 요소의 경우 마운트가 확보될 때까지 페이드인 효과 및 빈 박스로 높이를 예약 유지하는 지연 렌더링 구조로 대응했습니다.
Concept · 03

컴포넌트 영속성 관리 (State Preservation)

디바이스 규격 및 해상도가 실시간으로 전환될 때 상위 구조만 변경하고 내부 컴포넌트를 무조건 재생성(Remount)하지 않아 내부 로컬 상태를 일관성 있게 보전하는 렌더링 설계 전략입니다.

  • 드롭다운 컴포넌트의 활성화 상태(isOpen)가 너비가 좁아지며 바텀시트로 전이되어도 온전히 유지되도록 설계 축을 통합했습니다.
  • 이러한 영속적 상태 설계 덕분에 팝업이나 탭이 디바이스 레이아웃을 넘나들어도 스크롤 복원과 입력 중이던 흐름이 그대로 이어집니다.