greensock / GSAP

GSAP (GreenSock Animation Platform), a JavaScript animation library for the modern web
https://gsap.com
19.93k stars 1.72k forks source link

Nextjs Render Error caused by Scroll Trigger being enabled #603

Open robert-j-webb opened 5 days ago

robert-j-webb commented 5 days ago

Repro case, on NextJS 15

Hi, I tracked down the cause of this error in the console:

installHook.js:1 Warning: Extra attributes from the server: style Error Component Stack
    at body (<anonymous>)
    at html (<anonymous>)
    at RootLayout [Server] (<anonymous>)
    at RedirectErrorBoundary (redirect-boundary.js:57:27)
    at RedirectBoundary (redirect-boundary.js:107:26)
    at NotFoundErrorBoundary (not-found-boundary.js:29:27)
    at NotFoundBoundary (not-found-boundary.js:116:26)
    at DevRootNotFoundBoundary (dev-root-not-found-boundary.js:33:26)
    at ReactDevOverlay (ReactDevOverlay.js:38:27)
    at HotReload (hot-reloader-client.js:327:29)
    at Router (app-router.js:229:25)
    at ErrorBoundaryHandler (error-boundary.js:76:27)
    at ErrorBoundary (error-boundary.js:185:32)
    at AppRouter (app-router.js:613:38)
    at ServerRoot (app-index.js:117:27)
    at Root (app-index.js:122:26)

I'm using NextJS 14.1.2, app router, gsap/react: 2.1.1

The code that causes this error is just:

'use client';

import { useGSAP } from '@gsap/react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/all';
import { useRef } from 'react';

gsap.registerPlugin(useGSAP, ScrollTrigger);

export default function Page() {
  const container = useRef<HTMLDivElement>(null);

  useGSAP(
    () => {
      const tl = gsap.timeline({
        scrollTrigger: {
          trigger: `.space-y-8`,
          start: 'top top',
          end: 'bottom top',
          scrub: true,
        },
        defaults: { ease: 'none' },
      });
    },
    { scope: container },
  );
  return <div className="space-y-8" ref={container}></div>;
}

I wanted to see if I could fix the problem, so I took a look at the source code and found some code that looked like a potential cause.

In the enable function, body style is modified twice link to code

let bodyStyle = _body.style,
    border = bodyStyle.borderTopStyle,
    AnimationProto = gsap.core.Animation.prototype,
    bounds, i;
AnimationProto.revert || Object.defineProperty(AnimationProto, "revert", { value: function() { return this.time(-0.01, true); }}); // only for backwards compatibility (Animation.revert() was added after 3.10.4)
bodyStyle.borderTopStyle = "solid"; // works around an issue where a margin of a child element could throw off the bounds of the _body, making it seem like there's a margin when there actually isn't. The border ensures that the bounds are accurate.
bounds = _getBounds(_body);
_vertical.m = Math.round(bounds.top + _vertical.sc()) || 0; // accommodate the offset of the <body> caused by margins and/or padding
_horizontal.m = Math.round(bounds.left + _horizontal.sc()) || 0;
border ? (bodyStyle.borderTopStyle = border) : bodyStyle.removeProperty("border-top-style");

I found that if I removed the two lines that modify body style, then the error went away.

So now it becomes:

let AnimationProto = gsap.core.Animation.prototype,
    bounds, i;
AnimationProto.revert || Object.defineProperty(AnimationProto, "revert", { value: function() { return this.time(-0.01, true); }}); // only for backwards compatibility (Animation.revert() was added after 3.10.4)
bounds = _getBounds(_body);
_vertical.m = Math.round(bounds.top + _vertical.sc()) || 0; // accommodate the offset of the <body> caused by margins and/or padding
_horizontal.m = Math.round(bounds.left + _horizontal.sc()) || 0;

Now I'm not sure what the suggested fix is. Probably you need to make sure your bounds calculation doesn't occur until after render.

You can wrap client side only checks like this in:

if(typeof document !== undefined) {

}

And then it won't run on the server, which is what is causing this error.

robert-j-webb commented 5 days ago

Note, I am also seeing it on Next 15 canary

installHook.js:1 A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:

- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.

It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.

https://react.dev/link/hydration-mismatch

  ...
    <HotReload assetPrefix="">
      <ReactDevOverlay onReactError={function} state={{nextId:1, ...}} dispatcher={{...}}>
        <DevRootNotFoundBoundary>
          <NotFoundBoundary notFound={<NotAllowedRootNotFoundError>}>
            <NotFoundErrorBoundary pathname="/" notFound={<NotAllowedRootNotFoundError>} notFoundStyles={undefined} ...>
              <RedirectBoundary>
                <RedirectErrorBoundary router={{...}}>
                  <Head>
                  <link>
                  <RootLayout>
                    <html lang="en" className="[color-sch...">
                      <body
                        className={"overflow-y-scroll bg-gray-1100 bg-[url('/grid.svg')] pb-36"}
-                       style={{}}
                      >
                  ...
        ...

Repro case: https://replit.com/@robertwebb10/GSAP-Nextjs-Body-style-error-reprodution?v=1