0

I'm stuck with a weird ScrollTrigger issue in my Next.js app and could really use some help.

What's happening

I have a section on my homepage that uses GSAP ScrollTrigger to pin and animate when you scroll. Here's the weird part:

  • When I first visit the homepage (or refresh the page), everything works great. The section pins and animates perfectly.

  • But when I navigate to another page and then come back to the homepage, the ScrollTrigger just stops working. The section doesn't pin anymore and the animations don't trigger.

The really confusing part

This only happens in production! On localhost, it works fine even when I navigate back and forth between pages. But on my live server, it breaks every time I return to the homepage.

My code

'use client';

import React, { useRef } from 'react';
import { usePathname } from 'next/navigation';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { useGSAP } from '@gsap/react';

gsap.registerPlugin(ScrollTrigger);

const ProcessSection = () => {
    const sectionRef = useRef(null);
    const pathname = usePathname();

    useGSAP(() => {
            if (pathname !== '/') return;

            const el = sectionRef.current;
            if (!el) return;

            const q = gsap.utils.selector(el);
            const isMobile = window.innerWidth < 768;
            const scrollDistance = isMobile ? 1500 : 2000;

            const tl = gsap.timeline()
                .to({}, { duration: 0.4 })
                // Slide 1 -> 2
                .to(q('.slide-0'), { top: '100%', duration: 0.25, ease: 'power2.inOut' })
                .to(q('.slide-1'), { top: '0%', duration: 0.25, ease: 'power2.inOut' }, '<')
                .to({}, { duration: 0.4 })
                // Slide 2 -> 3
                .to(q('.slide-1'), { top: '100%', duration: 0.25, ease: 'power2.inOut' })
                .to(q('.slide-2'), { top: '0%', duration: 0.25, ease: 'power2.inOut' }, '<')
                .to({}, { duration: 0.4 });

            ScrollTrigger.create({
                id: 'process-section-pin',
                trigger: el,
                start: 'top top',
                end: `+=${scrollDistance}`,
                scrub: 0.5,
                pin: true,
                pinSpacing: true,
                pinReparent: true,
                animation: tl,
                anticipatePin: 1,
                invalidateOnRefresh: true,
            });

            const onResize = () => {
                ScrollTrigger.refresh();
            };

            window.addEventListener('resize', onResize);

            return () => {
                window.removeEventListener('resize', onResize);
            };
        },
        {
            scope: sectionRef,
            dependencies: [pathname],
            revertOnUpdate: true,
        }
    );

    return (
        <section
            ref={sectionRef}
            className="bg-secondaryBg py-[clamp(100px,calc(100px+60*(100vw-760px)/1120),160px)] relative"
        >
            <div className="max-w-[clamp(680px,calc(680px+920*(100vw-760px)/1120),1600px)] px-[15px] mx-auto">
                <div className="max-w-[clamp(500px,calc(500px+400*(100vw-760px)/1120),900px)] mx-auto text-center">
                    <h2 className="md:text-[clamp(48px,calc(48px+30*(100vw-760px)/1120),78px)] text-[clamp(32px,calc(32px+16*(100vw-360px)/400),48px)] md:leading-[clamp(48px,calc(48px+30*(100vw-760px)/1120),78px)] leading-[clamp(32px,calc(32px+16*(100vw-360px)/400),48px)] font-medium">
                        How we push startup{' '}
                        <span className="font-bold">ideas into real growth</span>
                    </h2>
                </div>

                <div
                    className="flex flex-col mt-[clamp(20px,calc(20px+40*(100vw-760px)/1120),60px)] mb-[clamp(30px,calc(30px+50*(100vw-760px)/1120),80px)] md:h-[clamp(280px,calc(280px+200*(100vw-760px)/1120),480px)] h-[420px] relative overflow-hidden"
                    style={{ perspective: '1000px' }}
                >
                    {/* Slide 1 */}
                    <div
                        className="slide slide-0 grid md:grid-cols-3 gap-4 absolute left-0 w-full"
                        style={{ top: '0%' }}
                    >
                        <div className="flex flex-col justify-center md:items-start max-md:order-2">
                            <h4 className="md:text-[clamp(16px,calc(16px+16*(100vw-760px)/1120),32px)] md:leading-[clamp(20px,calc(20px+20*(100vw-760px)/1120),40px)] text-[clamp(26px,calc(26px+10*(100vw-360px)/400),36px)] leading-[clamp(30px,calc(30px+30*(100vw-360px)/400),40px)] font-medium md:max-w-[clamp(160px,calc(160px+200*(100vw-760px)/1120),360px)] max-md:text-center">
                                Lorem ipsum dolor sit amet.
                            </h4>
                        </div>
                        <div className="flex flex-col justify-center text-center max-md:order-1">
                            <h3 className="md:text-[clamp(280px,calc(280px+200*(100vw-760px)/1120),480px)] text-[180px] leading-none font-medium">
                                1
                            </h3>
                        </div>
                        <div className="flex flex-col justify-center md:text-right md:items-end max-md:order-3">
                            <h4 className="md:text-[clamp(12px,calc(12px+12*(100vw-760px)/1120),24px)] text-[clamp(18px,calc(18px+6*(100vw-360px)/400),24px)] font-medium md:max-w-[clamp(210px,calc(210px+200*(100vw-760px)/1120),410px)] max-md:text-center">
                                Lorem ipsum dolor sit, amet consectetur adipisicing elit. mollitiia eos ducimus labore. Ab, provident repellat.
                            </h4>
                        </div>
                    </div>

                    {/* Slide 2 */}
                    <div
                        className="slide slide-1 grid md:grid-cols-3 gap-4 absolute left-0 w-full"
                        style={{ top: '100%' }}
                    >
                        <div className="flex flex-col justify-center md:items-start max-md:order-2">
                            <h4 className="md:text-[clamp(16px,calc(16px+16*(100vw-760px)/1120),32px)] md:leading-[clamp(20px,calc(20px+20*(100vw-760px)/1120),40px)] text-[clamp(26px,calc(26px+10*(100vw-360px)/400),36px)] leading-[clamp(30px,calc(30px+30*(100vw-360px)/400),40px)] font-medium md:max-w-[clamp(160px,calc(160px+200*(100vw-760px)/1120),360px)] max-md:text-center">
                                Lorem ipsum dolor sit amet.
                            </h4>
                        </div>
                        <div className="flex flex-col justify-center text-center max-md:order-1">
                            <h3 className="md:text-[clamp(280px,calc(280px+200*(100vw-760px)/1120),480px)] text-[180px] leading-none font-medium">
                                2
                            </h3>
                        </div>
                        <div className="flex flex-col justify-center md:text-right md:items-end max-md:order-3">
                            <h4 className="md:text-[clamp(12px,calc(12px+12*(100vw-760px)/1120),24px)] text-[clamp(18px,calc(18px+6*(100vw-360px)/400),24px)] font-medium md:max-w-[clamp(210px,calc(210px+200*(100vw-760px)/1120),410px)] max-md:text-center">
                                Lorem ipsum dolor sit, amet consectetur adipisicing elit. mollitiia eos ducimus labore. Ab, provident repellat.
                            </h4>
                        </div>
                    </div>

                    {/* Slide 3 */}
                    <div
                        className="slide slide-2 grid md:grid-cols-3 gap-4 absolute left-0 w-full"
                        style={{ top: '100%' }}
                    >
                        <div className="flex flex-col justify-center md:items-start max-md:order-2">
                            <h4 className="md:text-[clamp(16px,calc(16px+16*(100vw-760px)/1120),32px)] md:leading-[clamp(20px,calc(20px+20*(100vw-760px)/1120),40px)] text-[clamp(26px,calc(26px+10*(100vw-360px)/400),36px)] leading-[clamp(30px,calc(30px+30*(100vw-360px)/400),40px)] font-medium md:max-w-[clamp(160px,calc(160px+200*(100vw-760px)/1120),360px)] max-md:text-center">
                                Lorem ipsum dolor sit amet.
                            </h4>
                        </div>
                        <div className="flex flex-col justify-center text-center max-md:order-1">
                            <h3 className="md:text-[clamp(280px,calc(280px+200*(100vw-760px)/1120),480px)] text-[180px] leading-none font-medium">
                                3
                            </h3>
                        </div>
                        <div className="flex flex-col justify-center md:text-right md:items-end max-md:order-3">
                            <h4 className="md:text-[clamp(12px,calc(12px+12*(100vw-760px)/1120),24px)] text-[clamp(18px,calc(18px+6*(100vw-360px)/400),24px)] font-medium md:max-w-[clamp(210px,calc(210px+200*(100vw-760px)/1120),410px)] max-md:text-center">
                                Lorem ipsum dolor sit, amet consectetur adipisicing elit. mollitiia eos ducimus labore. Ab, provident repellat.
                            </h4>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    );
};

