사내 프로젝트에서 회원가입페이지를 모바일 기기에서만 접근할 수 있도록 만들어달라는 요청이있어 구현과정을 정리해보았습니다. 프로젝트는 next.js v14.2버전으로 진행했습니다.
User-agent란?
사용자 에이전트 (User Agent)란, 우리가 사용하는 웹 브라우저 속에 숨겨진 중요한 기능 중 하나를 말합니다. 간단히 말해 내가 어떤 OS를 쓰고 있고, 버전은 어떤 버전인지 웹 브라우저의 정보는 어떤 것인지 등을 담고 있는 번호판 같은 개념입니다.
user-agent는 requestheader에서 찾아볼 수 있습니다. 브라우저 개발자도구의 network탭에서 서버에 요청된 아무거나 눌러보면 아래 화면을 찾을 수 있습니다.
왜 사용할까요?
user-agent는 사용자의 기기, 브라우저 정보를 제공하기 때문에 이러한 정보를 수집하여 마케팅 정보로 활용하거나 특정 기기, 운영체제에 따라서 요청된 페이지에서 다른페이지로 redirect시킬 수 도 있습니다.
어떻게 사용할까요?
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> ) } }
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> ); }
정규표현식 활용
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;