코딩하는라민

[Next.js] 모바일 브라우저 환경에서 초기 렌더링 시 미디어쿼리 적용되지 않는 현상 본문

개발 공부/Next.js

[Next.js] 모바일 브라우저 환경에서 초기 렌더링 시 미디어쿼리 적용되지 않는 현상

코딩하는라민 2024. 1. 26. 01:58
728x90
반응형

[Next.js] 모바일 브라우저 환경에서 초기 렌더링 시 미디어쿼리 적용되지 않는 현상

figma iconify 로 제작

문제 상황

프로젝트 진행 중 모바일 브라우저 환경에서 미디어쿼리가 적용되지 않는 현상을 확인했다.

 

사용중인 스펙은 `next.js`, `emotion/styled`, `react-responsive`, `vercel`.

 

데스크탑 브라우저에서 페이지를 오픈했을 때는 미디어쿼리 스타일이 정상적으로 작동했다. 물론 브라우저의 창을 줄여 모바일 뷰포트로 변경되었을 때에도 정상적으로 작동했다.

하지만

모바일 브라우저에서 페이지를 오픈했을 때 미디어쿼리 스타일이 적용되지 않았다.

 

모바일 환경에서는 왜 적용되지 않는걸까? 🫨

Next.js 에서는 기본적으로 `pre-rendering` 을 한다. pre-rendering 이란 서버 측에서 DOM 요소를 빌드해서 HTML 요소를 미리 렌더링한다.

그러나 뷰포트를 확인하는 것은 클라이언트 측에서 이루어지고, 초기 렌더링 시 서버 측에서는 클라이언트 측의 뷰포트 크기를 알 수 없으므로 미디어 쿼리를 평가할 수 없다.

 

 axios 를 이용한 동적 페이지에서는 모바일 브라우저에서 페이지를 오픈해도 미디어쿼리가 정상적으로 적용되었다.

 

axios 를 이용해서 데이터를 받아오는 동적 페이지에서는 클라이언트 측에서 렌더링이 된다. 따라서 초기 렌더링 및 뷰포트 확인이 클라이언트 측에서 모두 이루어지기 때문에 초기 렌더링 시 미디어쿼리가 적용된다.

 

기존 코드

프로젝트에서 미디어쿼리를 react-responsive 라이브러리를 통해 커스텀 훅으로 만들어 사용했다.

@/hooks/useMediaQuery.ts

useIsMobile 은 모바일이면 true 를 반환하고, 모바일이 아니면 false 를 반환한다.

import { useMediaQuery } from "react-responsive"

export const useIsMobile = () => {
  const isMobile = useMediaQuery({ query: "(max-width: 767px)" })
  return isMobile
}

 

issue/index.tsx

페이지 컴포넌트에서 이 커스텀 훅을 isMobile 에 할당해서 스타일 컴포넌트의 Props 로 넘겨준다.

스타일 컴포넌트에서는 props 의 값에 따라 스타일을 조건부 렌더링하고있다.

import { useIsMobile } from "@/hooks/useMediaQuery"
import styled from "@emotion/styled"

export default function Issue() {
  const isMobile = useIsMobile()
  return (
    <Div isMobile={isMobile}>
       훅에서 useEffect 를 이용해 미디어 쿼리를 추가적으로 확인합니다.
    </Div>
  )
}

interface Style {
  isMobile?: boolean
}

const Div = styled.div<Style>`
  padding: ${(props) => (props.isMobile ? "20px" : "0")};
`

 

해결방법

useEffect 추가

`useEffect` 를 이용해서 컴포넌트가 렌더링된 이후에 클라이언트 측에서 미디어쿼리를 추가적으로 확인해주는 방법이다. 순서를 정리해보면 이렇다.

  • 컴포넌트 렌더링
  • useEffect hook 실행
  • 클라이언트 측에서 미디어 쿼리 확인
  • 스타일 조정
export const useIsMobile = () => {
  const [isMobile, setIsMobile] = useState(false)
  const mobile = useMediaQuery({ query: "(max-width: 767px)" })

  useEffect(() => {
    setIsMobile(mobile)
  }, [mobile])

  return isMobile
}

 

이 방법을 이용하면 새로고침 시 레이아웃이 깜빡이는 현상이 발생한다.

사용해 보지는 않았지만 서버 측에서 미디어 쿼리 적용하는 방법(ServerStyleSheet)도 있는 것 같다.

이 방법은 서버 측. 즉, 렌더링 이전에 스타일을 적용하는 방법이다.

― styled-components 에서 사용할 수 있다.

 

페이지 내부에서 조건부 렌더링 하는 방법도 있다.

function Solution2() {
  const isMobile = useIsMobile()
  const [isMount, setIsMount] = useState(false)

  useEffect(() => {
    setIsMount(true)
  }, [])

  return <>{isMount && <Div isMobile={isMobile}>마운트된 이후에 컴포넌트를 렌더링합니다.</Div>}</>
}

이 방법을 사용하면 컴포넌트가 지연 렌더링되며 레이아웃의 깜빡임 현상이 발생하지는 않는다.

다만 문제가 발생하는 모든 페이지에 코드를 삽입해줘야한다.

 

css 미디어 쿼리 사용

해당 스타일 컴포넌트에 직접적으로 미디어 쿼리 css 스타일을 추가해준다.

export default function Other() {
  return (
    <Wrapper className="wrapper">css 미디어쿼리를 반영한 페이지입니다.</Wrapper>
  )
}

const Wrapper = styled.div`
  background-color: #eaeaea;

  @media screen and (max-width: 767px) {
    padding: 20px;
  }
`

이 방법을 사용하면 미디어쿼리 문제가 발생하는 곳 각각에 해당하는 스타일 코드를 추가해야한다.

 

마치며

SSR 에서 반응형을 구현할 때 정적/동적 페이지에 따라 미디어쿼리가 적용이 달라지는 것을 처음 알게되었다.

처음에는 미디어 쿼리가 먼저 동작 후에 브라우저가 뷰포트를 확인하기 때문에 발생한 문제라고만 생각했다. 또한 Next.js 에서 window 객체가 동작하지 않기 때문에 발생한 문제라고 생각했지만, 문제는 SSR 의 렌더링 방식에 있었다.

본 포스팅을 통해 Next.js 의 렌더링 방식을 더 깊게 알게되었다. 


▼ 포스팅에 사용된 소스코드 보기

 

 

GitHub - mkk00/Next.js-mediaQuery-Issue: 모바일 브라우저에서 초기 렌더링 시 미디어쿼리 적용되지 

모바일 브라우저에서 초기 렌더링 시 미디어쿼리 적용되지 않는 이슈 해결. Contribute to mkk00/Next.js-mediaQuery-Issue development by creating an account on GitHub.

github.com


728x90
반응형