Gatsby's static site generation requires specific patterns for Lottie â animations need to be excluded from SSR, JSON imports need proper webpack config, and performance needs special attention for static pages. This guide covers all of it.
The Core SSR Problem
Gatsby generates HTML at build time. Lottie animations reference window, document, and navigator â cone of which exist during server-side rendering. If you import Lottie without precautions, builds fail with:
WebpackError: ReferenceError: window is not defined
The fix: use Gatsby's dynamic import with { ssr: false }, or wrap initialization in typeof window !== 'undefined' checks.
Before You Build
Open your animation files in IconKing first:
- Preview exactly how the animation renders
- Edit colors to match your Gatsby site's design system
- Convert
.jsonâ.lottiefor 75% smaller files - Catch unsupported effects before they fail silently in production
Free, no login.
Installation
npm install lottie-react
# or for .lottie format
npm install @lottiefiles/dotlottie-react
Option 1: gatsby-plugin-no-ssr (Simplest)
The simplest approach: wrap your animated component in the NoSSR pattern. Gatsby provides this natively using loadable-components:
npm install @loadable/component
// src/components/AnimatedHero.jsx
import loadable from '@loadable/component';
const LottieAnimation = loadable(
() => import('./LottiePlayer'),
{ ssr: false }
);
export function AnimatedHero() {
return (
<section>
<h1>Hero Title</h1>
<LottieAnimation />
</section>
);
}
// src/components/LottiePlayer.jsx
import Lottie from 'lottie-react';
import heroAnim from '../animations/hero.json';
export default function LottiePlayer() {
return (
<div style={{ width: 400, height: 400 }}>
<Lottie animationData={heroAnim} loop autoplay />
</div>
);
}
This completely excludes the Lottie component from SSR and hydrates it on the client.
Option 2: useEffect Guard Pattern
If you prefer not to use an extra library, guard the animation initialization:
import { useState, useEffect } from 'react';
export function SafeLottie({ animationData, ...props }) {
const [isBrowser, setIsBrowser] = useState(false);
useEffect(() => {
setIsBrowser(true);
}, []);
if (!isBrowser) return null;
// Dynamically import Lottie only on the client
const Lottie = require('lottie-react').default;
return (
<div style={{ width: 200, height: 200 }}>
<Lottie animationData={animationData} {...props} />
</div>
);
}
Option 3: Gatsbys gatsby-browser.js Approach
For animations that appear on every page (e.g., nav icons), initialize globally in gatsby-browser.js:
// gatsby-browser.js
export const onClientEntry = () => {
// Lottie initializations that need window
window.__lottieReady = true;
};
Then check window.__lottieReady in your components.
Working with JSON Files
Gatsby handles .json imports via webpack. The standard require pattern works:
import Lottie from 'lottie-react';
import animData from '../animations/loading.json';
// animData is automatically available as a JavaScript object
<Lottie animationData={animData} loop autoplay />
For larger animation files, place them in static/animations/ and load at runtime to keep them out of the JS bundle:
import { useState, useEffect } from 'react';
import Lottie from 'lottie-react';
export function LazyAnimation({ src }) {
const [animData, setAnimData] = useState(null);
useEffect(() => {
fetch(src)
.then(res => res.json())
.then(setAnimData);
}, [src]);
if (!animData) return <div style={{ width: 200, height: 200, background: '#f0f0f0' }} />;
return (
<div style={{ width: 200, height: 200 }}>
<Lottie animationData={animData} loop autoplay />
</div>
);
}
// Usage â file in static/animations/hero.json
<LazyAnimation src="/animations/hero.json" />
Using dotLottie in Gatsby
For .lottie format (75% smaller), use @lottiefiles/dotlottie-react:
npm install @lottiefiles/dotlottie-react
// Place files in static/animations/
// Reference as absolute path from /
import loadable from '@loadable/component';
const DotLottieAnim = loadable(
async () => {
const { DotLottieReact } = await import('@lottiefiles/dotlottie-react');
return function DotLottieWrapper(props) {
return <DotLottieReact {...props} />;
};
},
{ ssr: false }
);
export function HeroAnimation() {
return (
<DotLottieAnim
src="/animations/hero.lottie"
loop
autoplay
style={{ width: 400, height: 400 }}
/>
);
}
gatsby-config.js: No Changes Needed
Unlike some libraries, Lottie doesn't require any Gatsby plugin configuration. The @loadable/component approach handles everything at the component level.
IntersectionObserver for Gatsby (Scroll Animations)
Gatsby sites are often content-heavy. Pause off-screen animations to prevent performance issues on long pages:
import { useRef, useEffect, useState } from 'react';
import loadable from '@loadable/component';
const Lottie = loadable(() => import('lottie-react'), {
ssr: false,
resolveComponent: mod => mod.default,
});
export function ScrollRevealAnimation({ animationData }) {
const wrapperRef = useRef(null);
const lottieRef = useRef(null);
const [shouldRender, setShouldRender] = useState(false);
useEffect(() => {
if (typeof IntersectionObserver === 'undefined') return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setShouldRender(true); // render once visible
lottieRef.current?.play();
} else {
lottieRef.current?.pause();
}
},
{ threshold: 0.2 }
);
if (wrapperRef.current) observer.observe(wrapperRef.current);
return () => observer.disconnect();
}, []);
return (
<div style={{ width: 300, height: 300 }} ref={wrapperRef}>
{shouldRender && (
<Lottie
lottieRef={lottieRef}
animationData={animationData}
autoplay={false}
loop={false}
/>
)}
</div>
);
}
Gatsby Image + Lottie: Hero Section Pattern
import { StaticImage } from 'gatsby-plugin-image';
import loadable from '@loadable/component';
const LottieAnim = loadable(() => import('../components/LottiePlayer'), { ssr: false });
export default function HeroSection() {
return (
<section className="hero">
<div className="hero-content">
<h1>Your Product</h1>
<p>Tagline here</p>
</div>
<div className="hero-animation">
<LottieAnim />
</div>
</section>
);
}
MDX / Blog Post Animations
Gatsby is popular for blogs (via MDX). Add animated illustrations to MDX posts:
// src/components/mdx/AnimatedCallout.jsx
import { useState, useEffect } from 'react';
export function AnimatedCallout({ src, children }) {
const [Lottie, setLottie] = useState(null);
const [animData, setAnimData] = useState(null);
useEffect(() => {
import('lottie-react').then(mod => setLottie(() => mod.default));
fetch(src).then(r => r.json()).then(setAnimData);
}, [src]);
return (
<div style={{ display: 'flex', gap: 16, padding: 16, background: '#f9f9f9', borderRadius: 8 }}>
{Lottie && animData && (
<div style={{ width: 48, height: 48, flexShrink: 0 }}>
<Lottie animationData={animData} loop autoplay />
</div>
)}
<div>{children}</div>
</div>
);
}
In your gatsby-config.js, add it to mdxOptions.components:
// gatsby-config.js
module.exports = {
plugins: [
{
resolve: 'gatsby-plugin-mdx',
options: {
mdxOptions: {
remarkPlugins: [],
},
},
},
],
};
import { AnimatedCallout } from '../components/mdx/AnimatedCallout'
<AnimatedCallout src="/animations/tip.json">
This is a callout with an animated icon!
</AnimatedCallout>
Performance Checklist for Gatsby
- Use
.lottieformat (convert at IconKing) â smaller files = better Lighthouse scores - Keep large animations in
static/animations/(not bundled in JS) - Use
@loadable/componentwith{ ssr: false }for all Lottie components - Add
<link rel="preload">ingatsby-ssr.jsfor above-fold animations:
// gatsby-ssr.js
import React from 'react';
export const onRenderBody = ({ setHeadComponents }) => {
setHeadComponents([
<link
key="preload-hero-lottie"
rel="preload"
href="/animations/hero.lottie"
as="fetch"
crossOrigin="anonymous"
/>,
]);
};
Summary
- Always use
@loadable/componentwith{ ssr: false }â Lottie requireswindow - For small files: bundle via
import animData from './anim.json' - For large files: store in
static/animations/and fetch at runtime - Use IntersectionObserver to pause off-screen animations on long pages
- Convert to
.lottieat IconKing for better Lighthouse scores - Preload hero animations in
gatsby-ssr.jsto eliminate the flash on load
Top comments (0)