You may see "ReferenceError: document is not defined" or "window is not defined" when you add lottie-react or lottie-web to a Next.js app. The lottie-web package touches the DOM at import time, and Next.js imports your components on the server too. The same issue is open on the lottie-web GitHub repo.
Fix 1: dynamic import (App Router)
'use client';
import dynamic from 'next/dynamic';
import success from './success.json';
const Lottie = dynamic(() => import('lottie-react'), { ssr: false });
export default function Success() {
return <Lottie animationData={success} loop={false} style={{ width: 120, height: 120 }} />;
}In the App Router, ssr: false is only allowed inside a client component, so keep the use client line.
Fix 2: import inside useEffect
'use client';
useEffect(() => {
let anim;
import('lottie-web').then(({ default: lottie }) => {
anim = lottie.loadAnimation({ container: ref.current, renderer: 'svg', path: '/success.json' });
});
return () => anim?.destroy();
}, []);Avoid layout shift
Because the animation renders only in the browser, give its container a fixed width and height so content does not jump when it appears. Call destroy() on unmount to avoid leaks when users navigate.
The same pattern applies to any canvas-based runtime, including Rive and ExodeUI. We make ExodeUI; its React package is used inside client components the same way.