← Back to listing

GSAP & Next.js Setup: The BSMNT Way

DATE

AUTHOR

Nazareno Oviedo

 / 

Agu Seguí

CATEGORY

Development

READ TIME

9 minutes

VIEWS

...
Batman and Robin? Rick and Morty? Bad Boys? Sure, you could name them all, but here we're more into the dynamic duo of Next.js and GSAP, right? Let's have fun!

GSAP & Next.js Setup: The BSMNT Way

author
Nazareno Oviedo / Agu Seguí
Greensock and GSAP to the rescue

At basement.studio we're firm believers in streamlining our toolset to drive creativity and thrive strongly. And that's where Next.js and GSAP come into play. These tools are our weapons of choice to build creative and performant experiences in all our projects.

Here on our blog, we wanna share our experience because, let's face it, we have a blast building cool stuff with these tools. Take BSMNT Scrollytelling, our latest library. It's a prime example of what we've accomplished from investing time and effort in this particular stack.

Plus, there are some specific tips for pairing Next.js with GSAP that can make your life easier. Before jumping into our OSS library or other complex animation challenges, we'll start with the basics — project setup and our team's specific approaches.

WHY NEXT.JS AND GSAP

Next.js is our go-to framework, simplifying web development by providing a solid application structure, routing, and server-side rendering (SSR), not to mention a plethora of components that take the headache out of complex front-end infrastructure. Even though the framework is a key part of our toolkit, we won't be delving into fundamentals in this post. But stay tuned; we’re cooking something for upcoming content!

Interested in learning more about Next.js? Check out their official resources.

When it comes to bringing our creations to life, GSAP is our animation engine of choice. We know how it pairs with React—the good and the bad stuff. It's also brimming with features far beyond any other library and is full of powerful plugins we always vet before tackling a tricky animation problem. As a framework-agnostic library, you can use it whether you're working in React or any other framework. The core principles stay the same.

With GSAP, you can work with any web component. In Greensock's words, it lets you "animate anything" —whether it's simple DOM transitions, SVG, Three.js, canvas or WebGL, or even generic JavaScript objects.

And let's not forget, as they proudly state in their docs, animating imperatively with GSAP offers you a lot more power, control, and flexibility. Plus, GSAP is obsessed with performance, optimizations, and browser compatibility, so you can focus on the fun stuff.

This post is all about configuring GSAP in Next.js. We recommend a solid understanding of React and encourage you to check out the GSAP’s intro documentation.

THE BSMNT WAY

Now, let's get down to business. Where do we set up GSAP? You could put it anywhere on the app, but what if you are looking for consistency or easy maintenance? Instead, let's keep things tidy and organized.

Let’s start by generating a file within the lib/gsap directory, where we will perform the imports and possible plugin registrations. Here we also will set the default values we want to use across the app, such as duration, ease, and other settings.

1// src/lib/gsap/index.tsx
2
3import gsap from 'gsap';
4import { CustomEase } from 'gsap/dist/CustomEase';
5import { SplitText } from 'gsap/dist/SplitText';
6
7gsap.registerPlugin(CustomEase, SplitText);
8
9const GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2;
10const RECIPROCAL_GR = 1 / GOLDEN_RATIO;
11const DURATION = RECIPROCAL_GR;
12const EASE = CustomEase.create('ease', '0.175, 0.885, 0.32, 1');
13
14// Configuring GSAP with custom settings that aren't Tween-specific
15gsap.config({
16 autoSleep: 60,
17 nullTargetWarn: false
18});
19
20// Setting default animation properties that should be inherited by ALL tweens
21gsap.defaults({
22 duration: DURATION,
23 ease: EASE
24});
25
26// Once the desired configurations are set, we simply export what we need to work with in the future.
27export { CustomEase, DURATION, EASE, GOLDEN_RATIO, gsap, SplitText };

In a nutshell, what we're doing in this file is setting up and customizing GSAP to fit our needs and preferences for the project. Now, let's go over the key parts of the code and their purposes:

Basic import

1import gsap from 'gsap';
2import { CustomEase } from 'gsap/dist/CustomEase';
3import { SplitText } from 'gsap/dist/SplitText';
4
5gsap.registerPlugin(CustomEase, SplitText);

