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 } };
}
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;
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;
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.