개발/프로젝트

Tailwind CSS + SCSS로 반응형 구현하기

xuwon 2025. 2. 25. 14:54

기존 Tailwind CSS를 사용하고 있었고, 모바일뷰, 태블릿뷰, 데스크탑뷰 이렇게 3개의 반응형을 구현해야 했다.

멘토님께서 BP가 많아서 복잡해질 가능성이 있다고, 아예 뷰마다 컴포넌트를 분리하라고 하셨는데
제대로 된 방법을 몰라서 그런지 코드 중복이 너무 많아졌고, 유지보수에도 문제가 있을 것 같았다.

그래서 또 알려주신 방법이, Tailwind CSSSCSS를 함께 사용하는 것!

return (
    <div className="pb-12">
      <Header />
      <div className="flex flex-col items-center gap-20 md:gap-32 px-mobile md:px-tablet lg:px-desktop">
        {/* 인기 웹툰 */}
        <div className="flex flex-col transform relative left-0 h-[300px] md:w-[704px] lg:h-[400px] xl:h-auto xl:w-[1121px]">
          <p className="text-center text-sm text-black md:text-base xl:text-xl">
            인기 웹툰
          </p>
          <div className="relative w-[400px] overflow-visible pt-7 md:w-full md:pt-14">
            {breakpoint === "mobile" ? (
              <Swipers>
                {popularData.map((webtoonData, idx) => (
                  <WebtoonCardColMobile key={idx} data={webtoonData} />
                ))}
              </Swipers>
            ) : (
              <Swipers>
                {popularData.map((webtoonData, idx) => (
                  <WebtoonCardCol key={idx} data={webtoonData} />
                ))}
              </Swipers>
            )}
          </div>
        </div>

        {/* 웹툰 목록 */}
        <div className="flex flex-col max-w-[672px] md:w-[704px] xl:w-[1121px]">
          <p className="origin-left text-sm text-black md:text-base xl:text-xl">
            웹툰 목록
          </p>
          <div className="flex flex-col items-center gap-4 self-center pt-7 md:flex-wrap md:justify-center md:gap-x-10 md:gap-y-7 md:pt-10 xl:pt-14">
            <PaginationList data={data}>
              {(webtoonData) =>
                breakpoint === "mobile" ? (
                  <WebtoonCardMobile data={webtoonData} />
                ) : (
                  <WebtoonCard data={webtoonData} />
                )
              }
            </PaginationList>
          </div>
        </div>
      </div>
    </div>
  );
}

이렇게 md:, xl: 범벅으로 테일윈드 코드가 길어지고 가독성이 좋지 않다.

이걸 SCSS를 사용해서 분리해보자.

pnpm add sass

우선 sass 패키지를 설치해 준다.

그리고 scss 파일을 생성해 주면 되는데, Next.js를 사용할 경우 .module.scss로 파일명을 지정해줘야 한다!

 

// main.module.scss

// 인기 웹툰
.mobile {
  .popular_webtoon_container {
    @apply relative left-0 h-[300px];
  }

  .popular_webtoon_text {
    @apply text-center;
  }

  .popular_webtoon_swiper {
    @apply w-[400px] overflow-visible pt-7;
  }
}

.tablet,
.desktop {
  .popular_webtoon_container {
    @apply w-[704px] lg:h-[400px] xl:h-auto xl:w-[1121px];
  }

  .popular_webtoon_text {
    @apply md:text-base xl:text-xl;
  }

  .popular_webtoon_swiper {
    @apply w-full pt-14;
  }
}

// 웹툰 목록
.mobile {
  .webtoon_container {
    @apply max-w-[672px];
  }

  .webtoon_text {
    @apply origin-left md:text-base xl:text-xl;
  }

  .webtoon_pagination {
    @apply flex-col items-center gap-4 self-center pt-7;
  }
}

.tablet,
.desktop {
  .webtoon_container {
    @apply h-auto w-[704px] xl:w-[1121px];
  }

  .webtoon_text {
    @apply md:text-base xl:text-xl;
  }

  .webtoon_pagination {
    @apply flex-wrap justify-center gap-x-10 gap-y-7 pt-10 xl:pt-14;
  }
}

그리고 모바일뷰, 태블릿뷰, 데스크탑뷰에서 쓰이는 스타일들을 작성해준다.
나는 중복되는 코드들은 빼고 각 뷰에 해당하는 스타일들만 써주었다.

scss 파일을 import 한 뒤, className에 지정해주면 끝!

클래스명도 직관적이라 나중에 수정할 때도 편리하고 무엇보다 가독성이 좋아져서 좋다.

import s from "./main.module.scss";

const breakpoint = useBreakpoint(); // 화면 크기 확인하는 훅

return (
    <div className="pb-12">
      <Header /> 
      <div
        className={clsx(
          "flex flex-col items-center gap-20 tablet:gap-32",
          "px-mobile md:px-tablet lg:px-desktop",
          s[breakpoint], {/* BP에 따라 스타일 적용 */}
        )}
      >
        {/* 인기 웹툰 - 데스크탑 & 태블릿 */}
        <div
          className={clsx(
            "flex transform flex-col",
            s.popular_webtoon_container,
          )}
        >
          <p className={`${s.popular_webtoon_text} text-sm text-black`}>
            인기 웹툰
          </p>
          <div className={`${s.popular_webtoon_swiper} relative`}>
            {breakpoint === "mobile" ? (
              <Swipers>
                {popularData.map((webtoonData, idx) => {
                  return <WebtoonCardColMobile key={idx} data={webtoonData} />;
                })}
              </Swipers>
            ) : (
              <Swipers>
                {popularData.map((webtoonData, idx) => {
                  return <WebtoonCardCol key={idx} data={webtoonData} />;
                })}
              </Swipers>
            )}
          </div>
        </div>

        {/* 웹툰 목록 - 데스크탑 & 태블릿 */}
        <div className={clsx("flex flex-col", s.webtoon_container)}>
          <p className={`${s.webtoon_text} text-sm text-black`}>웹툰 목록</p>
          <div className={`${s.webtoon_pagination} flex`}>
            <PaginationList data={data}>
              {(webtoonData) =>
                breakpoint === "mobile" ? (
                  <WebtoonCardMobile data={webtoonData} />
                ) : (
                  <WebtoonCard data={webtoonData} />
                )
              }
            </PaginationList>
          </div>
        </div>
      </div>
    </div>
  );
}