
4월 20일, 대학생을 위한 과제 관리 서비스 CHECKTASK가 정식 출시됐다. 출시 직전에 발견한 문제 하나를 정리해두려 한다.
서비스에 모바일/태블릿 전용 뷰를 따로 만들어뒀다. CSS 미디어쿼리로 화면 너비에 따라 뷰를 분기하는 방식이었다.
그런데 아이패드로 접속해보니 태블릿 뷰가 아닌 데스크톱 뷰가 그대로 나왔다. 아이패드 화면이 넓다 보니 미디어쿼리 기준을 넘어버린 것이다.
"화면 너비가 아니라 실제 기기를 기준으로 뷰를 분기할 수 없을까?" 하는 생각에 방법을 찾아봤다.
기존 코드는 이렇게 생겼다.
{/* 모바일 (< 768px) */}
<div className={css({ display: 'block', md: { display: 'none' } })}>
<MobileLoginView />
</div>
{/* 태블릿 (768px – 1024px) */}
<div className={css({ display: 'none', md: { display: 'block' }, lg: { display: 'none' } })}>
<TabletLoginView />
</div>
{/* 데스크톱 (≥ 1024px) */}
<div className={css({ display: 'none', lg: { display: 'flex' } })}>
<DesktopLoginView />
</div>
화면 너비 기준이라 아이패드처럼 넓은 태블릿은 데스크톱으로 분류된다. 이게 문제였다.
User-Agent(UA)는 브라우저가 서버에 요청을 보낼 때 함께 전송하는 HTTP 헤더로, 기기와 OS, 브라우저 정보가 담겨 있다.
Next.js App Router에서는 서버 컴포넌트 안에서 headers()로 이 헤더를 읽을 수 있다.
처음엔 UA 문자열을 직접 정규식으로 파싱했는데, 공식 문서를 보다가 Next.js에 userAgent 유틸이 이미 내장돼 있다는 걸 알게 됐다.
next/server에서 import해서 쓸 수 있고, 내부적으로 ua-parser-js를 사용하기 때문에 정규식을 직접 관리할 필요가 없다.
import { headers } from 'next/headers';
import { userAgent } from 'next/server';
const headersList = await headers();
const { device } = userAgent({ headers: headersList });
const isMobile = device.type === 'mobile';
const isTablet = device.type === 'tablet';
if (isMobile) return <MobileLoginView />;
if (isTablet) return <TabletLoginView />;
return <DesktopLoginView />;
서버에서 UA를 읽어 해당 기기에 맞는 뷰만 렌더링한다. 아이패드로 접속하면 device.type === 'tablet'이 되어 태블릿 뷰가 정확히 내려온다.
두 방식은 기준이 다르다.
CSS만 알고 있었는데 서버에서 이런 방식으로도 처리할 수 있다는 걸 알게 됐다. 상황에 따라 잘 골라 쓰면 될 것 같다.