We're importing GSAP and some additional plugins, like CustomEase and SplitText. We’re not diving deep into this today, but plugins are often used for creating more advanced animations. Don’t forget to register them with gsap.registerPlugin() to ensure seamless function with the core framework without tree-shaking issues in build tools/bundlers.

Constants

1const GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2;
2const RECIPROCAL_GR = 1 / GOLDEN_RATIO;
3const DURATION = RECIPROCAL_GR;
4const EASE = CustomEase.create('ease', '0.175, 0.885, 0.32, 1');

We're setting a series of constants that we'll use later. For example, DURATION represents the default timing for animations, so you can tweak it here and propagate the value everywhere on your app, keeping consistency in your animation system.

Default configuration

1// Configuring GSAP with custom settings that aren't Tween-specific
2gsap.config({
3 autoSleep: 60,
4 nullTargetWarn: false
5});
6
7// Setting default animation properties that should be inherited by ALL tweens
8gsap.defaults({
9 duration: DURATION,
10 ease: EASE
11});

In this section, we're setting up some custom options.

With gsap.config(), you can tweak GSAP's settings that aren't Tween-specific.

Through gsap.defaults(), we're setting the default properties of all animations by passing the constant values we previously defined.

Export

1// Once the desired configurations are set, we simply export what we need to work with in the future.
2export { CustomEase, DURATION, EASE, GOLDEN_RATIO, gsap, SplitText };

Finally, we're exporting the relevant constants and objects so they can be used in other files. This facilitates consistency and reusability of configurations across the code.

THE KEY PART: CUSTOM IMPORTING

Now, here's the magic trick. Instead of importing GSAP the old-fashioned way, we're going to use import { gsap } from '~/lib/gsap' instead of import gsap from 'gsap'. Why? Because we're all about consistency, global management, and reusability. When we import GSAP from the ~/lib/gsap/index.tsx file, we're using a GSAP instance that's already set up according to preferences and default values we've configured before.

This approach ensures all animations use the same settings, making the code more predictable and easier to maintain.

  1. If we ever need to make changes to GSAP's configuration across the entire project, we only have to do it in one place. This simplifies global configuration management.

  2. If we wish to reuse certain constants or settings in multiple components, like DURATION, we can easily do so by importing them from our preset file instead of having to define them again in each component.

  3. If we need to update GSAP or its plugins in the future, we only have to perform the update in one place rather than searching and modifying each individual import throughout the project.

WORKING IN REACT ENVIRONMENTS

It's worth noting that GSAP is framework-agnostic, so it's not specifically designed to work with any particular framework, including React. This means you can use GSAP with any front-end technology, making it incredibly versatile.

However, when using GSAP within a React (or Next.js) project, it's important to keep some key aspects in mind to ensure they work together efficiently and harmoniously. This includes handling the state, component lifecycles, and the way React updates the DOM.

Respect the React flow: For instance, you shouldn't use GSAP to directly modify React's state, but instead, use the setters provided by state hooks.

Proper DOM Handling: React has its own virtual DOM management, while GSAP works directly with the actual DOM. Make sure that when GSAP interacts with DOM elements, they're already present and managed by React. We’ll be diving deep into this while talking about the lifecycle.

Understanding Lifecycles: React handles its own component lifecycle, and it's important that your GSAP animations integrate with it.

Surviving in the lifecycle

The biggest challenge when working with animations in SSR apps like Next.js is making sure they play nice with the component's lifecycle. You might want to start animations after React has updated the DOM, right?

So, here comes useIsomorphicLayoutEffect to the rescue.

1useIsomorphicLayoutEffect(() => {
2 const tween = gsap.fromTo(...);
3
4 return () => {
5 tween.revert();
6 };
7}, []);

This hook is a blend of React's traditional effect (useEffect) and useLayoutEffect, designed to handle things both on the server and on the client. Whenever we use this, we shouldn't run into synchronization issues since it runs after DOM mutations have been made but before the browser has painted.

But what’s under the hood? It’s all about being aware of the window component.

1import { useEffect, useLayoutEffect } from 'react';
2
3const useIsomorphicLayoutEffect =
4 typeof window !== 'undefined' ? useLayoutEffect : useEffect;
5
6export default useIsomorphicLayoutEffect;

Animating at the component level

