SwipiDocumentation

Guides

Server rendering

What the server sends, why hydration stays silent and which parts of the UI appear only after the first measurement.

It renders on the server

Nothing the hook does during render touches window, document, ResizeObserver or MutationObserver. Every measurement lives in an effect, and the effects that need the layout fall back to useEffect when there is no window, so renderToString stays silent on React 18 as well as on React 19.

What the server sends

Your markup, with an unmeasured carousel behind it:

React
const [carouselRef, carousel] = useSwipiCarousel()

carousel.slidesCount   // 0
carousel.snapCount     // 0
carousel.hasOverflow   // false
carousel.canScrollNext // false

The first client render returns the same values, so the markup hydrates without a mismatch; the measurement lands right after, before the browser paints. The slides themselves are in the HTML from the start — it is only the numbers derived from measuring that arrive a moment later.

React
// snapCount is 0 until the first measurement,
// so render the dots from data you already have
{items.map((item, index) => (
  <button
    type="button"
    key={item.id}
    aria-label={`Go to slide ${index + 1}`}
    aria-current={index === carousel.selectedIndex}
    onClick={() => carousel.scrollTo(index)}
  />
))}

Next.js and Remix and friends

The hook is a hook, so the component holding it is a client component — in the Next.js app router that means a 'use client' directive at the top of the file. Everything around it, including the data fetching and the slides themselves, can stay on the server.

Carousel.jsx
'use client'

import { useSwipiCarousel } from '@midstem/swipi-react'

export const Carousel = ({ items }) => {
  const [carouselRef, carousel] = useSwipiCarousel({ loop: true })

  return (
    <div className="carousel__viewport" ref={carouselRef}>
      {/* … */}
    </div>
  )
}
  • No stylesheet is imported by the package, so there is nothing to add to a root layout.
  • Static export works the same way — this documentation is one.
  • A carousel measured to zero on the server is the reason a hidden carousel (a closed tab panel, display: none) re-measures when it becomes visible: sizes are watched, not cached from the first render. More in Troubleshooting.

@ Copyright 2026 Midstem. All rights reserved.

DocsMidstemGitHubLinkedin