Next.js

Next.js // GSAP ScrollTrigger 적용하면서 겪은 문제 해결

해보구 2025. 1. 17. 18:03
반응형

최근 프로젝트에서 Next.js에서 GSAP ScrollTrigger를 사용하려고 했는데, 여러 문제가 발생했다.
SSR(서버 사이드 렌더링)과 GSAP가 충돌하면서 예상대로 동작하지 않았다.
이 과정에서 발생한 오류들과 해결 방법을 정리해본다.


1. ScrollTrigger가 정의되지 않는 오류

❌ 문제

Next.js에서 ScrollTrigger를 사용하려고 하면 ScrollTrigger is not defined라는 오류가 발생했다.
이유를 찾아보니 Next.js는 서버에서 먼저 HTML을 렌더링하는데, GSAP의 ScrollTrigger는 브라우저에서만 실행되기 때문이었다.

✅ 해결 방법

useEffect 내부에서 import("gsap/ScrollTrigger")를 사용해 ScrollTrigger를 동적으로 로드하면 서버에서는 실행되지 않도록 할 수 있다.

 

import React, { useEffect, useState, useRef } from "react";
import gsap from "gsap";

export default function MyComponent() {
  const sectionRef = useRef(null);
  const [scrollTriggerLoaded, setScrollTriggerLoaded] = useState(false);

  useEffect(() => {
    if (typeof window !== "undefined") {
      import("gsap/ScrollTrigger").then(({ ScrollTrigger }) => {
        gsap.registerPlugin(ScrollTrigger);
        setScrollTriggerLoaded(true);
      });
    }
  }, []);

  useEffect(() => {
    if (!scrollTriggerLoaded || !sectionRef.current) return;

    import("gsap/ScrollTrigger").then(({ ScrollTrigger }) => {
      ScrollTrigger.create({
        trigger: sectionRef.current,
        start: "top center",
        end: "bottom center",
        scrub: true,
        pin: true,
      });

      ScrollTrigger.refresh();
    });

    return () => {
      import("gsap/ScrollTrigger").then(({ ScrollTrigger }) => {
        ScrollTrigger.getAll().forEach((trigger) => trigger.kill());
      });
    };
  }, [scrollTriggerLoaded]);

  return <section ref={sectionRef}>Scroll Animation Section</section>;
}

 

이렇게 하면 서버에서 실행되지 않고, 브라우저에서만 GSAP가 적용된다.


2. ScrollTrigger가 적용되지 않는 문제

❌ 문제

ScrollTrigger가 오류 없이 실행되는데도 스크롤을 내려도 애니메이션이 적용되지 않았다.
콘솔을 확인해보니 GSAP가 .about-line-{i} 클래스를 제대로 찾지 못하는 문제였다.
Next.js의 SSR에서 요소가 아직 렌더링되지 않았거나, querySelectorAll()을 사용해야 할 수도 있었다.

✅ 해결 방법

  • .about-line-{i} 대신 querySelectorAll(".about-line")을 사용하여 모든 요소를 한 번에 가져오도록 수정
  • useEffect 실행 전에 aboutRef.current가 null이 아닌지 확인
import React, { useEffect } from "react";
import gsap from "gsap";

export default function AboutSection({ aboutRef }) {
  useEffect(() => {
    if (typeof window === "undefined" || !aboutRef?.current) return;

    import("gsap/ScrollTrigger").then(({ ScrollTrigger }) => {
      gsap.registerPlugin(ScrollTrigger);

      const aboutLines = aboutRef.current.querySelectorAll(".about-line");

      if (aboutLines.length === 0) {
        console.warn("No elements found with class `.about-line`");
        return;
      }

      gsap.timeline({
        scrollTrigger: {
          trigger: aboutRef.current,
          start: "top center",
          end: "bottom center",
          scrub: true,
          pin: true,
        },
      }).to(aboutLines, { color: "#fff", duration: 1.5, stagger: 0.3 });

      ScrollTrigger.refresh();
    });

    return () => {
      import("gsap/ScrollTrigger").then(({ ScrollTrigger }) => {
        ScrollTrigger.getAll().forEach((trigger) => trigger.kill());
      });
    };
  }, [aboutRef]);

  return (
    <section ref={aboutRef}>
      <p className="about-line">Hello, this is a GSAP ScrollTrigger example.</p>
      <p className="about-line">Scroll down to see the color change.</p>
    </section>
  );
}

이렇게 수정하고 나니 스크롤을 내리면 글씨 색상이 정상적으로 변경되었다.

 


3. ScrollTrigger가 특정 섹션에서만 동작하지 않는 문제

❌ 문제

Next.js에서 dynamic()을 사용해 컴포넌트를 동적으로 로드했더니 ScrollTrigger가 특정 섹션에서만 동작하지 않는 문제가 발생했다.
이는 useEffect 실행 시점에서 ref.current가 null일 가능성이 있었기 때문이다.

✅ 해결 방법

  • Home.js에서 useRef()를 생성하고, 자식 컴포넌트에 props로 전달
  • useEffect 실행 전에 ref.current가 null이 아닌지 체크
import React, { useEffect, useRef, useState } from "react";
import dynamic from "next/dynamic";
import gsap from "gsap";

// 동적 로드 (SSR 비활성화)
const AboutSection = dynamic(() => import("../components/AboutSection"), { ssr: false });

export default function Home() {
  const aboutRef = useRef(null);
  const [scrollTriggerLoaded, setScrollTriggerLoaded] = useState(false);

  useEffect(() => {
    if (typeof window !== "undefined") {
      import("gsap/ScrollTrigger").then(({ ScrollTrigger }) => {
        gsap.registerPlugin(ScrollTrigger);
        setScrollTriggerLoaded(true);
      });
    }
  }, []);

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

 

이렇게 수정하고 나니 모든 섹션에서 ScrollTrigger가 정상적으로 동작했다.


4. 정리

ScrollTrigger 적용할 때 주의할 점

GSAP ScrollTrigger는 SSR에서 실행되지 않도록 import("gsap/ScrollTrigger")를 useEffect에서 실행
✅ querySelectorAll(".about-line")을 사용하여 Next.js의 SSR 문제 없이 요소를 찾도록 변경
✅ useRef()를 부모에서 생성하고 자식 컴포넌트에 props로 전달
✅ ScrollTrigger.getAll().forEach((trigger) => trigger.kill());을 사용해 메모리 누수 방지


 

이렇게 수정하니까 Next.js에서도 GSAP ScrollTrigger가 정상적으로 작동했다.
Next.js에서 GSAP를 사용하다가 ScrollTrigger가 적용되지 않는다면, 위 방법을 시도해보면 해결될 것이다.

반응형