WEB/Next.js

Next.js + mobile responsive [react-responsive]

chan 2021. 10. 12. 00:55
반응형

# Responsive web

리액트를 가지고 개발하면서 여러 기기의 디바이스 사이즈에 대응하여 반응형 웹 페이지를 만드는 방법에는 여러 가지가 있지만,

css media query 말고 js 단에서 반응형 다이나믹 스타일을 구현하고 싶다면 대표적으로 react-responsive 라는 라이브러리가 있다.

 

react-responsive 에서 대표적으로 제공하는 훅인 useMediaQuery 가 있는데, 사용 예시는 다음과 같다.

const isMobile = useMediaQuery({ query: "(max-width: 767px)"}); // true or false

 

컴포넌트 내부에서 해당 훅을 호출하여, 위와 비슷한 방식으로 디바이스 종류를 나누어 스타일을 대응시키면 되는데, 보통의 SPA(single page application) 방식으로 웹을 개발할 것이라면 언급한 방식에 따라 개발하는데 무리가 없다. 위의 useMediaQuery 는 react hook 이며 window resize 이벤트에 실시간으로 반응한다.

 

# Server Side Rendering

그러나 퍼포먼스, 혹은 검색 최적화 등 모종의 목적을 위해 서버사이드 렌더링을 구현해야 하는 상황이라면, 위의 훅을 사용할 때 어려움을 겪을 수 있다.

 

그 이유는 서버사이드 렌더링이 기존 CSR 과 다르게 서버에서 리액트 코드를 js, css, html 등으로 변환하여 응답하는 방식에서 기인되는데, 최초 응답 시 window 객체 초기화가 되지 않아 window 관련된 정보 (window.innerWidth 등) 를 참조하지 않은 데이터가 전송된다.

(+ 컴포넌트 마운트가 되지 않은 상황에서 window 객체를 호출하면 window is undefined 에러가 발생하게 된다.)

 

따라서 위의 isMobile 이라는 변수를 분기로 스타일링을 했다 하더라도, 새로 고침 했을 때 나타나는 화면은 isMobile 이라는 변수에 의해 분기되지 않은 기본 스타일이 적용되어 있게 된다. 

즉 해당 훅을 Next.js 에서 올바르게 사용하려면, 추가 작업을 해주어야 한다.

 

# Component mounted

물론 Next.js 를 활용해서 서버사이드 렌더링을 구현한다고 해도, React 를 사용해 웹을 구현한다면 클라이언트 사이드에서 컴포넌트들이 마운트 되었을 때 isMobile 변수의 정보를 반영시키면 된다. 

컴포넌트 마운트 되는 시기, 호출되는 함수는 React lifecycle 을 참고하면 된다. 

 

예를 들면 다음과 같다.

function App() {
    const [isMobile, setIsMobile] = useState(false);
    const mobile = useMediaQuery({ query: "(max-width: 767px)"});
    
    useEffect(() => {
    	if(mobile) setIsMobile(mobile);
    }, [mobile])
    
    return <div style={ { 
    	display: 'flex',		
        flexDirection: isMobile ? 'column' : 'row',
    } } />
}

컴포넌트가 마운트 되고, 그에 따라 호출되는 useEffect 훅 내부에서 isMobile 상태변수를 업데이트 시켜 컴포넌트를 재랜더링 시키는 방법이다. 

컴포넌트가 한번 더 렌더링 되는 단점이 있으나, 서버 사이드 렌더링과 다이나믹 한 반응형 웹디자인을 동시에 잡을 수 있으므로 나쁘지 않은 방법이라는 생각이 든다.

 

물론 이 외의 해결책으로 컴포넌트를 dynamic import 한다던가, getInitialProps 함수에서 device정보를 얻어온다던가 할 수 있지만, 전자는 서버사이드 렌더링의 장점을 포기하는 방법이고, 후자는 window resizing에 다이나믹하게 반응하지 못한다는 단점이 있다.

 

위의 코드는

커스텀 훅을 사용하면 더욱 간단하게 구현할 수 있다.

export function useIsMobile() {
    const [isMobile, setIsMobile] = useState(false);
    const mobile = useMediaQuery({ query: "(max-width: 767px)"});
    useEffect(() => {
        setIsMobile(mobile);
    }, [mobile]);
    return isMobile;
}
function App() {
    const isMobile = useIsMobile();
    
    return <div style={ { 
    	display: 'flex',		
        flexDirection: isMobile ? 'column' : 'row',
    } } />
}
반응형