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.
Top comments (5)
How do u use useEffect in server component?
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.
Thank you!
Love the simplicity without external libraries, Awesome work! 👍
hope you got benefit from it .