airbnb / lottie-web

Render After Effects animations natively on Web, Android and iOS, and React Native. http://airbnb.io/lottie/
MIT License
30.34k stars 2.87k forks source link

document is not defined for server side rendering SSR at createTag #2739

Open joshunger opened 2 years ago

joshunger commented 2 years ago

I'm seeing document is not defined for server side rendering (SSR).

function createTag(type) {
  // return {appendChild:function(){},setAttribute:function(){},style:{}}
  return document.createElement(type);
}

export default createTag;

https://github.com/airbnb/lottie-web/blob/master/player/js/utils/helpers/html_elements.js#L3

@bodymovin would it be possible to check for document so it doesn't break SSR when this JavaScript is included in the SSR bundle?

Thanks.

sebinievas commented 2 years ago

@bodymovin any response? Thanks!

joshunger commented 2 years ago

I hope this helps others. This is what we ended up doing to workaround the issue -

const ChevronDownBouncing = lazy(() => import('./ChevronDownBouncing'));

Then -

<NoSsr>
  <Suspense fallback={<img alt="" src={chevronDownBouncing} />}>
    <ChevronDownBouncing />
  </Suspense>
</NoSsr>

Then -

import { useEffect, useRef } from 'react';
import lottieWeb from 'lottie-web';
import ChevronDownBouncingIcon from './ChevronDownBouncingIcon.json';
import * as styles from './ChevronDownBouncing.module.scss';

export default function ChevronDownBouncing() {
  const ref = useRef();

  useEffect(() => {
    lottieWeb.loadAnimation({
      container: ref.current,
      renderer: 'svg',
      loop: true,
      autoplay: true,
      animationData: ChevronDownBouncingIcon
    });
  }, []);

  return <div ref={ref} className={styles.chevron} />;
}

And chevronDownBouncing is the last frame. We saved the last frame to an svg by doing something this and grabbing the svg content in Google Chrome.

function SaveLastFrame() {
  const ref = useRef();
  useEffect(() => {
    const animation = lottie.loadAnimation({
      container: ref.current, // the dom element that will contain the animation
      renderer: 'svg',
      loop: true,
      autoplay: true,
      animationData: json // the path to the animation json
    });

    // open json and find op value
    animation.goToAndStop(120, true);
  }, []);

  return <div style={{ width: '100px' }} ref={ref}></div>;
}
horrylala commented 1 year ago

Same error,have you ever solved it ? I'm using loadable-component with loadable(() => import(a.jsx)). Got the same error.

Without dynamic import , it goes all right. So, what's wrong with dynamic import?

aas395 commented 1 year ago

This happened to me yesterday on Shopify/Oxygen and @joshunger's solution fixed it for me.

djrcawley commented 8 months ago

Maybe this will help someone looking for this similar issue. I accidentally upgraded to node version to the newest version 21.5.0 which caused this error to appear. Downgrading to version 20.10.0 (LTS) and reinstalling all the packages resolved this issue for me.

HyatMyat4 commented 8 months ago

Thanks very very much @djrcawley your comment is very helpful 100% work , how I fix: after reinstalling 20.10.0 (LTS) , I delete node_modules , yarn.lock , package-lock.json , .next and after reinstalling these, need to close the vs code and open again it will work

ChaseObserves commented 8 months ago

@djrcawley I want you to know that I've been suffering with this issue in my daily job for months now, I've been in this thread multiple times trying different things to resolve it, but your comment is new as of last week and your fix totally worked for me. THANK YOU. The nightmare of refreshing my NextJS app 3-4 times until the 'document is not defined' error stopped popping up is now just a bad memory.

nadeemc commented 8 months ago

Hi!

Another solution specific to Next.js is to wrap the player component using dynamic to prevent SSR. This removes the issue without needing to downgrade node:

'use client';

import dynamic from "next/dynamic";

