user-agent를 이용한 모바일만 접근하기

user-agent를 이용한 모바일만 접근하기

·

4 min read

사내 프로젝트에서 회원가입페이지를 모바일 기기에서만 접근할 수 있도록 만들어달라는 요청이있어 구현과정을 정리해보았습니다. 프로젝트는 next.js v14.2버전으로 진행했습니다.

User-agent란?

사용자 에이전트 (User Agent)란, 우리가 사용하는 웹 브라우저 속에 숨겨진 중요한 기능 중 하나를 말합니다. 간단히 말해 내가 어떤 OS를 쓰고 있고, 버전은 어떤 버전인지 웹 브라우저의 정보는 어떤 것인지 등을 담고 있는 번호판 같은 개념입니다.

user-agent는 requestheader에서 찾아볼 수 있습니다. 브라우저 개발자도구의 network탭에서 서버에 요청된 아무거나 눌러보면 아래 화면을 찾을 수 있습니다.

왜 사용할까요?

user-agent는 사용자의 기기, 브라우저 정보를 제공하기 때문에 이러한 정보를 수집하여 마케팅 정보로 활용하거나 특정 기기, 운영체제에 따라서 요청된 페이지에서 다른페이지로 redirect시킬 수 도 있습니다.

어떻게 사용할까요?

  1. react-device-detect (라이브러리) 입니다. 오늘 날짜 기준 Weekly Downloads수가 80~90만 정도 됩니다. 아래 설치 방법과 간단하게 예시를 넣었습니다. 자세한 사용법은 라이브러리 링크 클릭!

     npm install react-device-detect --save
    
     or
    
     yarn add react-device-detect
    
     import {isMobile} from 'react-device-detect';
    
     function App() {
       renderContent = () => {
         if (isMobile) {
           return <div> This content is available only on mobile</div>
         }
         return <div> ...content </div>
       }
    
       render() {
         return this.renderContent();
       }
     }
    
     import { isIE } from 'react-device-detect';
    
     function App() {
       render() {
         if (isIE) return <div> IE is not supported. Download Chrome/Opera/Firefox </div>
         return (
           <div>...content</div>
         )
       }
     }
    
  2. mobile-detect (라이브러리) 오늘 날짜 기준 Weekly Downloads수가 20만 정도 됩니다. 아래 설치 방법과 간단하게 예시를 넣었습니다. 자세한 사용법은 라이브러리 링크 클릭!

     npm install mobile-detect
    
     or
    
     yarn add mobile-detect
    
     import MobileDetect from 'mobile-detect';
    
     // 사용자의 User-Agent 문자열을 가져옵니다.
     const userAgent = window.navigator.userAgent;
    
     // MobileDetect 인스턴스를 생성합니다.
     const md = new MobileDetect(userAgent);
    
     // 모바일 디바이스인지 확인
     if (md.mobile()) {
         console.log('이 디바이스는 모바일입니다.');
     }
    
     // 태블릿인지 확인
     if (md.tablet()) {
         console.log('이 디바이스는 태블릿입니다.');
     }
    
     // 특정 운영체제 확인
     if (md.is('iOS')) {
         console.log('이 디바이스는 iOS를 사용합니다.');
     }
    
     // 특정 브라우저 확인
     if (md.is('Chrome')) {
         console.log('이 브라우저는 Chrome입니다.');
     }
    
     // 디바이스 이름 가져오기
     console.log('디바이스 이름:', md.mobile());
    
     // 운영체제 이름 가져오기
     console.log('운영체제:', md.os());
    
     // 사용자 에이전트 문자열 가져오기
     console.log('User Agent:', md.userAgent());
    
     // next.js
     import { useEffect, useState } from 'react';
     import MobileDetect from 'mobile-detect';
    
     function MyComponent() {
       const [isMobile, setIsMobile] = useState(false);
    
       useEffect(() => {
         const md = new MobileDetect(window.navigator.userAgent);
         setIsMobile(!!md.mobile());
       }, []);
    
       return (
         <div>
           {isMobile ? '모바일 버전' : '데스크톱 버전'}
         </div>
       );
     }
    
  3. 정규표현식 활용

     export const isDesktop = () => {
       if (typeof window !== "undefined") {
         const userAgent = navigator.userAgent.toLowerCase();
         return !/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
           userAgent
         );
       }
       return false;
     };
    

