swiper: Using breakpoints option in Next JS causes Hydration error

Discussed in https://github.com/nolimits4web/swiper/discussions/5776

<div type='discussions-op-text'>

Originally posted by Sakkhor909 June 7, 2022 First of all, thank you for this amazing library.

Everything works fine until I use breakpoints

import { Swiper, SwiperSlide } from "swiper/react";
import Image from "next/image";

 <Swiper

        slidesPerView={1}

        breakpoints={{
          1024: {
            slidesPerView: 3,
          },
        }}
 
      >
        {Data.images.map((imgSrc, index) => {
          return (
            <SwiperSlide>
              <Image src={imgSrc} alt={Data.name + index} layout="fill" />
            </SwiperSlide>
          );
        })}
      </Swiper>

The error is shown on the next js page

Error: Hydration failed because the initial UI does not match what was rendered on the server.
</div>

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 30
  • Comments: 25

Most upvoted comments

Using slidesPerView="auto" fixed the issue for me.

Sample Code for reference

<Swiper
        spaceBetween={10}
        slidesPerView="auto"
        breakpoints={{
          320: {
            slidesPerView: 1,
            spaceBetween: 30,
          },
          640: {
            slidesPerView: 1,
            spaceBetween: 10,
          },
          768: {
            slidesPerView: 2,
            spaceBetween: 20,
          },
          1024: {
            slidesPerView: 3,
            spaceBetween: 30,
          },
        }}
      >
              <SwiperSlide>Slide one</SwiperSlide>
      </Swiper>

Than same issue with me also

Using slidesPerView="auto" fixed the issue for me.

Sample Code for reference

<Swiper
        spaceBetween={10}
        slidesPerView="auto"
        breakpoints={{
          320: {
            slidesPerView: 1,
            spaceBetween: 30,
          },
          640: {
            slidesPerView: 1,
            spaceBetween: 10,
          },
          768: {
            slidesPerView: 2,
            spaceBetween: 20,
          },
          1024: {
            slidesPerView: 3,
            spaceBetween: 30,
          },
        }}
      >
              <SwiperSlide>Slide one</SwiperSlide>
      </Swiper>

Thank you for the solution. It works.

I don’t know is it dirty or not? but i fixed it by the code :

  const [customSwiperOptions, setCustomSwiperOptions] = useState<SwiperOptions>();

  useEffect(() => {
    const options: SwiperOptions = {
      breakpoints: {

        320: {
          slidesPerView: 1,
        },
        640: {
          slidesPerView: 2,
        },
        768: {
          slidesPerView: 3,
        },
        1024: {
          slidesPerView: 4,
        },
        1280: {
          slidesPerView: 5,
        },
        1536: {
          slidesPerView: 6
        }
      }
    }
    setCustomSwiperOptions(options);
  }, [])

 {
          customSwiperOptions &&
          <Swiper modules={[Navigation, Pagination, Scrollbar, A11y]}
            spaceBetween={20}
            navigation
            breakpoints={customSwiperOptions.breakpoints}
            pagination={{ clickable: true }}
            scrollbar={{ draggable: true }}>
            {
              myItems.map((x) => {
                return <SwiperSlide key={x.id} className='p-2'> <Item data={x}></Item></SwiperSlide>
              })
            }
          </Swiper>
}

Thank you for this, I feel this is the best solution on this thread!

I made some small simplifications:

` const [customSwiperOptions, setCustomSwiperOptions] = useState(null);

useEffect(() => { const options = { 640: { slidesPerView: 2, spaceBetween: 20, }, 768: { slidesPerView: 2, spaceBetween: 20, }, 1024: { slidesPerView: 3, spaceBetween: 20, }, 1200: { slidesPerView: 4, spaceBetween: 20, }, } setCustomSwiperOptions(options); }, [])

const testimonialCarousel = { slidesPerView: 1, spaceBetween: 20, loop: true, speed: 1000, centeredSlides: true, autoHeight: true, modules: [Autoplay], autoplay: { waitForTransition: false, delay: 4000, }, breakpoints: customSwiperOptions };

{customSwiperOptions && <Swiper {...testimonialCarousel}
    >
      {TESTIMONIALS_DATA.map((item, index) => (
        <SwiperSlide key={index}>
          {item.map(({ image, text, name, username }, _index) => (
            <TestimonialsCard
              image={image}
              text={text}
              name={name}
              key={_index}
              username={username}
              sx={styles.testimonialsCard}
            />
          ))}
        </SwiperSlide>
      ))}
    </Swiper>}`

