Next.js 애플리케이션 개발할 때 반응형 UI를 구현하기 위해 디바이스 타입을 감지하는 것은 중요한 작업입니다. 서버 사이드 렌더링(SSR)과 클라이언트 사이드 렌더링(CSR)을 모두 활용하는 Next.js에서는 이 두 환경에서 일관된 디바이스 감지 방법이 필요합니다!
이 글에서는 서버 컴포넌트에서 초기 디바이스 판별을 하고, 이를 클라이언트 컴포넌트로 전달하여 실시간으로 반응형 UI를 구현했던 방법을 설명드리겠습니다!🙆♀️
✅ 서버 사이드에서 디바이스 감지하기 ✅
먼저 서버 컴포넌트에서 userAgent를 기반으로 디바이스 타입을 감지하는 함수를 만듭니다.
☑️ userAgent()란?
Next.js의 내장 기능으로 next/server 모듈에서 userAgent 함수를 제공합니다. 이 함수는 SSR 환경에서 사용자의 브라우저 정보를 분석할 수 있게 해주는 유틸리티 함수입니다. 따라서 Next.js의 자체 기능이므로 그냥 코드에 import만 추가하면 됩니다!
import { headers } from "next/headers";
import { userAgent } from "next/server";
/*
next.js 앱에서 userAgent를 가져옵니다. 서버사이드에서 userAgent를 판별할 목적
*/
export function getUserAgent() {
return userAgent({ headers: headers()})
}
☑️ headers()의 역할
또한 여기서 headers()는 Next.js에서 제공하는 함수로 HTTP 요청의 헤더 정보를 가져옵니다. 헤더는 클라이언트(브라우저)가 서버에 요청을 보낼 때 함께 전송하는 메타데이터입니다. 이 메타데이터에는 다양한 정보가 포함되어 있습니다.
- 브라우저 정보(User-Agent 헤더)
- 클라이언트 언어 설정
- 쿠키 정보
- 인증 정보
- 요청 형식 등
서버 컴포넌트나 API 라우트에서 headers()함수를 호출하면 현재 요청의 모든 헤더 정보에 접근할 수 있습니다.
☑️ userAgent() 함수에 headers를 넣는 이유
userAgent() 함수는 Next.js의 next/server 모듈에서 제공하는 함수로, 사용자의 브라우저와 기기 정보를 파싱하여 분석하는 역할을 합니다. 이 함수가 동작하려면 HTTP 헤더 정보가 필요합니다. 따라서 userAgent({ headers: headers() })는 현재 요청의 헤더 정보를 userAgent 함수에 전달하여, 해당 요청을 보낸 클라이언트 브라우저 정보를 분석할 수 있게 합니다.
✅ 서버 컴포넌트에서 활용하기 ✅
서버 컴포넌트에서 이 함수를 사용하여 초기 디바이스 타입을 판별합니다.
// app/page.tsx
import { getUserAgent } from "@/lib/getUserAgent";
import { HeroSection } from "./_home/components/hero/HeroSection";
// 다른 import 문...
export default function Home() {
const userAgent = getUserAgent();
// 서버사이드에서 먼저 모바일인지 판별
const isMobile = userAgent.device.type === "mobile";
console.log("userAgent =>", userAgent);
console.log("isMobile"=>, isMobile);
return (
<main className="flex flex-col items-center w-full overflow-x-hidden">
<HeroSection isMobile={isMobile} />
{/* 다른 컴포넌트들... */}
</main>
);
}
userAgent를 console로 찍어보면 아래와 같은 객체가 제공됩니다.
userAgent => {
ua: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Mobile Safari/537.36',
browser: { name: 'Chrome', version: '134.0.0.0', major: '134' },
engine: { name: 'Blink', version: '134.0.0.0' },
os: { name: 'Android', version: '6.0' },
device: { vendor: 'LG', model: 'Nexus 5', type: 'mobile' },
cpu: { architecture: undefined },
isBot: false
}
여기서는 서버 측에서 userAgent를 분석하여 userAgent.device.type === "mobile"이면 true를 반홥힙니다. 이 값을 클라이언트 컴포넌트인 HeroSection에 props로 전달합니다.
✅ 클라이언트 컴포넌트에서 실시간 반응형 구현하기 ✅
클라이언트 컴포넌트에서는 서버에서 받은 초기값을 사용하면서도, 브라우저 창 크기 변경에 실시간으로 반응하기 위한 커스텀 훅을 만듭니다.
// hooks/useIsMobile.js
import { MOBILEBREAKPOINT } from "@/constants";
import { useEffect, useState } from "react";
import { useMediaQuery } from "usehooks-ts";
export function useIsMobile(initialValue?: boolean) {
const [isMobile, setIsMobile] = useState(initialValue);
const isBreakpointMatched = useMediaQuery(
`(max-width: ${MOBILEBREAKPOINT}px)`
);
useEffect(() => {
setIsMobile(isBreakpointMatched);
}, [isBreakpointMatched]);
return isMobile;
}
이 훅은:
- 서버에서 전달받은 initialValue를 초기 상태로 사용합니다.
- useMediaQuery 훅을 사용하여 현재 화면 크기가 모바일 기준점보다 작은지 확인합니다.
- 화면 크기가 변경될 때마다 isMobile 상태를 업데이트합니다.
✅ 클라이언트 컴포넌트에 적용하기 ✅
이제 클라이언트 컴포넌트에서 이 훅을 사용하여 서버에서 전달받은 초기값을 기반으로 시작하되, 클라이언트 측에서 실시간으로 반응하는 UI를 구현할 수 있습니다.
// _home/components/hero/HeroSection.jsx
"use client";
import { useIsMobile } from "@hooks/useIsMobile";
// 다른 import 문...
type Props = {
isMobile?: boolean;
};
export function HeroSection({ isMobile: _isMobile = false }: Props) {
const isMobile = useIsMobile(_isMobile);
console.log("확인중 =>", isMobile);
return isMobile ? <MobileView /> : <DesktopView />;
}
// MobileView와 DesktopView 컴포넌트 구현...
여기서 useIsMobile 훅에 서버에서 전달받은 _isMobile 값을 전달하여 초기 상태를 설정하고, 이후에는 실제 화면 크기에 따라 UI가 자동으로 업데이트됩니다.
isMobile을 console에 찍어보았습니다. 내용은 아래와 같습니다.
isMobile true
이 방식의 장점:
- 하이드레이션 일관성: 서버와 클라이언트 초기 렌더링이 일치하여 하이드레이션 오류를 방지합니다.
- 초기 로딩 최적화: 첫 로딩 시 서버에서 이미 디바이스를 판별하여 적절한 컴포넌트를 렌더링함으로써 레이아웃 시프트를 방지합니다.
- 실시간 반응형: 사용자가 브라우저 창 크기를 조절할 때 UI가 자동으로 업데이트 됩니다.
- 코드 유지 보수성: 디바이스 감지 로직이 훅으로 분리되어 있어 여러 컴포넌트에서 재사용이 가능합니다.
✴️ 결론 ✴️
Next.js에서 서버 사이드와 클라이언트 사이드를 매끄럽게 연결하는 이 패턴을 통해, 더 나은 사용자 경험을 제공하는 반응형 애플리케이션을 구현할 수 있습니다. 서버에서 초기 판단과 클라이언트에서의 실시간 반응을 결합함으로써, 빠른 초기 로딩과 동적인 사용자 경험을 모두 충족할 수 있습니다!
'인턴' 카테고리의 다른 글
Next.js의 Link 컴포넌트와 target="_blank" 속성 이해하기 (0) | 2025.03.31 |
---|---|
[ Troubleshooting🛠️ ] Zod 스키마 검증과 데이터 처리 (0) | 2025.03.28 |
Next.js 프로젝트의 효율적인 폴더 구조 개선하기 📂 (0) | 2025.03.26 |
React와 styled-components로 만드는 커스텀 토글 버튼 (0) | 2025.03.25 |
useMemo와 useCallback (0) | 2025.03.25 |