Skip to main content
Lottie4 min readNovember 2026

How do I fix "document is not defined" with Lottie in Next.js?

Short answer

lottie-web reads browser globals such as document when it is imported, so it crashes during server-side rendering in Next.js. Load it only in the browser: put the animation in a client component and import it with next/dynamic and ssr: false, or import lottie-web inside useEffect.

Core Takeaway

  • The error appears because Next.js renders on the server, where there is no document
  • lottie-react uses lottie-web, so it has the same issue
  • Fix 1: next/dynamic with ssr: false inside a client component
  • Fix 2: import lottie-web inside useEffect
  • Reserve the animation's space so the page does not shift when it loads

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.

Frequently asked questions

Does this happen with the Pages Router too?

Yes. The same dynamic import with ssr: false fixes it.

Is @lottiefiles/dotlottie-react SSR safe?

Test it in your setup. If you see the same error, use the same client-only dynamic import.

Will dynamic import hurt SEO?

No. Search engines do not need the animation. Keep important text in normal HTML.

Why do I get "window is not defined" instead?

Same cause, different global. The fix is identical.

Related Guides & Examples

Interactive motion in Next.js

See how ExodeUI runs inside client components with inputs your app controls.

Read the guide
ExodeUI

EXODEUI DESKTOP

A little space for good ideas.

Choose your platform to get the official ExodeUI installer.

Looking for Intel Mac or other builds? Explore the web studio or check all download options.

Prefer the browser? Try for free in your browser →