Included with create-next-app
Create a blog post page with dynamic route, static generation, and ISR.
1 import { notFound } from 'next/navigation'; 2 3 type Post = { slug: string; title: string; content: string; author: string }; 4 5 // Pre-render these paths at build time 6 export async function generateStaticParams() { 7 const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10'); 8 const posts = await res.json(); 9 return posts.map((p: any) => ({ slug: p.id.toString() })); 10 } 11 12 // Generate metadata for SEO 13 export async function generateMetadata({ params }: { params: { slug: string } }) { 14 const post = await getPost(params.slug); 15 if (!post) return {}; 16 return { title: post.title, description: post.content.slice(0, 160) }; 17 } 18 19 async function getPost(slug: string): Promise<Post | null> { 20 const res = await fetch( 21 `https://jsonplaceholder.typicode.com/posts/${slug}`, 22 { next: { revalidate: 3600 } } // ISR: revalidate hourly 23 ); 24 if (!res.ok) return null; 25 const data = await res.json(); 26 return { slug: data.id.toString(), title: data.title, content: data.body, author: 'Admin' }; 27 } 28 29 export default async function PostPage({ params }: { params: { slug: string } }) { 30 const post = await getPost(params.slug); 31 if (!post) notFound(); 32 33 return ( 34 <article className="max-w-2xl mx-auto p-8"> 35 <h1 className="text-3xl font-bold mb-4">{post.title}</h1> 36 <p className="text-gray-500 mb-8">By {post.author}</p> 37 <div className="prose">{post.content}</div> 38 </article> 39 ); 40 }
/posts/hello-world renders and reflects DB changes within 1 hour
Sign in to share your feedback and join the discussion.