선택한 방법

저는 위에 설명드린 3번 정규표현식을 사용해서 modal이 나타나도록 구현하였습니다. (모달은 전역상태로 관리하고 있는데 나중에 따로 정리할까 합니다.)

'use client'
import { isDesktop } from "@/shared/utils/utils";
import { Button } from "@mui/material";
import { useRouter } from "next/navigation";

const Page = ()=>{
  const router = useRouter();
  const clickSignUp = () => {
    if (isDesktop()) {
      setDialogOpen({
        status: true,
        children: <MobileAccessModal />,
        useCloseButton: false,
      });
    } else {
      router.push("/signup");
    }
  };
return (
     <CommonButton onClick={clickSignUp}>
           회원가입
     </CommonButton>
    )
}

추가로 next.js middleware를 활용하여 /signup페이지에 접근했을 때 모바일 기기가 아니라면 다른페이지로 redirect처리 하였습니다. next.js middleware는 특정 페이지에 요청이 왔을 때 전처리를 하기 유용합니다. middleware.ts파일 app파일 경로에 만들어줍니다. handleSignupPage 함수를 보면 !isMobileDevice(userAgent) 확인 후 /noaccess 페이지로 redirect로 시키는 로직이있습니다.

// middleware.ts

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { handleSignupPage, handleProfilePage } from "@/shared/middleware";

const routes = [
  { path: "/signup", handler: handleSignupPage },
  { path: "/profile", handler: handleProfilePage },
  // 더 많은 라우트...
  {
    test: (path: string) => path.startsWith("/test/"),
    handler: handleProfilePage,
  },
];

export function middleware(request: NextRequest) {
  const path = request.nextUrl.pathname;

  for (const route of routes) {
    if (route.path === path || (route.test && route.test(path))) {
      return route.handler(request);
    }
  }

  return NextResponse.next();
}
// handleSignupPage
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

// 모바일 디바이스 체크 함수
function isMobileDevice(userAgent: string): boolean {
  const mobileRegex =
    /Mobile|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
  return mobileRegex.test(userAgent);
}

// 회원가입 페이지 미들웨어
export function handleSignupPage(request: NextRequest) {
  const userAgent = request.headers.get("user-agent") || "";
  console.log(userAgent);

  if (!isMobileDevice(userAgent)) {
    const url = request.nextUrl.clone();
    const message = encodeURIComponent("모바일에서 가입을 진행해 주세요.");
    url.pathname = "/noaccess";
    url.searchParams.set("message", message);
    return NextResponse.redirect(url);
  }

  return NextResponse.next();
}

noaccess페이지에 redirect되기 때문에 noaccess페이지도 만들어주면 완료입니다. 다른 경로에 대한 middleware 로직도 handleProfilePage 처럼 추가로 만들어서 사용할 수 있습니다.

// app/noaccess page.tsx 
"use client";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { Paper, Stack, Typography,Button } from "@mui/material";


const NoAccessPage = () => {

  const searchParams = useSearchParams();
  const encodedMessage = searchParams.get("message");
  const encodedDiscription = searchParams.get("discription");

  const title = encodedMessage
    ? decodeURIComponent(encodedMessage)
    : "접근 권한이 없습니다";

  const discription = encodedDiscription
    ? decodeURIComponent(encodedDiscription)
    : undefined;

  return (
        <Stack>
          <Typography textAlign={"center"} variant="h6" mb={2}>
            {title}
          </Typography>

          {discription && (
            <Typography textAlign={"center"} variant="body1" mb={2}>
              {discription}
            </Typography>
          )}

          <Link href={"/"}>
            <Button fullWidth>홈으로 돌아가기</Button>
          </Link>
        </Stack>
  );
};

export default NoAccessPage;

참고자료

웹 브라우저 속 숨겨진 중요 기능, 사용자 에이전트(User Agent)란?

사용자 에이전트를 사용한 브라우저 감지

웹페이지 접속 기기(모바일 / 태블릿 / PC) 구분하기