DEV Community

Kawan Idrees
Kawan Idrees

Posted on

Implementing Infinite Scroll in Next.js with SSG Without Any Library

Infinite scroll is a popular feature in modern web applications, providing a seamless way for users to access more content without the disruption of pagination. In the context of a Next.js application using Static Site Generation (SSG), implementing infinite scroll can be particularly interesting. Next.js's SSG allows for faster load times and better SEO, but it generally pre-renders pages at build time, which seems at odds with the dynamic nature of infinite scroll. However, with a bit of creativity, we can efficiently combine SSG with client-side data fetching to achieve this.

Understanding the Challenge
Static Site Generation (SSG) in Next.js pre-renders pages at build time. This is fantastic for performance and SEO but poses a challenge for features like infinite scroll, which relies on dynamically loading content. The key is to pre-render a significant initial portion of the content statically and then dynamically load additional content as needed.

Initial Setup with getStaticProps
First, we pre-render an initial set of data using getStaticProps. For this example, we'll use the JSONPlaceholder's /photos endpoint to simulate fetching data.

// pages/index.js
export async function getStaticProps() {
  const res = await fetch('https://jsonplaceholder.typicode.com/photos?_limit=10');
  const initialData = await res.json();
  return { props: { initialData } };
}

Enter fullscreen mode Exit fullscreen mode

Implementing Infinite Scroll on the Client Side
Next, we set up a mechanism to detect when the user has scrolled to the bottom of the page, triggering additional data fetches.


import Image from "next/image";
import { useCallback, useEffect, useState } from "react";

const IndexPage = ({ initialData }) => {
  const [data, setData] = useState(initialData);
  const [page, setPage] = useState(1);
  const [isLoading, setIsLoading] = useState(false); // New state for loading
  const loadMoreData = async () => {
    setIsLoading(true);
    const moreData = await fetch(`https://jsonplaceholder.typicode.com/photos?_limit=10&_page=${page + 1}`)
      .then(res => res.json());
    setData(currentData => [...currentData, ...moreData]);
    setPage(currentPage => currentPage + 1);
    setIsLoading(false);
  };

  const onScroll = useCallback(async () => { 
    if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100 && !isLoading) {
      await loadMoreData();
    }
  }, [isLoading, page]); // Dependencies

  useEffect(() => {
    window.addEventListener('scroll', onScroll);
    return () => window.removeEventListener('scroll', onScroll);
  }, [onScroll]); 

  return (
    <div
      style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
    >
      {data.map((item) => (
        <div
          key={item.id}
          style={{
            marginBottom: "20px",
            position: "relative",
            width: "400px",
            height: "400px",
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
          }}
        >
          <Image
            src={item.url}
            alt={item.title}
            fill
            style={{ objectFit: "contain" }}
          />
        </div>
      ))}
      {isLoading && <p>Loading more images...</p>} {/* Loading indicator */}
    </div>
  );
};

export default IndexPage;


Enter fullscreen mode Exit fullscreen mode

Adding a Loading Indicator for Better UX
To improve the user experience, we include a loading state to indicate when more data is being fetched.

Key Considerations
1-No External Library: This implementation relies solely on Next.js and native JavaScript, avoiding additional dependencies.
2-Optimizing Performance: Consider debouncing the scroll event and adding a throttle mechanism to reduce the number of API calls during rapid scrolling.
3-Handling the End of Data: Implement logic to handle the scenario when there is no more data to load.
4-Styling and UX: The loading indicator and content styling can be enhanced for a more engaging user experience.

don't forget to adjust your next.config.js because the remote images has to be configured in next js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,


  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'via.placeholder.com',
        port: '',
        pathname: '/**',
      },
    ],
  },
};

export default nextConfig;

Enter fullscreen mode Exit fullscreen mode

Conclusion
Combining SSG with client-side data fetching in Next.js offers a powerful approach to implementing features like infinite scroll. While SSG optimizes initial load performance and SEO, client-side fetching ensures dynamic, interactive user experiences. This blend of static and dynamic content delivery represents the versatility and power of modern web development frameworks like Next.js.

Top comments (5)

Collapse
 
andrew_kalita_0831f963f8e profile image
Andrew Kalita

How do u use useEffect in server component?

Collapse
 
kawanedres profile image
Kawan Idrees

Actually it's not a server component you can just wrap it with a client component which is we are retrieving data from a server component and we will pass it to a child component which is client side not server.

Collapse
 
andrew_kalita_0831f963f8e profile image
Andrew Kalita

Thank you!

Collapse
 
joseph42a profile image
Joseph42A

Love the simplicity without external libraries, Awesome work! 👍

Collapse
 
kawanedres profile image
Kawan Idrees

hope you got benefit from it .