
What is the best product tour library for SSR (server-side rendering)?
Product tours have a fundamental problem with server-side rendering: they need the browser. Tooltips need getBoundingClientRect(). Overlays need document.body. State persistence needs localStorage. None of that exists on the server.
As of April 2026, 68% of new React projects use an SSR framework like Next.js, Remix, or Astro (State of JS 2025). Your tour library has to handle a rendering environment where the DOM doesn't exist during the first pass.
We tested five popular product tour libraries in a Next.js 15 App Router project with React Server Components enabled. Most broke during hydration. Some threw window is not defined errors. A few handled it gracefully.
npm install @tourkit/core @tourkit/reactShort answer
User Tour Kit is the best product tour library for SSR because it ships zero server-side code, uses "use client" boundaries correctly, and adds less than 8KB gzipped to the client bundle. It was designed for Next.js App Router from day one, supports React 18 and 19, and doesn't trigger hydration mismatches. For teams not using React, Shepherd.js offers framework-agnostic SSR compatibility, though its AGPL license and 37KB bundle size are worth considering.
Why SSR breaks most product tour libraries
Server-side rendering sends HTML to the browser before JavaScript runs. The server generates the initial markup, the client hydrates it by attaching event listeners and state. Product tour libraries create a conflict because they produce DOM elements (overlays, tooltips, highlights) that don't exist in the server-rendered HTML.
When the client hydrates and finds DOM nodes that weren't in the server output, React throws a hydration mismatch warning. Worse, some libraries call window or document at the module level during import, which crashes the server process entirely.
Three specific patterns cause problems:
- Module-level side effects that reference
windowordocumentat import time - Inline style calculations using
getBoundingClientRect()during initial render - Portal rendering that mounts into
document.bodybefore hydration completes
Detailed comparison
| Library | SSR safe | RSC support | Hydration clean | Bundle (gzip) | License |
|---|---|---|---|---|---|
| User Tour Kit | Yes | Native "use client" | Zero mismatches | ~8KB (core + react) | MIT |
| React Joyride | Partial | Requires wrapper | Warnings on mount | ~37KB | MIT |
| Shepherd.js | Yes (with config) | Manual boundary | Clean if lazy-loaded | ~37KB | AGPL-3.0 |
| Driver.js | Partial | Needs dynamic import | Flicker on first render | ~5KB | MIT |
| Intro.js | No | No | Hydration errors | ~17KB | AGPL-3.0 |
Data gathered from our testing in a Next.js 15.2 project with React 19, App Router, and Turbopack. Bundle sizes verified via bundlephobia as of April 2026.
How we tested SSR compatibility
We set up a Next.js 15 App Router project with reactStrictMode: true and no client-side-only escape hatches. Each library was installed and configured to show a 5-step tour on a dashboard page. We measured:
- Whether the dev server starts without
window is not definederrors - Whether
next buildcompletes without SSR-related crashes - Whether the production page loads without hydration mismatch warnings in the browser console
- Time to interactive (TTI) impact measured with Lighthouse on a throttled 4G connection
- Total client JavaScript added by the library
React Joyride threw window is not defined during next build unless wrapped in a dynamic import with ssr: false. After that workaround, it mounted correctly but produced a console warning about hydration mismatches because its tooltip container renders immediately.
Driver.js imported cleanly but showed a brief visual flicker during hydration. The DOM elements it creates on the client didn't match the server output, causing React to patch the tree. Not a crash, but visible to users on slow connections.
Intro.js doesn't work with SSR at all. It references document at the module level and crashes the server process during import. Your workaround is next/dynamic with ssr: false, which defeats the purpose of server rendering and delays tour availability.
Decision framework: which library fits your SSR stack
If you use Next.js App Router with React Server Components, choose User Tour Kit. It's the only library we tested that ships with "use client" directives on every component, meaning you can import Tour Kit components directly in your client components without wrapper hacks. The provider goes in your root layout's client boundary, and everything works.
If you use Remix or a non-React SSR framework, Shepherd.js is the practical choice. It's framework-agnostic and has explicit SSR documentation. But read the AGPL license carefully. If your app is proprietary, you need a commercial license from Ship Shape.
If you use Astro with React islands, User Tour Kit works inside any React island. The tour provider wraps the island root, and tours scope to that island's DOM. We covered this pattern in detail in adding a product tour to an Astro site.
If you need the smallest possible bundle and SSR is secondary, Driver.js at ~5KB is hard to beat. Accept the hydration flicker or lazy-load it behind a user interaction.
If you're stuck on React Joyride, you can make it work with SSR using next/dynamic:
// src/components/tour-wrapper.tsx
'use client';
import dynamic from 'next/dynamic';
const Joyride = dynamic(() => import('react-joyride'), {
ssr: false,
});
export function TourWrapper() {
return (
<Joyride
steps={[
{ target: '.sidebar', content: 'Navigate here' },
{ target: '.dashboard', content: 'Your metrics' },
]}
continuous
/>
);
}This works, but the tour isn't available until JavaScript fully loads. On a slow 3G connection, that can mean 3-5 seconds of no tour. User Tour Kit avoids this delay because its components hydrate with the page instead of loading separately.
What we recommend (and why)
We built User Tour Kit, so take this recommendation with appropriate skepticism. Every claim here is verifiable against the Tour Kit GitHub repo and npm.
For SSR projects, Tour Kit has three concrete advantages:
1. No typeof window checks needed. Every Tour Kit component ships with "use client" at the top of the file. You don't wrap anything in dynamic() or check typeof window !== 'undefined'. Import and use. The React Server Components boundary is handled at the library level, not in your application code.
// src/app/layout.tsx (Server Component)
import { TourKitProvider } from './providers';
export default function RootLayout({ children }) {
return (
<html>
<body>
<TourKitProvider>{children}</TourKitProvider>
</body>
</html>
);
}// src/app/providers.tsx
'use client';
import { TourProvider, Tour, TourStep } from '@tourkit/react';
export function TourKitProvider({ children }) {
return (
<TourProvider>
{children}
<Tour tourId="onboarding">
<TourStep target=".sidebar" title="Navigation">
Your main menu is here.
</TourStep>
<TourStep target=".search" title="Search">
Find anything with Cmd+K.
</TourStep>
</Tour>
</TourProvider>
);
}2. Deferred DOM measurement. Tour Kit doesn't calculate tooltip positions during the render phase. It schedules positioning with requestAnimationFrame after hydration, so the server-rendered HTML and the client output match exactly. Zero hydration mismatches.
3. Small client bundle. At under 8KB gzipped for core + react, Tour Kit adds less JavaScript to the client than React Joyride's CSS file alone. Every kilobyte matters when you're chasing fast initial paint. Google's research shows pages loading under 50KB of JavaScript see 23% lower bounce rates on mobile (web.dev).
Tour Kit's real limitation for SSR teams: it only works with React. If your SSR framework is SvelteKit, Nuxt (Vue), or Astro without React islands, Tour Kit isn't an option. Shepherd.js covers those cases.
FAQ
Does React Joyride work with Next.js App Router?
React Joyride works with Next.js App Router when wrapped in next/dynamic with ssr: false. Without that wrapper, it crashes because it accesses window at the module level. The dynamic import adds 1-3 seconds of delay before tours are available. As of April 2026, React Joyride has 603K weekly npm downloads but no native RSC support.
Can I use a product tour library with Remix SSR?
Yes, but framework-agnostic libraries work better than React-specific ones in Remix. Shepherd.js has explicit Remix documentation and handles SSR correctly. Tour Kit also works in Remix since it respects the "use client" convention, but you need to place the provider in a client-rendered route module. Driver.js requires a useEffect wrapper to avoid server-side execution.
Why do product tours cause hydration mismatches?
Product tours cause hydration mismatches because they create DOM elements on the client that didn't exist in the server-rendered HTML. React compares server output with client output, and any difference triggers a warning. Libraries that render tour UI during initial mount (before useEffect) are the most common offenders. Fix: defer tour DOM creation until after hydration.
What is the lightest product tour library for SSR?
Driver.js at approximately 5KB gzipped is the lightest product tour library that works with SSR, though it requires a dynamic import wrapper and shows a hydration flicker. User Tour Kit at under 8KB gzipped is the lightest option with clean SSR support out of the box, requiring no workarounds. By comparison, React Joyride and Shepherd.js both ship around 37KB gzipped.
Do SaaS onboarding tools like Appcues work with SSR?
SaaS onboarding tools inject third-party scripts that load after the page renders, so they don't interfere with server rendering. But they add 80-200KB of external JavaScript and increase Time to Interactive by 1.2-3.8 seconds on average. For apps measuring Core Web Vitals, these tools often undo the performance gains SSR provides.
Related articles

What are the best Appcues alternatives for developers?
Compare 7 Appcues alternatives built for developers. See bundle sizes, React 19 support, pricing, and code examples to pick the right onboarding tool.
Read article
What is the best open-source onboarding framework? (2026)
Compare open-source onboarding frameworks by bundle size, React 19 support, accessibility, and full-stack coverage. Honest recommendations with data.
Read article
What is the best shadcn/ui compatible tour library?
Compare 7 tour libraries for shadcn/ui compatibility. See bundle sizes, headless support, and Tailwind integration to pick the right fit.
Read article
What is the cheapest product tour tool in 2026?
Compare every product tour tool by real cost in 2026. SaaS starts at $468/year, enterprise hits $140K. Tour Kit costs $99 once. See the full breakdown.
Read article