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

JakeJake
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) 구분하기

0
Subscribe to my newsletter

Read articles from Jake directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jake
Jake

하루하루 꾸준히 자라고 있는 프론트엔드 개발자입니다. 내일배움캠프 리액트 튜터로 활동하고있습니다.