밝을희 클태

[ Next.JS ] userAgent를 효율적으로 관리해보자 본문

KEYNUT 프로젝트

[ Next.JS ] userAgent를 효율적으로 관리해보자

huipark 2024. 9. 4. 17:05

 프로젝트의 대부분의 media query를 사용해서 반응형을 구현했다. 하지만 상품 상세 페이지의 경우 모바일일 때는 이미지 슬라이더를 터치 슬라이더로 구현을 하고 싶었고 PC의 경우 버튼을 클릭해서 이미지를 넘기게 하고 싶었다. 그래서 클라이언트의 userAgent를 확인해 모바일인지 아닌지 체크를 했고 결과는 내가 원하는 데로 잘 동작을 했다.

export function isMobile() {	
  if (typeof window !== 'undefined') {	
    if (navigator.maxTouchPoints >= 1) {	
      const isMobile = /Macintosh|iPad|Mobi|Android|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(	
        navigator.userAgent,	
      );	
      if (isMobile) return true;	
      return false;	
    }	
    return false;	
  }	
}

 그런데 상품 페이지를 들어갈 때마다 userAgent를 확인하는 건 매우 비효율적이고 또 확인을 할 때마다 비용이 발생해 상품의 이미지를 보여주는데 지연이 발생했다. 물론 엄청 짧은 시간이라 실사용에는 전혀 문제가 없었지만 해당 페이지에 진입할 때마다 검사를 하는 게 마음에 걸렸다.

 그래서 생각한 게 서버 컴포넌트에서 한 번만 클라이언트의 정보를 확인하고, 그 결과를 페이지 전역 상태로 관리하는 방식으로 개선하기로 했다. 

Next.JS 14 App router 서버 컴포넌트 (RootLayout):

import { headers } from 'next/headers';
import { userAgent } from 'next/server';

export default function RootLayout({ children }) {
// 서버 컴포넌트에서 userAgent 받기
  const user = userAgent({ headers: headers() });

  return (
    <html lang="en">
    ...
          <UserProvider initialUser={user}>
              <main>{children}</main>
          </UserProvider>
	...
    </html>
  );
}

RootLayout 컴포넌트에서 서버 측에서 받은 userAgent 정보UserProvider에 전달합니다. 레이아웃 컴포넌트에서 정보를 받는 이유는 UserProviderReact Hooks인 useContext와 useState 등을 사용해야 하므로, 클라이언트 컴포넌트로 선언했다.

클라이언트 컴포넌트 (UserProvider):

'use client';

import { useContext, createContext, useState, useEffect } from 'react';

const UserContext = createContext();

export const UserProvider = ({ children, initialUser }) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const isIPad = initialUser.device.model === 'Macintosh' && navigator.maxTouchPoints >= 1;

    setUser({
      ...initialUser,
      device: {
        ...initialUser.device,
        type: initialUser.device.type || (isIPad ? 'mobile' : 'desktop'), // 기본값으로 'desktop' 설정
      },
    });
  }, []);

  return <UserContext.Provider value={{ user, setUser }}>{children}</UserContext.Provider>;
};

export const useUser = () => useContext(UserContext);

UserProvider 컴포넌트는 부모로부터 받은 초기 사용자 정보(initialUser)를 바탕으로, 모바일인지 PC인지 판단해 전역 상태로 관리한다. 아이패드의 경우 Macintosh로 인식되기 때문에, 추가적인 조건문을 통해 판단을 한다.

성능 측정 (4G 기준)

성능 측정 결과 기존 코드는 평균적으로 174.5ms 만큼의 시간이 걸렸고

수정 후 평균 172.1ms  만큼의 시간이 걸렸다.

결론

비록 성능 수치상으로 큰 차이는 없었지만, 페이지에 진입할 때마다 클라이언트 정보를 확인하는 것은 확실히 비효율적이다. 클라이언트 정보를 서버에서 한 번만 확인하고, 이를 전역 상태로 관리하는 방법을 적용한 덕분에 성능이 개선됐고, 이제 다른 페이지에서도 손쉽게 클라이언트 정보를 확인할 수 있다.

참고 자료

https://medium.com/@rajendransoundar3/detecting-device-type-in-next-js-ssr-on-both-page-and-app-router-32c07249e1a7

 

Detecting Device Type in Next.js SSR on both Page and App Router

Detecting the device type in server-side rendering (SSR) can significantly enhance user experience by tailoring content for mobile or…

medium.com