Even if we keep swiper client side, isn’t it causing a huge CLS ?

If you decide to use slidesPerView="auto" you have to take into account that you have to control the CLS (Web Vitals) since doing this causes the images to move, in the first render they will be loaded automatically while in the next one it will exist a design change.

Using slidesPerView="auto" fixed the issue for me. Sample Code for reference

<Swiper
        spaceBetween={10}
        slidesPerView="auto"
        breakpoints={{
          320: {
            slidesPerView: 1,
            spaceBetween: 30,
          },
          640: {
            slidesPerView: 1,
            spaceBetween: 10,
          },
          768: {
            slidesPerView: 2,
            spaceBetween: 20,
          },
          1024: {
            slidesPerView: 3,
            spaceBetween: 30,
          },
        }}
      >
              <SwiperSlide>Slide one</SwiperSlide>
      </Swiper>

Thank you for the solution. It works.

I want to have a bit of the next slide peaking on the screen, so slidesPerView=auto won’t work for me- any other ideas or possible fixes you came up with?

I don’t know is it dirty or not? but i fixed it by the code :

  const [customSwiperOptions, setCustomSwiperOptions] = useState<SwiperOptions>();

  useEffect(() => {
    const options: SwiperOptions = {
      breakpoints: {

        320: {
          slidesPerView: 1,
        },
        640: {
          slidesPerView: 2,
        },
        768: {
          slidesPerView: 3,
        },
        1024: {
          slidesPerView: 4,
        },
        1280: {
          slidesPerView: 5,
        },
        1536: {
          slidesPerView: 6
        }
      }
    }
    setCustomSwiperOptions(options);
  }, [])

 {
          customSwiperOptions &&
          <Swiper modules={[Navigation, Pagination, Scrollbar, A11y]}
            spaceBetween={20}
            navigation
            breakpoints={customSwiperOptions.breakpoints}
            pagination={{ clickable: true }}
            scrollbar={{ draggable: true }}>
            {
              myItems.map((x) => {
                return <SwiperSlide key={x.id} className='p-2'> <Item data={x}></Item></SwiperSlide>
              })
            }
          </Swiper>
}

This actually makes sense. Your static build created the HTML without a screensize, so the default settings are run. When your JS runs for the first time, it will consider breakpoints, thus creating a mismatch in HTML elements.

<div className="relative w-full lg:mx-auto lg:max-w-7xl">
          <Swiper
            slidesPerView={3}
            spaceBetween={20}
            breakpoints={{
              768: {
                slidesPerView: 4,
                spaceBetween: 20,
              },
              1024: {
                slidesPerView: 5,
                spaceBetween: 10,
              },
              1280: {
                slidesPerView: 6,
              },
              1440: {
                slidesPerView: 7,
              },
            }}
          >
            {[...Array(7).keys()].map((item) => (
              <SwiperSlide key={item}>
                Slide {item}
               </SwiperSlide>
            ))}
          </Swiper>
        </div>

This is my solution. I have used Tailwind CSS, and wrapped Swiper with one more div, and gave width: 100%. it works ok

Is anyone still getting hydration error even after using slidesperview =“auto” ?

<Swiper
          spaceBetween={50}
          slidesPerView="auto"
          loopedSlides={3}
          pagination={{
            el: ".my-custom-pagination-div",
            clickable: true,
            renderBullet: (index, className) => {
              return '<span class="' + className + '">' + "</span>";
            },
          }}
          navigation={true}
          loop={true}
          centeredSlides={true}
          breakpoints={{
            992: {
              slidesPerView: 2,
            },
          }}
          onSlideChange={(swiperCore) => {
            const { activeIndex, snapIndex, previousIndex, realIndex } =
              swiperCore;
            setActiveApp(realIndex);
          }}

          // left and right carousels should be half
          // width of the slide
        >
          {data.map((item, index) => {
            return (
              <SwiperSlide key={index}>
                <DataCard
                  visits={visits}
                  item={item}
                  active={activeData === index}
                />
              </SwiperSlide>
            );
          })}

          {isDesktop && (
            <>
              <PrevOverlay />
              <NextOverlay />
            </>
          )}
        </Swiper>

Just want to clarify here, slidesPerView="auto" is not a fix, it’s a workaround.