export default ProcessSection;
New contributor
Zain Mateen is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
1
  • Can you add a live/online example, it would be much easier to debug. Also one thing that comes to mind is this line 'if (pathname !== '/') return;', try adding a '/' in the case when the animation isn't working to see if this is the problem Commented Nov 18 at 16:43

1 Answer 1

0

I ran into a similar issue recently and finally figured out what was going wrong, hope this helps someone else!

This problem is a classic sign that the ScrollTrigger instance isn’t being cleaned up or re-initialized properly during client-side navigation.

In dev mode, things often get reloaded more aggressively, so the issue doesn’t show up. But in production, Next.js preserves state between route transitions unless you explicitly handle it.

There are two key things you need to do:

  1. Clean up your ScrollTrigger on unmount (use kill ScrollTrigger instance and avoid memoty leak as wel)

  2. Re-initialize it properly when navigating back (use useEffect to ensure re-initialized on path change)

    Here’s how I’d rewrite your useGSAP block using useEffect instead, with proper cleanup:

useEffect(() => {
  if (pathname !== '/') return;

  const el = sectionRef.current;
  if (!el) return;

  const q = gsap.utils.selector(el);
  const isMobile = window.innerWidth < 768;
  const scrollDistance = isMobile ? 1500 : 2000;

  const tl = gsap.timeline()
    .to({}, { duration: 0.4 })
    .to(q('.slide-0'), { top: '100%', duration: 0.25, ease: 'power2.inOut' })
    .to(q('.slide-1'), { top: '0%', duration: 0.25, ease: 'power2.inOut' }, '<')
    .to({}, { duration: 0.4 })
    .to(q('.slide-1'), { top: '100%', duration: 0.25, ease: 'power2.inOut' })
    .to(q('.slide-2'), { top: '0%', duration: 0.25, ease: 'power2.inOut' }, '<')
    .to({}, { duration: 0.4 });

  ScrollTrigger.create({
    id: 'process-section-pin',
    trigger: el,
    start: 'top top',
    end: `+=${scrollDistance}`,
    scrub: 0.5,
    pin: true,
    pinSpacing: true,
    pinReparent: true,
    animation: tl,
    anticipatePin: 1,
    invalidateOnRefresh: true,
  });

  const onResize = () => ScrollTrigger.refresh();
  window.addEventListener('resize', onResize);

  return () => {
    window.removeEventListener('resize', onResize);
    ScrollTrigger.getById('process-section-pin')?.kill();
  };
}, [pathname]);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.