Skip to main content

Command Palette

Search for a command to run...

[북잡 프로젝트] 반응형 모바일 헤더 vs 기본 헤더 구현하기

Updated
•4 min read•View as Markdown
[북잡 프로젝트] 반응형 모바일 헤더 vs 기본 헤더 구현하기

이번 프로젝트에서 팀원 대신 헤더 퍼블리싱을 맡아서 하기로 했다 🙌🏻

이제까지 했던 프로젝트에서는 웹앱으로 구현했기 때문에 헤더 부분이 크게 다른 게 없었는데 이번에는 웹이랑 앱 UI를 완전히 다르게 디자인해서 퍼블리싱 자체도 다르게 하기로 결정!

👉🏻 (피그마) 모바일 UI

👉🏻 웹 UI

완전히 다른것을 확인할 수 있다.

//layout
import Header from '../components/web/Header'
import Footer from './../components/web/Footer'

const Layout = ({ children }) => {
  return (
    <div className='flex flex-col min-h-screen'>
      <Header />
      <main className='flex flex-col justify-center flex-1 mx-4 my-4 sm:my-10 sm:mx-10'>
        {children}
      </main>
      <Footer email='bookjob@gmail.com' />
    </div>
  )
}

export default Layout

이렇게 web header를 전체적으로 적용해준 상태이다.

📲 모바일 헤더 적용하기

🧐 고민한 부분

처음에는 useState()랑 useEffect를 사용해서 직접 window.innerWidth를 사용해줘야하나..? 라는 고민을 했다.

이렇게 해도 되겠지만 코드가 길어진다는 단점이 있었다..!

현재는 React + JS로 구현하고 있지만, 추후 팀원과 함께 Next.js로 전환할 계획이 있어 SSR 환경에서 초기값 차이로 인한 hydration mismatch 위험을 고려하게 되었다. 이런 점에서 기존 방식에 한계를 느끼고 다른 접근 방식을 고민하게 되었다.

서버 사이드 렌더링에선 window가 없기 때문에 에러 또는 UI mismatch가 발생 가능

그래서 도입한 해결책은 💡 react-responsive

여기서 useMediaQuery를 사용하기로 결정했다.

  • SSR 호환성

  • 자동 업데이트

  • 재사용/유지보수성

  • 테스트 용이성

장점들이 있었고..! 추후 Next.js로 전환할 계획까지 생각하면 최적의 방법이라는 생각을 했다👍🏻

//설치 명령어
yarn add react-responsive
//현재 화면이 모바일인지 아닌지를 판단하는 부분을 훅으로 빼주었다
import { useMediaQuery } from 'react-responsive'

const useIsMobile = () => {
  return useMediaQuery({ maxWidth: 639 })
}

export default useIsMobile
//layout
import Header from '../components/web/Header'
import MobileHeader from '../components/app/MobileHeader'
import MobileMainHeader from '../components/app/MobileMainHeader'
import useIsMobile from '../hooks/header/useIsMobile'
import Footer from './../components/web/Footer'

const Layout = ({ children, headerType }) => {
  const isMobile = useIsMobile()

  return (
    <div className='flex flex-col min-h-screen'>
      {/* isMobile이 true일 때만 MobileHeader 또는 MobileMainHeader를 하나만 렌더링 */}
      {isMobile ? headerType === 'main' ? <MobileMainHeader /> : <MobileHeader /> : <Header />}
      <main className='flex flex-col justify-center flex-1 mx-4 my-4 sm:my-10 sm:mx-10'>
        {children}
      </main>
      <Footer email='bookjob@gmail.com' />
    </div>
  )
}

export default Layout

모바일 헤더도 모바일 메인 헤더랑 모바일 헤더로 나누어져 있어서 … 추후에 로그인 여부에 따라 다르게 주기도 해야하겠지만 일단은 메인이랑 회원가입 로그인을 제외하고는 모바일 헤더 + label로 주는 것이 맞다고 판단해서 구분해주었다.

MainPage

처음에는 메인페이지에서 모바일 메인 헤더랑 + 모바일 헤더가 같이 랜더링이 되는 문제를 겪었다. 아무래도 첫 Layout을 팀원이 설정해줘서 길게 가지고 간 어려움은 아니었지만 그래도 역시,, 팀원이 짠 코드도 열심히 봐야하는 것을 또 한번 느꼈다.

문제점

import { Route, Routes } from 'react-router-dom'
import routes from './routes'
import Layout from './Layout'

const AppRoutes = () => {
  return (
    <Layout>
      <Routes>
        {routes.map((route, index) => (
          <Route key={index} path={route.path} element={route.element} />
        ))}
      </Routes>
    </Layout>
  )
}

export default AppRoutes

바로 이 부분이 문제였다. Layout으로 전체를 감싸고 있어서 MainPage에서도 Layout + 적용한 헤더으로 중복 적용이 된 것

import { Route, Routes } from 'react-router-dom'
import routes from './routes'
import Layout from './Layout'

const AppRoutes = () => {
  return (
    <Routes>
      {routes.map((route, index) => {
        const isMain = route.path === '/'

        return (
          <Route
            key={index}
            path={route.path}
            element={<Layout headerType={isMain ? 'main' : undefined}>{route.element}</Layout>}
          />
        )
      })}
    </Routes>
  )
}

export default AppRoutes

headerType isMain이 true일 때 'main'을 전달하고 그렇지 않으면 undefined를 전달하게 해주었다.

(헤더만 봐주세요 ㅋㅋ)잘 적용된다.!!

그러면 여기서 또 메인모바일 헤더가 아니라 모바일헤더 + 각 페이지마다 label을 적용하는 방법!

import { Route, Routes } from 'react-router-dom'
import routes from './routes'
import Layout from './Layout'

