Infinite Scroll Blog
2025-06-212 turns4,549 charsgpt-4o
Summary
Convert a Next.js blog post into an infinite scroll blog page with dynamic post loading.
Messages
make this into a blog page that has infinite scroll: import { notFound } from "next/navigation";
import { getPostData } from "@/lib/posts";
import MarkdownRenderer from "@/components/markdown-renderer";
export { generateStaticParams } from "./generate-static-params";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default async function Page(props: any) {
const { params } = props;
const post = await getPostData(params.slug);
if (!post) {
notFound();
}
return (
<main className="container mx-auto px-4 py-8">
<article className="prose lg:prose-xl dark:prose-invert mx-auto">
<h1>{post.title}</h1>
<p className="text-gray-500 dark:text-gray-400">
{new Date(post.date).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</p>
{post.serializedContent && (
<MarkdownRenderer source={post.serializedContent} />
)}
</article>
</main>
);
}
To turn this `Page` component into a **blog with infinite scroll**, you'll need to **load multiple posts progressively** instead of rendering just one post from `params.slug`. That means this is no longer a **dynamic route for a single post**—instead, you're building a `/blog` or `/posts` **feed** page.
Below is a modified version that:
1. Fetches an initial set of blog posts.
2. Renders them in a list.
3. Adds infinite scrolling using `IntersectionObserver`.
---
### ✅ Create a new file: `/app/blog/page.tsx`
```tsx
"use client";
import { useEffect, useRef, useState } from "react";
import { getPaginatedPosts } from "@/lib/posts";
import MarkdownRenderer from "@/components/markdown-renderer";
type Post = {
slug: string;
title: string;
date: string;
serializedContent: string;
};
const POSTS_PER_PAGE = 5;
export default function BlogPage() {
const [posts, setPosts] = useState<Post[]>([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const loaderRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const loadPosts = async () => {
const { posts: newPosts, hasMore } = await getPaginatedPosts(page, POSTS_PER_PAGE);
setPosts((prev) => [...prev, ...newPosts]);
setHasMore(hasMore);
};
loadPosts();
}, [page]);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && hasMore) {
setPage((prev) => prev + 1);
}
},
{ threshold: 1.0 }
);
const loader = loaderRef.current;
if (loader) observer.observe(loader);
return () => {
if (loader) observer.unobserve(loader);
};
}, [hasMore]);
return (
<main className="container mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-8 text-center">Blog</h1>
{posts.map((post) => (
<article key={post.slug} className="prose lg:prose-xl dark:prose-invert mx-auto mb-12">
<h2>{post.title}</h2>
<p className="text-gray-500 dark:text-gray-400">
{new Date(post.date).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</p>
{post.serializedContent && (
<MarkdownRenderer source={post.serializedContent} />
)}
</article>
))}
{hasMore && (
<div ref={loaderRef} className="text-center py-8">
<span className="text-gray-500 dark:text-gray-400">Loading more...</span>
</div>
)}
</main>
);
}
```
---
### 🧠 Backend Function (`getPaginatedPosts`)
Make sure `@/lib/posts.ts` includes a function like this:
```ts
export async function getPaginatedPosts(page: number, limit: number) {
const allPosts = await getAllPosts(); // Assume this returns an array sorted by date descending
const start = (page - 1) * limit;
const end = start + limit;
const paginated = allPosts.slice(start, end);
return {
posts: paginated,
hasMore: end < allPosts.length,
};
}
```
---
### 📝 Notes
- This should live at `/blog`.
- You'll want `getAllPosts()` to return posts with serialized Markdown.
- If you’re using `getPostData(slug)` to serialize Markdown, you'll need to modify it to accept multiple slugs for batching.
- Optionally, add skeleton loaders or animated transitions with `framer-motion`.
Want me to help you wire this into your existing project structure or switch this to a REST API with SSR?
gpt-4o