인턴

Next.js에서 서버와 클라이언트 간 모바일 감지 연동하기

choijming21 2025. 3. 27. 12:30

 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

 

 

이 방식의 장점:

  1. 하이드레이션 일관성: 서버와 클라이언트 초기 렌더링이 일치하여 하이드레이션 오류를 방지합니다.
  2. 초기 로딩 최적화: 첫 로딩 시 서버에서 이미 디바이스를 판별하여 적절한 컴포넌트를 렌더링함으로써 레이아웃 시프트를 방지합니다.
  3. 실시간 반응형: 사용자가 브라우저 창 크기를 조절할 때 UI가 자동으로 업데이트 됩니다.
  4. 코드 유지 보수성: 디바이스 감지 로직이 훅으로 분리되어 있어 여러 컴포넌트에서 재사용이 가능합니다.

 

 

 

 

 

 

 

✴️ 결론 ✴️

 Next.js에서 서버 사이드와 클라이언트 사이드를 매끄럽게 연결하는 이 패턴을 통해, 더 나은 사용자 경험을 제공하는 반응형 애플리케이션을 구현할 수 있습니다. 서버에서 초기 판단과 클라이언트에서의 실시간 반응을 결합함으로써, 빠른 초기 로딩과 동적인 사용자 경험을 모두 충족할 수 있습니다!