Alright, enough with the configs and warnings. Let's get this party started and make something move on the screen. Let’s make a div that plays a fade-in and slide-in animation when the component mounts. We’ll need to revert the animation when the component unmounts for cleanup. Easy stuff:

1export default function MyComponent() {
2 const productRef = useRef<HTMLDivElement>(null);
3
4 useIsomorphicLayoutEffect(() => {
5 if (!productRef.current) return;
6
7 const timeline = gsap.timeline({
8 paused: true,
9 defaults: {
10 ease: 'power4.out'
11 }
12 });
13
14 timeline.fromTo(
15 productRef.current,
16 {
17 autoAlpha: 0,
18 xPercent: 100
19 },
20 {
21 autoAlpha: 1,
22 xPercent: 0
23 }
24 );
25
26 timeline.play();
27
28 return () => {
29 timeline.revert();
30 }
31 }, []);
32
33 return (
34 <div ref={productRef} />
35 );
36}

First, we created a reference to a DOM element using useRef, which targets the element we want to animate. In the render method, we can return a div element and attach our reference to it. This element is what we'll animate with the GSAP timeline.

Next, we used the great useIsomorphicLayoutEffect hook that runs synchronously after all DOM mutations. Inside this hook, it’s important to first check if the productRef.current is defined. If it isn't, we return early to avoid errors.

Then, we created our GSAP timeline. We recommend you check out the official docs to learn everything about this powerful tool. Basically, by using timeline.fromTo(), we defined the animation from a specific starting point, for instance, 0 for the autoAlpha (making the element invisible) and 100 for the xPercent (the horizontal offset). Then, it goes to an endpoint, where autoAlpha must be 1 to reveal the element, and xPercent needs to be 0 to appear in its original position. In simpler terms, this animation fades in the element while moving it horizontally from right to left.

Remember, there's no need to establish a duration or easing here, as we're applying the constant values we've already defined through gsap.defaults() in '~/lib/gsap'. This ensures a consistent rhythm across all components for a harmonious user experience. However, if you wish to deviate from these default values, you can manually overwrite them at this point.

Finally, with timeline.play(), we can start the animation and then return a cleanup function that calls timeline.revert() to reset the timeline to its initial state when the component unmounts.

Easy debugging

Lastly, we also want to have some control and good debugging experience. Especially when we're orchestrating a symphony of complex animations across multiple components. We're all familiar with the numerous methods available for debugging. These include the browser dev tools, the one and only console.log(), and the powerful GSDevTools, to name a few.

But remember, you're operating within the lifecycle, so within the useIsomorphicLayoutEffect, you can log the state of the timeline after it has played or even after it has been reverted. Double-check that the references to the DOM elements (using useRef in React) are correctly set up. Sometimes, animations fail simply because references aren't pointing to the right elements.

WHAT’S COMING

We're all set! Now you have a scalable codebase from which you can start building, maintaining, and tweaking with ease. We hope this approach proves beneficial to you. Do not hesitate to reach out to our team if you need any help - we're always open to discussing anything related to development on our Discord channel.

But don't think we're stopping here. We have exciting things lined up for you. Keep an eye out for upcoming chapters where we'll delve into more complex animation tasks. Plus, we'll get into BSMNT Scrollytelling, our open-source library for creating React-friendly on-scroll animations, all powered by GSAP's ScrollTrigger.

↖ Back to listing
CATEGORYDevelopment
DATE
AUTHORMatias Perez / Matias Gonzalez

Creating Daylight | The Devex

Discover how we enhanced our development process for the Daylight project, from debugging tips to performance boosts maintaining a clean codebase. Meet you down below!

categoryDevelopment
authorMatias Perez / Matias Gonzalez
date

DATE

AUTHOR

Matias Perez

 / 

Matias Gonzalez

CATEGORY

Development

READ TIME

7 minutes

VIEWS

...
CATEGORYDevelopment
DATE
AUTHORMatias Gonzalez / Matias Perez

Creating Daylight | The Shadows

Ever wondered how we created those lifelike shadows on Daylight's website? Here we break down the secrets of our shadow rendering process.

categoryDevelopment
authorMatias Gonzalez / Matias Perez
date

DATE

AUTHOR

Matias Gonzalez

 / 

Matias Perez

CATEGORY

Development

READ TIME

8 minutes

VIEWS

...