Next.js에서 반응형 구현하기

Dave·2023년 10월 1일
0

NEXT

목록 보기
4/4
post-thumbnail

출처
React/NextJS, SSR and Responsive Design
React 02, SSR vs Responsive Design
React, Combining Server-Side Rendering and Responsive Design

보통 리액트 프로젝트에서 장치 너비에 따라 반응형을 구현하려면 아래와 같이 한다.

  1. 미디어 쿼리마다 다른 CSS 선택자를 적용한다.
  2. useEffect 훅 내부에서 자바스크립트 코드로 장치 너비 변수를 resize 이벤트가 발생할 때마다 재계산하여 컴포넌트에 반영한다.
  3. react-responsive 라이브러리를 사용한다.

Next.js 프로젝트에도 그대로 적용한다.

1. 방법들

1-1. resize 이벤트를 이용해서 뷰포트 너비를 실시간으로 재할당하기

import { useState, useEffect } from "react"

useEffect(() => {
  const resizeViewportWidth = () => {
    setViewportWidth(window.innerWidth)
  }

  window.addEventListener('resize', resizeViewportWidth)

  return () => {
    window.removeEventListener('resize', resizeViewportWidth)
  } 
}, [])

{
  viewportWidth < 768 ? <div>모바일</div>:<div>데스크탑</div>
}

메모리 누수를 방지하기 위해 컴포넌트가 마운트 됐을 때 resize 이벤트 핸들러를 딱 한 번 부여해주고 컴포넌트가 디마운트 됐을 때 해당 이벤트 핸들러를 제거해준다.

1-2. CSS로 제어하기

@media screen 키워드를 통해서 특정 뷰포트 너비에 따라 선택자의 CSS 코드를 변경한다.

@media screen and (max-width: 1024px) {
  .content-wrap{
    background-color:rgba(243, 246, 251, 1);
    padding: 1rem;
  }
}

1-3. react-responsive로 제어하기

출처 : react-responsive

import React from 'react'
import { useMediaQuery } from 'react-responsive'

const Example = () => {
  const isDesktopOrLaptop = useMediaQuery({
    query: '(min-width: 1224px)'
  })
  const isBigScreen = useMediaQuery({ query: '(min-width: 1824px)' })
  const isTabletOrMobile = useMediaQuery({ query: '(max-width: 1224px)' })
  const isPortrait = useMediaQuery({ query: '(orientation: portrait)' })
  const isRetina = useMediaQuery({ query: '(min-resolution: 2dppx)' })

  return <div>
    <h1>Device Test!</h1>
    {isDesktopOrLaptop && <p>You are a desktop or laptop</p>}
    {isBigScreen && <p>You  have a huge screen</p>}
    {isTabletOrMobile && <p>You are a tablet or mobile phone</p>}
    <p>Your are in {isPortrait ? 'portrait' : 'landscape'} orientation</p>
    {isRetina && <p>You are retina</p>}
  </div>
}

2. 문제점

서버 사이드 렌더링에서는 window 전역객체를 인식할 수 없다.

window 전역객체는 브라우저 환경에서만 인식할 수 있다. 따라서 서버 사이드에서 컴포넌트를 인식할 때는 window 객체를 읽을 수 업다.

따라서 window 객체를 이용하여 렌더링 조건을 설정했을 때는 제대로 적용이 안 된다.

위 사진을 보면 처음 렌더링 됐을 때 뷰포트 사이즈가 데스크톱 사이즈임에도 불구하고 모바일 컴포넌트를 불러오는 것을 확인할 수 있다.

또한 react-responsive 라이브러리를 쓰더라도 다음과 같은 에러가 발생한다.

에러 메세지를 보면 텍스트 컨텐츠가 서버에서 렌더링된 HTML과 일치하지 않는다고 한다. 해당 링크를 들어가보면 Next.js 공식 웹 사이트가 나온다.

Next.js - Text content does not match server-rendered HTML

해당 문서에 대한 설명에 따르면 이 에러는 서버에서 만들어진 리액트 트리와 첫 렌더링에서 만들어진 브라우저 내부 리액트 트리가 일치하지 않을 때 발생한다고 한다.

해당 공식문서에서는 해결책을 두 가지 제시하고 있다.

컴포넌트가 마운트된 이후 반응형 관련 로직을 useEffect 내부에 줘서 클라이언트 환경에서만 반응형 디자인을 통제하도록 하는 것과 해당 에러가 발생하는 컴포넌트만 서버 사이드 렌더링을 비활성화 시키는 것이다.

이 중 첫 번째 방법을 사용하기로 하였다.

3. 해결 방법

window 객체를 useEffect 내부 훅에서 참조하여 서버 단에서는 window 객체에 접근하지 않게 한다.

// hooks
import { useState, useEffect } from "react"
import { useMediaQuery } from "react-responsive"

일단 react-responsive 패키지에서 useMediaQuery 훅을 불러오고 컴포넌트 내부에서 제어할 useState, useEffect 훅도 react 패키지에서 불러온다.

// hooks
import { useState, useEffect } from "react"
import { useMediaQuery } from "react-responsive"

export default function Component(){

	// useMediaQuery 훅을 통해 현재 브라우저의 뷰포트가 최대 1024px인지 확인하는 불리언 형태의 변수를 생성한다.
	const isDesktop = useMediaQuery({
 	 query: "(min-width: 1024px)"
	})
    
    // 이후 useState 훅을 통해 해당 컴포넌트에서 desktop 뷰포트인지 식별할 수 있도록 상태를 선언한다.
    const [desktop, setDesktop] = useState(false);
    
    // useEffect 훅을 통해 useMediaQuery로 선언한 불리언 형태의 변수가 변경되면 해당 컴포넌트의 상태가 변화되게 설정한다.
    useEffect(() => {
    setDesktop(isDesktop)
  }, [isDesktop])
  
  return (
  	{desktop && <Component />}
  )
}
profile
프론트엔드를 희망했었던 화학공학과 취준생

0개의 댓글