Are your client components being delayed when using Next JS ? consider this...
Tags: nextjs, ssr, javascript
Published: 2023-12-04 05:30:41

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](https://nextjs.org/docs).

**The Problem**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7n65ovxo6rbj4planawr.gif)



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](https://gsap.com/). But no, it was stemming from somewhere else....


Let's look at the code for this.


**LandingPage.tsx**

```typescript
'use client';

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

function LandingPage() {
  return <Hero />;
}
export default LandingPage;
```

**Hero.tsx**

```typescript
'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;
```

**Used in Next JS SSR**


```typescript

"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 />
    </>
  );
}
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6eh740w4aefohydvl6h.gif)


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 

```typescript
"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 />
    </>
  );
}
```

`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](https://www.developerway.com/posts/components-composition-how-to-get-it-right) 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

```typescript

'use client';

import { ReactNode } from 'react';

function LandingPage({ children }: { children: ReactNode }) {
  return <>{children}</>;
}
export default LandingPage;
```

Then in our Next JS file.

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

```typescript
"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 />
    </>
  );
}
```

This will load our components more efficiently. 


Now lets look at the result..


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3l62vq2uln7twt5g16f.gif)



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 🥳 🥳.