백승현
전체 프로젝트
2026·기획 + 설계 + 구현 (단독)

모바일 반응형 전면 대응

데스크톱 전용으로 뷰포트를 차단하던 서비스를 모바일에 열면서, '어느 경로를 열 것인가'라는 정책부터 다시 세운 8개 PR 규모의 작업.

ReactTypeScriptEmotionuseSyncExternalStore

주요 기여

데스크톱 전용이던 서비스를 모바일에 열었습니다. 어느 경로를 열고 어느 경로를 막을지부터 정해야 해서 CSS보다 정책 결정이 더 많은 작업이었고, 8열짜리 표는 억지로 줄이는 대신 같은 값끼리 묶어 4개 블록으로 다시 짰습니다.

문제

App.tsx에서 window.innerWidth < 1024면 경로와 무관하게 'PC 환경에서 접속해 주세요'만 렌더하고 있었습니다. 구글 모바일 우선 색인 기준으로는 크롤러가 모든 페이지에서 안내 문구만 봤을 가능성이 컸고, 차단을 걷어내자 막아야 할 /apply가 깨진 채 열리고 로그인·로그아웃 진입점은 통째로 사라지는 등 정책이 비어 있는 문제가 드러났습니다.

목표

뷰포트 차단을 제거하고, 모바일에 열 경로와 데스크톱 전용 경로를 코드로 명시한 뒤, 열리는 화면은 좁은 폭에서도 읽히게 만드는 것.

구현

브레이크포인트를 2단으로 분리했습니다. 흔한 1024px 대신 실제 레이아웃 폭 기준 belowDesktop(≤1039.98px)을 신설해 1025~1039px 구간의 가로 스크롤을 없앴고, 모바일 레이아웃 전환은 md(≤768px)로 두었습니다. features/viewport에 desktopOnlyPaths와 DesktopOnlyRoute를 만들어 데스크톱 전용 경로 정책을 코드로 세우고, useMediaQuery(useSyncExternalStore)로 리사이즈·회전 시 즉시 판정이 바뀌게 했습니다. 이 가드는 ProtectedRoute 바깥에 두어 어차피 막힐 경로에 로그인부터 시키지 않게 했습니다. 로그인 상태 메뉴 3개가 320px에서 98px 넘치는 것을 실측해 md 이하에서만 햄버거 메뉴로 대체했고(백드롭·ESC 닫기, 44px 터치 영역, aria-expanded, prefers-reduced-motion), 8열 장학금 비교표는 같은 값끼리 묶어 4블록으로 재구성하되 데스크톱 표의 병합 셀 정의를 그대로 슬라이스해 두 화면의 값이 어긋날 수 없게 했습니다.

어려웠던 점

헤더 메뉴를 숨기는 것만으로는 마이페이지 → /apply/:id 동선과 URL 직접 입력이 남고, 반대로 일괄 숨김은 로그아웃 수단까지 없애 막다른 길을 만들었습니다. PDF 뷰어 툴바는 버튼을 키우면 자식 svg와 ScrollContainer padding이 함께 틀어지는 구조였습니다.

해결 방법

라우트 진입 시점 판정으로 두 동선을 모두 차단하고, 헤더는 일괄 숨김에서 항목별 정책으로 전환해 '진입점이 없으면 사용자에게는 기능이 없는 것과 같다'는 기준으로 로그아웃과 아직 반응형 전인 페이지의 진입점도 열어뒀습니다. 툴바 버튼은 요소를 키우지 않고 오버레이로 탭 영역만 44px로 넓혔습니다. PR은 화면 단위로 쪼개고 열린 PR 4건과 겹치는 파일이 0개임을 본문에 명시했습니다.

회고

1024px은 흔한 값이지만 우리 레이아웃 폭은 1040px이라 그 사이 15px 구간이 깨졌습니다. 값을 빌려오지 말고 실제 레이아웃에서 유도해야 한다는 것, 그리고 좁은 화면에서 안 되는 건 표가 아니라 '훑어보기'라는 걸 배웠습니다.