const AppRoutes = () => {
  return (
    <Routes>
      {routes.map((route, index) => {
        const isMain = route.path === '/'

        return (
          <Route
            key={index}
            path={route.path}
            element={
              <Layout headerType={isMain ? 'main' : 'sub'} label={route.label}>
                {route.element}
              </Layout>
            }
          />
        )
      })}
    </Routes>
  )
}

export default AppRoutes
//routes 
 {
    path: ROUTER_PATHS.WRITE_COMMUNITY_POST,
    element: <WriteCommunityPostPage />,
    label: '자유게시판 글 등록',
  },

모바일 헤더 + label은 이렇게 routes에 label을 넣어주면 된다.

완성했다…!✨

😼 느낀점

처음엔 간단할 줄 알았지만 실제 구현은 생각보다 복잡했다. 모바일과 웹 환경에서 서로 다른 헤더를 보여주기 위해 라우트마다 분기 처리를 하고 필요한 label 값도 동적으로 넘겨야 했기 때문..!

특히 반응형 처리에서 단순히 window.innerWidth를 쓰는 게 아니라 React의 흐름에 더 맞는 방식인 react-responsive를 사용하기로 결정했다. 이렇게 함으로써 상태 변화에도 안정적으로 대응할 수 있고 재사용성과 테스트 측면에서도 이점이 있다고 판단했다. 단순히 헤더를 구현하는 것도 방법이 여러가지가 있고 선택해야 한다는 부분이 흥미로웠다. ✏️

More from this blog

[React] 클라이언트에서 이미지를 압축해보자

우리는 서버 비용을 직접 부담하고 있기 때문에 항상 최적화와 비용 절감 방법을 고민하게 된다. 현재 자유게시판이 활성화가 많이 되지는 않았지만 사용자들이 업로드하는 이미지가 평균 5MB 이상일 경우를 대비해서 클라이언트에서 이미지 압축을 진행했다. 웹에서는 크게 두 가지 방법이 있었는데 Canvas API를 직접 사용하는 방법 browser-image-compression 라이브러리를 활용하는 방법 이 있다. 여러 장점을 고려해 brow...

Oct 8, 20252 min read
[React] 클라이언트에서 이미지를 압축해보자

[책 추천] 프론트엔드 개발자라면 반드시 알아야 할 '웹 접근성' 이야기

내가 생각하는 프론트엔드 개발자는 단순히 보이는 화면만 구현하는 것을 넘어서 누구나 접근할 수 있는 웹을 만드는 데 중요한 역할을 해야 한다고 생각한다. 실무에 바로 적용하는 웹 접근성 가이드북 이번에 이 책을 받고 프엔들이 읽기에 너무 좋다고 생각해서 블로그에 추천 글까지 적게 되었다 ! 목차 Chatper1. 쉽게 이해하는 접근성 Chapter2. 웹 접근성의 기초 Chapter3. HTML 태그, 의미 있게 사용하기 Chpater4...

Jul 27, 20255 min read
[책 추천] 프론트엔드 개발자라면 반드시 알아야 할 '웹 접근성' 이야기

[Next.js] 나만의 학습 블로그 만들기#3-다국어 지원 (Feat.next-i18next)

오늘은 다국어 지원 가능한 기능 구현 과정을 적어보려고 한다. 원래는 댓글기능보다 먼저 구현하려고 했는데 ,, 갑자기 댓글기능 알아보다가 재밌어서 먼저 끝내버렸다,,,🙄 시작하기 전에 정말 어려웠다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 어려움이 많아서 기능 적용하는 데 시간이 꽤 걸렸다 ,,, 다국어 지원 기능 나는 영어랑 일본어를 추가해줬다 ! (해외취업도 관심 있기 때문에 나중에 이력서 낼 때 도움이 되지 않을까? ..🤭) 다국어 구현 방법 구현 방...

Jul 15, 20254 min read
[Next.js] 나만의 학습 블로그 만들기#3-다국어 지원 (Feat.next-i18next)

[Next.js] 나만의 학습 블로그 만들기#4 - 댓글 기능(Feat. Giscus)

댓글기능이 있으면 무조건 재밌을 거 같아서 넣어보려고 한다🤭 아무래도 개발에 관련된 학습 블로그라서 개발자들이 많을 것으로 예상했다 ! 그래서 깃허브 이슈 기반 댓글 시스템을 기반으로 기능을 구현해보려고 한다. 👉🏻 도움이 많이 된 블로그 ! 방법 Giscus | (https://giscus.app) utterances | (https://utteranc.es) 알아봤을 때는 이렇게 두 개가 유명하다고 한다. 그럼 각각의 특징과 장...

Jul 14, 20253 min read
[Next.js] 나만의 학습 블로그 만들기#4 - 댓글 기능(Feat. Giscus)

[React] React + localStorage로 하루에 한 번만 보이는 팝업 만들기

이번 프로젝트에 설문조사 배너를 제작했는데… 흠… 뭔가 별로라는 의견이 많았다! (팀원들 의견) 🙄 디자인이 문제일까? 해서 래퍼런스를 많이 찾아봤는데 … ! 일반 웹사이트는 설문조사 배너 자체를 안 만들어요,,,🤭 충격 그 자체 ( 난 아직 멀었음 ) 그리고 알아보니깐.. 설문조사 배너는 사용자 경험을 해치기 쉬워 잘 쓰이지 않는다고 한다! 그래서 나는 모달 팝업 방식으로 전환하기로 결정했고 실무에서 자주 사용하는 패턴으로 구현했다. 방...

Jul 9, 20253 min read
[React] React + localStorage로 하루에 한 번만 보이는 팝업 만들기
S

subin-dev-blog

27 posts

끊임없이 배우기 위해 노력합니다. 🌱