ReferenceError with lottie-react on Next.js 13 - SSR Incompatibility?
Asked Answered
S

7

5

I'm encountering a bug in my Next.js 13 application after installing lottie-react. When I compile my project, it completes successfully, but I get a ReferenceError stating that document is not defined. Here's the error log:

 ✓ Compiled in 104ms (1559 modules)
 ⨯ ReferenceError: document is not defined
    at createTag (/Users/thomas/starknetid/app.starknet.id/node_modules/lottie-web/build/player/lottie.js:30:5)
    at /Users/thomas/starknetid/app.starknet.id/node_modules/lottie-web/build/player/lottie.js:1316:20
    at /Users/thomas/starknetid/app.starknet.id/node_modules/lottie-web/build/player/lottie.js:1323:6
    at /Users/thomas/starknetid/app.starknet.id/node_modules/lottie-web/build/player/lottie.js:1540:4
    at /Users/thomas/starknetid/app.starknet.id/node_modules/lottie-web/build/player/lottie.js:2:83
    at Object.<anonymous> (/Users/thomas/starknetid/app.starknet.id/node_modules/lottie-web/build/player/lottie.js:5:3)
    at Module._compile (node:internal/modules/cjs/loader:1376:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Module.require (node:internal/modules/cjs/loader:1235:19)
    at mod.require (/Users/thomas/starknetid/app.starknet.id/node_modules/next/dist/server/require-hook.js:64:28)
    at require (node:internal/modules/helpers:176:18)
    at Object.<anonymous> (/Users/thomas/starknetid/app.starknet.id/node_modules/lottie-react/build/index.js:5:14)
    at Module._compile (node:internal/modules/cjs/loader:1376:14) {
  page: '/register/testingdomain.stark'
}

I suspect this issue might be related to server-side rendering (SSR) incompatibility with lottie-react. In a typical client-side environment, document would be defined, but in SSR, document is not available.

Is there a workaround or a best practice for using lottie-react with Next.js in an SSR context? Do I need to check if SSR is enabled for every component that uses lottie?

Shaman answered 6/12, 2023 at 10:41 Comment(0)
L
2

I just found out a workaround, basically, just import lottie-web after component mount, with "npm run dev" the asset is redered twice, this problem disappears in production build

"use client"
import *  as data from '../../public/404.json';
import { useEffect, useRef } from "react";

export default function AnimationNotFound() {

    let animationRef = useRef(null);

    async function getLottie() {
        const lot = await import("lottie-web");

        lot.default.loadAnimation({
            autoplay: true,
            loop: true,
            animationData:data,
            container:animationRef.current
        })

    }
    useEffect(() => {

        getLottie();

    }, []);


    return (
        <div ref={animationRef}></div>
    );

}
Layer answered 1/7, 2024 at 12:26 Comment(0)
B
10

The issue you're facing is likely due to the server-side rendering (SSR) nature of Next.js. Lottie-web uses the window object, which is not available during SSR, causing the build to fail.

To fix this, you can dynamically import the Lottie component and disable SSR for it (docs ref). Here's how your code can looks like:

import dynamic from 'next/dynamic';

const Lottie = dynamic(() => import('react-lottie'), { ssr: false });
// or lottie-react - depending on what library you use
Baseless answered 16/1, 2024 at 9:45 Comment(2)
followed the answer, still getting Error: document is not definedPhinney
Hi @Phinney If you created another component which is using lottie and you try to import this component into another client component (for example error.tsx) Import Dynamically the component you've created import dynamic from "next/dynamic"; const NotFoundLottie = dynamic(() => import("../NotFoundLottie"), { ssr: false, }); And your component "use client"; import Lottie from "lottie-react"; import animationData from "./movie-lottie.json"; const NotFoundLottie = () => { return ( <Lottie animationData={animationData} loop /> ); };Baseless
L
3

The error is caused by the way one of the dependencies handles server and browser environments, it's a known bug but they didn't say when it's getting patched at least not in this answer which explains it much better than me: https://github.com/Gamote/lottie-react/issues/101

Using node version 18 should fix it because it allows for the existing browser/server. Check the GitHub issue for more details.

Lombardy answered 8/12, 2023 at 14:26 Comment(0)
L
2

I just found out a workaround, basically, just import lottie-web after component mount, with "npm run dev" the asset is redered twice, this problem disappears in production build

"use client"
import *  as data from '../../public/404.json';
import { useEffect, useRef } from "react";

export default function AnimationNotFound() {

    let animationRef = useRef(null);

    async function getLottie() {
        const lot = await import("lottie-web");

        lot.default.loadAnimation({
            autoplay: true,
            loop: true,
            animationData:data,
            container:animationRef.current
        })

    }
    useEffect(() => {

        getLottie();

    }, []);


    return (
        <div ref={animationRef}></div>
    );

}
Layer answered 1/7, 2024 at 12:26 Comment(0)
S
1

I used dynamic imports with SSR disabled that fixed this issue. This ensures that the Lottie component is only loaded on the client side, avoiding SSR-related problems. I'm using react-lottie for an example.

In LottieAnimation.tsx, define the Lottie component:

import Lottie, { Options } from "react-lottie";

const lottieOptions: Options = {
  loop: true,
  autoplay: true,
  animationData: require("path-to-lottie.json"),
  rendererSettings: {
    preserveAspectRatio: "xMidYMid slice",
  },
};

const LottieAnimation: React.FC = () => <Lottie options={lottieOptions} />;

export default LottieAnimation;

In the component file (Component.tsx), use Next.js's dynamic import to load the LottieAnimation component without SSR:

import dynamic from "next/dynamic";

const LottieAnimation = dynamic(() => import("./LottieAnimation"), { ssr: false });

const Component = () => (
  <>
    <LottieAnimation />
  </>
);

export default Component;
Spiegleman answered 20/8, 2024 at 19:24 Comment(0)
N
0

If you are running next dev or npm run dev you will probably encounter problem where your lottie is loaded twice. That's because useEffect may be called twice in development run. In accepted answer there is no cleanup method in which we should be calling lottie.destroy().

function Page(){
        const soundwaveBox = useRef(null);
        const lottie = useRef<AnimationItem|null>(null);
        useEffect(() => {
            let stop = false
            async function getLottie() {
                const importedLottie = await import("lottie-web");
                if(stop) return;
                lottie.current = importedLottie.default.loadAnimation({
                    autoplay: true,
                    loop: true,
                    animationData: audioWaveData,
                    container: soundwaveBox.current!
                })
                lottie.current.setSpeed(0.85)
            }
            getLottie()
            return () => {
                if(lottie.current)
                    lottie.current.destroy();
                else stop = true
            }
        })
}

In addition I added variable stop which will stop lottie from loading because in my case useEffect is called 2 times one after another and when we have async import code will not finish before useEffect cleanup function is called and lottie.current will still be null

Nappy answered 23/7, 2024 at 13:2 Comment(0)
K
0

The issue is that "lottie-react" tries to access the browser's document during server-side rendering (SSR). You can import it like this:

import React, { useEffect } from "react";
import dynamic from 'next/dynamic';
import animExample from "../anim.json"

function Component() {

  const [Lottie, setLottie] = useState(undefined)

  useEffect(() => {
    setLottie(dynamic(() => import('lottie-react'), { ssr: false }))
  }, [])

  return (
    <div>
      {Lottie && <Lottie animationData={animExample} loop={true} />}
    </div>
  );
}

export default Component;
Kiele answered 25/7, 2024 at 21:54 Comment(0)
H
0

The issue is that "lottie-react" tries to access the browser's document during server-side rendering (SSR) in javascript

"use client";
import dynamic from 'next/dynamic';

const Lottie = dynamic(() => import('lottie-react'), { ssr: false });
// import Lottie from "lottie-react";

const AnimationLottie = ({ animationPath, width }) => {
  const defaultOptions = {
    loop: true,
    autoplay: true,
    animationData: animationPath,
    style: {
      width: '95%',
    }
  };

  return (
    <Lottie {...defaultOptions} />
  );
};

export default AnimationLottie;
Hadlock answered 31/10, 2024 at 5:8 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.