출처
React/NextJS, SSR and Responsive Design
React 02, SSR vs Responsive Design
React, Combining Server-Side Rendering and Responsive Design
보통 리액트 프로젝트에서 장치 너비에 따라 반응형을 구현하려면 아래와 같이 한다.
Next.js 프로젝트에도 그대로 적용한다.
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 이벤트 핸들러를 딱 한 번 부여해주고 컴포넌트가 디마운트 됐을 때 해당 이벤트 핸들러를 제거해준다.
@media screen 키워드를 통해서 특정 뷰포트 너비에 따라 선택자의 CSS 코드를 변경한다.
@media screen and (max-width: 1024px) {
.content-wrap{
background-color:rgba(243, 246, 251, 1);
padding: 1rem;
}
}
출처 : 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>
}
서버 사이드 렌더링에서는 window 전역객체를 인식할 수 없다.
window 전역객체는 브라우저 환경에서만 인식할 수 있다. 따라서 서버 사이드에서 컴포넌트를 인식할 때는 window 객체를 읽을 수 업다.
따라서 window 객체를 이용하여 렌더링 조건을 설정했을 때는 제대로 적용이 안 된다.
위 사진을 보면 처음 렌더링 됐을 때 뷰포트 사이즈가 데스크톱 사이즈임에도 불구하고 모바일 컴포넌트를 불러오는 것을 확인할 수 있다.
또한 react-responsive 라이브러리를 쓰더라도 다음과 같은 에러가 발생한다.
에러 메세지를 보면 텍스트 컨텐츠가 서버에서 렌더링된 HTML과 일치하지 않는다고 한다. 해당 링크를 들어가보면 Next.js 공식 웹 사이트가 나온다.
Next.js - Text content does not match server-rendered HTML
해당 문서에 대한 설명에 따르면 이 에러는 서버에서 만들어진 리액트 트리와 첫 렌더링에서 만들어진 브라우저 내부 리액트 트리가 일치하지 않을 때 발생한다고 한다.
해당 공식문서에서는 해결책을 두 가지 제시하고 있다.
컴포넌트가 마운트된 이후 반응형 관련 로직을 useEffect 내부에 줘서 클라이언트 환경에서만 반응형 디자인을 통제하도록 하는 것과 해당 에러가 발생하는 컴포넌트만 서버 사이드 렌더링을 비활성화 시키는 것이다.
이 중 첫 번째 방법을 사용하기로 하였다.
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 />}
)
}