export const MyComponentWithLottie = () => {
  return (<>
    {/* ... other components ... */}

    <PlayerWithNoSSR
      autoplay
      keepLastFrame
      loop
      src={'https://static3.lottiefiles.com/lotties/01_ramen_character.json'}
    />

    {/* ... other components ... */}
  </>);
};

const PlayerWithNoSSR = dynamic(
  () => import('@lottiefiles/react-lottie-player').then(module => module.Player),
  {ssr: false},
);
juan-alencar commented 6 months ago

Hi!

Another solution specific to Next.js is to wrap the player component using dynamic to prevent SSR. This removes the issue without needing to downgrade node:

'use client';

import dynamic from "next/dynamic";

export const MyComponentWithLottie = () => {
  return (<>
    {/* ... other components ... */}

    <PlayerWithNoSSR
      autoplay
      keepLastFrame
      loop
      src={'https://static3.lottiefiles.com/lotties/01_ramen_character.json'}
    />

    {/* ... other components ... */}
  </>);
};

const PlayerWithNoSSR = dynamic(
  () => import('@lottiefiles/react-lottie-player').then(module => module.Player),
  {ssr: false},
);

i love you

makowey commented 6 months ago

Dynamic imports Svelte/SvelteKit:

<script lang="ts">
    const imports = {
        lottieAnimation: () => import('./LottieAnimation.svelte')
    };

    export let options = {
        path: '94729-not-found',
        loop: true
    };
</script>

{#await imports['lottieAnimation']() then module}
    <svelte:component this={module.default} {options} />
{/await}
jangir-ritik commented 6 months ago

Maybe this will help someone looking for this similar issue. I accidentally upgraded to node version to the newest version 21.5.0 which caused this error to appear. Downgrading to version 20.10.0 (LTS) and reinstalling all the packages resolved this issue for me.

This saved me... I wonder what the underlying issue is

hewelt commented 6 months ago

@jangir-ritik probably this: a github issue about lottie checking for typeof navigator which can't work in Node 21

RonaldoAlencar commented 5 months ago

Hi!

Another solution specific to Next.js is to wrap the player component using dynamic to prevent SSR. This removes the issue without needing to downgrade node:

'use client';

import dynamic from "next/dynamic";

export const MyComponentWithLottie = () => {
  return (<>
    {/* ... other components ... */}

    <PlayerWithNoSSR
      autoplay
      keepLastFrame
      loop
      src={'https://static3.lottiefiles.com/lotties/01_ramen_character.json'}
    />

    {/* ... other components ... */}
  </>);
};

const PlayerWithNoSSR = dynamic(
  () => import('@lottiefiles/react-lottie-player').then(module => module.Player),
  {ssr: false},
);

thank youu

neelp03 commented 3 months ago

Hello, if you are using lottie-web in next.js then you can dynamically load it inside useEffect() and the error will go away


'use client';

import React, { useEffect, useRef } from 'react';
import { signatureData } from '../constants/index';

const Signature = () => {
  const animationContainerRef = useRef<HTMLDivElement>(null);
  const titleTextRef = useRef<HTMLDivElement>(null);
  const signatureContainerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const loadLottie = async () => {
      if (!animationContainerRef.current || !titleTextRef.current || !signatureContainerRef.current) return;

      const lottie = await import('lottie-web');
      const animation = lottie.default.loadAnimation({
        container: animationContainerRef.current,
        renderer: 'svg',
        loop: false,
        autoplay: true,
        animationData: signatureData,
      });

      const titleTextElement = titleTextRef.current as HTMLElement;
      const signatureContainerElement = signatureContainerRef.current as HTMLElement;
      const navElement = document.querySelector('#navbar') as HTMLElement;
      // ......
      return () => {
        animation.destroy();
      };
    };

    loadLottie();
  }, []);

  return (
    <div className="..." ref={signatureContainerRef}>
      <div id="animation" ref={animationContainerRef}></div>
      <div className="..." ref={titleTextRef}>xyz</div>
    </div>
  );
};

export default Signature;