React, Swiper, Image 컴포넌트 렌더링 오류
처음에 잘 나오던 메인 홈페이지 캐러셀이 작업중간 부터 렌더링후 배경색만 나오는 오류가 생겼다.
✅ 로딩? 렌더링? 무엇이 문제인가?
확인1.
F12 Elements 탭에서 보면 <img src="..." /> 태그는 존재 → 렌더링 OK
확인2.
http://localhost:3000/assets/carousel/carousel1.jpg
주소창에 입력해보니 이미지가 잘 나온다 → 로딩 OK
로딩(Loading)
<img> 또는 <Image> 태그가 참조하는 이미지 파일이 실제로 서버에서 내려받아지는 것
즉,
- 네트워크를 통해 이미지 파일을 요청하고
- 브라우저가 성공적으로 응답받아 표시하는 과정
렌더링(Rendering)
페이지 구조상 <Image> 태그 자체가 DOM에 표시되는 것
즉,
- 컴포넌트가 mount되고
- HTML 구조가 브라우저에 그려지는 과정
❌ 하지만:
Swiper 구조가 꼬이면서 부모 div 또는 .swiper-slide의 height가 0,
또는 이미지 자체가 opacity: 0, visibility: hidden, display: none 등으로
"화면에는 보이지 않았던 것" → 렌더링 문제
브라우저는 이미지를 받아왔지만,
Swiper의 잘못된 레이아웃 계산 때문에 그걸 "보여주지 못한" 상태.
즉, "이미지는 로딩됐지만, 시각적으로 렌더링이 실패"한 상황
개발자 도구를 보니 돔이 그려지고 나서 width가 이상하다.
(정확한 용어로: 컴포넌트가 렌더링되어 DOM에는 존재하지만, 브라우저가 계산한 width 값이 잘못되었다.)
<div class="swiper-wrapper" style="transition-duration: 0ms; transform: translate3d(-60px, 0px, 0px); transition-delay: 0ms;">
<div class="swiper-slide" data-swiper-slide-index="0" style="width: 3.35544e+07px; margin-right: 30px;">
<div class="w-full h-[500px] min-h-[500px] overflow-hidden bg-peach-300">
<img alt="배너1" width="1920" height="500" decoding="async" data-nimg="1" class="object-cover" style="color:transparent" src="/assets/carousel/carousel1.jpg">
</div>
</div>
width: 3.35544e+07px; 가 어떻게 나오는거지?
슬라이드 하나당 너비가 33,554,400px (즉, 3천만 픽셀 넘음)
이건 정상적인 Swiper의 슬라이드 width가 아니며, 캐러셀이 비정상적으로 무한하게 확장되고 있는 상태입니다.
라고 쳇지피티도 판단했다.
📉 이로 인해 발생하는 현상
현상 | 원인 |
이미지 영역이 초대형으로 계산됨 | 슬라이드 width가 비정상적 (3천만 px) |
이미지가 translate3d(-60px, 0, 0)으로 살짝 이동함 | 전체 슬라이드가 과도하게 길어서 Swiper 위치 계산 망가짐 |
결국 img는 렌더는 되지만 색상만 있음(style="color:transparent") | Next.js가 이미지가 시야에 없다고 판단하고 렌더링을 지연시키는 상태 |
🎯 원인은 대부분 다음 중 하나입니다
- Swiper의 container width가 0 또는 작게 인식되어 계산이 망가짐
- SwiperSlide의 width를 Swiper가 제대로 계산하지 못함
- CSS 또는 부모 구조가 display: none 상태에서 Swiper가 마운트됨 → 슬라이드 width가 Infinity처럼 계산됨
그렇다면 해결책은?
Carousel.tsx를 감싸는 부모에 width를 설정.
<main className="min-h-screen ">
<section className="text-center">
<Carousel />
...
</section>
</main>
main에 w-full 적용
과정,
처음엔 <Image> 컴포넌트 안에 width, height를 적용했다가 이후에 <Image>컴포넌트를 감싸는 div에 적용해서 레이아웃 문제를 해결해보려 했다. 그러다 문제는 부모 컴포넌트 란걸 깨닫게 됐다.
그 후에 section에 w-full을 적용했다가 그 상위인 main의 width가 지정되어 있지 않아 Swiper가 너비를 0으로 계산해버리는 문제가 발생. 그래서 최종적으로 main에 w-full올 적용, 그렇게 에러를 해결했다.
(section은 main의 넓이를 상속받아서 width가 100%가 됨.)
정리하면,
section에 w-full이 있어도, 상위인 main이 너비를 갖고 있지 않으면 Swiper는 슬라이드 너비를 제대로 계산하지 못하게 된다.
최종코드
[page.tsx]
import Carousel from "@/components/carousel/Carousel";
// app/page.tsx: 메인 페이지 내용만 표시됨
export default function Home() {
return (
<main className="min-h-screen w-full">
<section className="text-center">
<Carousel />
<p className="text-lg mt-8">쇼핑몰 홈 화면입니다.</p>
<p className="text-gray-500 mt-2">
상단 바, 메인페이지, 푸터는 Layout.tsx를 통해 자동 적용됩니다.
</p>
</section>
</main>
);
}
[Carousel.tsx]
"use client";
import Image from "next/image";
import { Autoplay, Navigation, Pagination } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/pagination";
const Carousel = () => {
return (
<Swiper
modules={[Navigation, Pagination, Autoplay]}
slidesPerView={1} // 한 화면에 보일 슬라이드 수
spaceBetween={0} // 슬라이드 간 간격 (px)
loop // 슬라이드 반복 여부
autoplay={{ delay: 3000, disableOnInteraction: false }} // 자동 재생 설정
pagination={{ clickable: true }} // 페이지 점 표시 설정
navigation={true} // 자동 재생 설정
onSlideChange={(swiper) => {
console.log("🔄 슬라이드 변경:", swiper.activeIndex);
}}
onInit={(swiper) => {
swiper.update();
}}
>
{Array.from({ length: 11 }).map((_, idx) => (
<SwiperSlide key={idx}>
<div className="w-full h-[500px] min-h-[500px] overflow-hidden bg-peach-300">
{/* <div> */}
<Image
src={`/assets/carousel/carousel${idx + 1}.jpg`}
alt={`배너${idx + 1}`}
fill
priority={idx === 0}
className="object-cover"
/>
</div>
</SwiperSlide>
))}
</Swiper>
);
};
export default Carousel;