DEV Community

EliHood
EliHood

Posted on

Are your client components being delayed when using Next JS ? consider this...

Within Next JS, you're forced to architect your React components in the most optimized way. Otherwise you'll run into slight delays, and undesirable side effects.

In this walkthrough, I'll show one common, and undesirable side effect when using client within Next JS.

The Problem

Image description

Notice how you see the content / text first before you see the gsap animation kick in ?

At first, maybe i thought this was a side effect using GSAP. But no, it was stemming from somewhere else....

Let's look at the code for this.

LandingPage.tsx

'use client';

import Hero from '../common/Hero/Hero';

function LandingPage() {
  return <Hero />;
}
export default LandingPage;
Enter fullscreen mode Exit fullscreen mode

Hero.tsx

'use client';

import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { useEffect, useLayoutEffect, useRef } from 'react';
import './style.css';

function Hero() {
  const introText = useRef(null);
  const secondIntroText = useRef(null);
  const finalButton = useRef(null);

  useLayoutEffect(() => {
    const tl = gsap.timeline();
    gsap.registerPlugin(ScrollTrigger);
    tl.to(introText.current, {
      duration: 0.5,
      autoAlpha: 1,
      ease: 'easeIn-Out',
      delay: 0.5,
      x: -100,
    })
      .to(secondIntroText.current, {
        duration: 0.5,
        autoAlpha: 1,
        ease: 'easeOut',
        delay: 0.2,
        x: 10,
        y: -100,
      })
      .to(finalButton.current, {
        duration: 0.5,
        autoAlpha: 1,
        ease: 'easeOut',
        delay: 0.1,
        y: 0,
        x: 0,
      });
  }, []);

  return (
    <>
      <div className='lg:2/6 xl:w-2/4 mt-20 lg:mt-40 lg:ml-16 text-left'>
        <div ref={introText} className='text-6xl font-semibold text-gray-900 leading-none intro-text'>
          Bring all your work together
        </div>
        <div ref={secondIntroText} className='mt-6 text-xl font-light text-true-gray-500 antialiased second-text'>
          A better experience for yout attendees and less stress yout team.
        </div>
        <button
          ref={finalButton}
          className='mt-6 px-8 py-4 rounded-full font-normal
         tracking-wide bg-gradient-to-b
          text-black outline-none focus:outline-none hover:shadow-lg
          bg-third transition duration-200 ease-in-out final-button'>
          Join Today
        </button>
      </div>
      <div className='mt-12 lg:mt-32 lg:ml-20 text-left'></div>
    </>
  );
}

export default Hero;
Enter fullscreen mode Exit fullscreen mode

Used in Next JS SSR


"use client";

import dynamic from "next/dynamic";
import React from "react";
import Footer from "../src/common/Footer";

const LandingPage = dynamic(() => import("../src/Pages/LandingPage"), {
  ssr: true,
});

// export async function generateStaticParams() {
//   const posts = await fetch('https://.../posts').then((res) => res.json());

//   return posts.map((post: any) => ({
//     slug: post.slug,
//   }));
// }

export default function Bootstrap() {
  return (
    <>
      <LandingPage />
      <Footer />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Maybe if i wrapped the Hero component in a react memo, it will resolve it ? cause we are trying to keep things optimized.

Image description

Nope.. not in this case.

The solution

When it comes to delays, and such, what i've noticed is when you don't compose your react components in a performant manner, it will result in undesirable behavior.

Let's go back at the code

"use client";

import dynamic from "next/dynamic";
import React from "react";
import Footer from "../src/common/Footer";

const LandingPage = dynamic(() => import("../src/Pages/LandingPage"), {
  ssr: true,
});

// export async function generateStaticParams() {
//   const posts = await fetch('https://.../posts').then((res) => res.json());

//   return posts.map((post: any) => ({
//     slug: post.slug,
//   }));
// }

export default function Bootstrap() {
  return (
    <>
      <LandingPage />
      <Footer />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

LandingPage is essentially an ssr / client component. It really is just a client side component.

Within LandingPage we have to import the Hero component. Although this makes sense to most react developers, this approach will lead to component delays, as you seen in the problem statement.

Also, our landing page might grow, we will have more components being used within this component, and their components will have dependencies that will be quite expensive to load effiencetly.

E.g

Imagine if a component you're using inside your landing page component is using a dependency that is large in size, you can imagine next js will delay the load of this component.

How do we solve this ?

You solve this issue by effective component composition.

Nadia Makarevich has a detailed post about composition in React.

In our case, this problem is super easy to resolve. We just make our LandingPage component take in children as the prop.

Like this


'use client';

import { ReactNode } from 'react';

function LandingPage({ children }: { children: ReactNode }) {
  return <>{children}</>;
}
export default LandingPage;
Enter fullscreen mode Exit fullscreen mode

Then in our Next JS file.

bootstrap.tsx (this file gets called on page.tsx by the way)

"use client";

import dynamic from "next/dynamic";
import React from "react";
import Footer from "../src/common/Footer";
import Hero from "../src/common/Hero/Hero";

const LandingPage = dynamic(() => import("../src/Pages/LandingPage"), {
  ssr: true,
});

// export async function generateStaticParams() {
//   const posts = await fetch('https://.../posts').then((res) => res.json());

//   return posts.map((post: any) => ({
//     slug: post.slug,
//   }));
// }

export default function Bootstrap() {
  return (
    <>
      <LandingPage> // landing page takes react components as children.
        <Hero />. 
      </LandingPage>
      <Footer />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

This will load our components more efficiently.

Now lets look at the result..

Image description

The animation works instantly without showing the component contents first, and then the.... animation second.

The animation works out the gate, without any weird side effects or delays.

Side note

You don't need to do this for all your client side components. You have to gauge which components are having the above mentioned side effects, before committing to make these changes.

If your components are working fine, no delays, or weird side effects, no need to change it. This is just a way to resolve said issue.

Conclusion

Hope this helps, cheers 🥳 🥳.

Top comments (0)