Next.js Personal Blog
Create a beautiful Personal Blog in Next.js App Router demonstrating static rendering, dynamic routing, metadata configuration, and markdown rendering architecture.
The Problem
Create a beautiful Personal Blog in Next.js App Router demonstrating static rendering, dynamic routing, metadata configuration, and markdown rendering architecture.
Real-World Use Case
Create a beautiful Personal Blog in Next.js App Router demonstrating static rendering, dynamic routing, metadata configuration, and markdown rendering architecture.
Technology Stack
Understanding of React components
Prerequisite
Familiarity with Next.js App Router structure
Prerequisite
Basic understanding of SEO metadata
Prerequisite
Architecture & Design
Folder Structure
personal_blog/
├── app/
│ ├── blog/
│ │ └── [slug]/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
└── package.jsonStep-by-Step Implementation
Initialize Next.js App Router project.
### Step 1: Project Setup Initialize Next.js project with TypeScript and App Router.
// Sample implementation of app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
const POSTS = [
{ slug: 'first-post', title: 'Getting Started with Next.js 15', date: '2026-05-01', content: 'Next.js App Router brings server components and advanced caching to frontend development.' },
{ slug: 'seo-guide', title: 'Next.js SEO Best Practices', date: '2026-05-05', content: 'Metadata configuration and static rendering are essential for ranking on Google.' },
];
export function generateStaticParams() {
return POSTS.map(post => ({ slug: post.slug }));
}
export function generateMetadata({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) return { title: 'Not Found' };
return { title: `${post.title} | Personal Blog`, description: post.content };
}
export default function BlogPostPage({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) notFound();
return (
<article className="max-w-2xl mx-auto my-12 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-3xl font-bold text-slate-900 mb-2">{post.title}</h1>
<time className="text-xs text-slate-500 mb-6 block">{post.date}</time>
<div className="prose text-sm text-slate-700 leading-relaxed pt-6 border-t border-slate-100">
<p>{post.content}</p>
</div>
</article>
);
}Code Explanation
Implementation step
Create blog post data list array.
### Step 2: Data & Static Routing Define blog posts array and generateStaticParams.
// Sample implementation of app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
const POSTS = [
{ slug: 'first-post', title: 'Getting Started with Next.js 15', date: '2026-05-01', content: 'Next.js App Router brings server components and advanced caching to frontend development.' },
{ slug: 'seo-guide', title: 'Next.js SEO Best Practices', date: '2026-05-05', content: 'Metadata configuration and static rendering are essential for ranking on Google.' },
];
export function generateStaticParams() {
return POSTS.map(post => ({ slug: post.slug }));
}
export function generateMetadata({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) return { title: 'Not Found' };
return { title: `${post.title} | Personal Blog`, description: post.content };
}
export default function BlogPostPage({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) notFound();
return (
<article className="max-w-2xl mx-auto my-12 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-3xl font-bold text-slate-900 mb-2">{post.title}</h1>
<time className="text-xs text-slate-500 mb-6 block">{post.date}</time>
<div className="prose text-sm text-slate-700 leading-relaxed pt-6 border-t border-slate-100">
<p>{post.content}</p>
</div>
</article>
);
}Code Explanation
Implementation step
Implement blog overview page rendering list of article cards.
### Step 3: Page Layouts Build home page and dynamic article detail page.
// Sample implementation of app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
const POSTS = [
{ slug: 'first-post', title: 'Getting Started with Next.js 15', date: '2026-05-01', content: 'Next.js App Router brings server components and advanced caching to frontend development.' },
{ slug: 'seo-guide', title: 'Next.js SEO Best Practices', date: '2026-05-05', content: 'Metadata configuration and static rendering are essential for ranking on Google.' },
];
export function generateStaticParams() {
return POSTS.map(post => ({ slug: post.slug }));
}
export function generateMetadata({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) return { title: 'Not Found' };
return { title: `${post.title} | Personal Blog`, description: post.content };
}
export default function BlogPostPage({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) notFound();
return (
<article className="max-w-2xl mx-auto my-12 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-3xl font-bold text-slate-900 mb-2">{post.title}</h1>
<time className="text-xs text-slate-500 mb-6 block">{post.date}</time>
<div className="prose text-sm text-slate-700 leading-relaxed pt-6 border-t border-slate-100">
<p>{post.content}</p>
</div>
</article>
);
}Code Explanation
Implementation step
Implement blog/[slug]/page.tsx dynamic route handler.
### Step 4: SEO & Edge Cases Ensure notFound() triggers for unknown slugs.
// Sample implementation of app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
const POSTS = [
{ slug: 'first-post', title: 'Getting Started with Next.js 15', date: '2026-05-01', content: 'Next.js App Router brings server components and advanced caching to frontend development.' },
{ slug: 'seo-guide', title: 'Next.js SEO Best Practices', date: '2026-05-05', content: 'Metadata configuration and static rendering are essential for ranking on Google.' },
];
export function generateStaticParams() {
return POSTS.map(post => ({ slug: post.slug }));
}
export function generateMetadata({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) return { title: 'Not Found' };
return { title: `${post.title} | Personal Blog`, description: post.content };
}
export default function BlogPostPage({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) notFound();
return (
<article className="max-w-2xl mx-auto my-12 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-3xl font-bold text-slate-900 mb-2">{post.title}</h1>
<time className="text-xs text-slate-500 mb-6 block">{post.date}</time>
<div className="prose text-sm text-slate-700 leading-relaxed pt-6 border-t border-slate-100">
<p>{post.content}</p>
</div>
</article>
);
}Code Explanation
Implementation step
Implement generateStaticParams for build-time pre-rendering.
### Step 4: SEO & Edge Cases Ensure notFound() triggers for unknown slugs.
// Sample implementation of app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
const POSTS = [
{ slug: 'first-post', title: 'Getting Started with Next.js 15', date: '2026-05-01', content: 'Next.js App Router brings server components and advanced caching to frontend development.' },
{ slug: 'seo-guide', title: 'Next.js SEO Best Practices', date: '2026-05-05', content: 'Metadata configuration and static rendering are essential for ranking on Google.' },
];
export function generateStaticParams() {
return POSTS.map(post => ({ slug: post.slug }));
}
export function generateMetadata({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) return { title: 'Not Found' };
return { title: `${post.title} | Personal Blog`, description: post.content };
}
export default function BlogPostPage({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) notFound();
return (
<article className="max-w-2xl mx-auto my-12 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-3xl font-bold text-slate-900 mb-2">{post.title}</h1>
<time className="text-xs text-slate-500 mb-6 block">{post.date}</time>
<div className="prose text-sm text-slate-700 leading-relaxed pt-6 border-t border-slate-100">
<p>{post.content}</p>
</div>
</article>
);
}Code Explanation
Implementation step
Configure generateMetadata for dynamic Open Graph tags.
### Step 4: SEO & Edge Cases Ensure notFound() triggers for unknown slugs.
// Sample implementation of app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
const POSTS = [
{ slug: 'first-post', title: 'Getting Started with Next.js 15', date: '2026-05-01', content: 'Next.js App Router brings server components and advanced caching to frontend development.' },
{ slug: 'seo-guide', title: 'Next.js SEO Best Practices', date: '2026-05-05', content: 'Metadata configuration and static rendering are essential for ranking on Google.' },
];
export function generateStaticParams() {
return POSTS.map(post => ({ slug: post.slug }));
}
export function generateMetadata({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) return { title: 'Not Found' };
return { title: `${post.title} | Personal Blog`, description: post.content };
}
export default function BlogPostPage({ params }) {
const post = POSTS.find(p => p.slug === params.slug);
if (!post) notFound();
return (
<article className="max-w-2xl mx-auto my-12 p-8 bg-white rounded-3xl shadow-sm border border-slate-200">
<h1 className="text-3xl font-bold text-slate-900 mb-2">{post.title}</h1>
<time className="text-xs text-slate-500 mb-6 block">{post.date}</time>
<div className="prose text-sm text-slate-700 leading-relaxed pt-6 border-t border-slate-100">
<p>{post.content}</p>
</div>
</article>
);
}Code Explanation
Implementation step
Common Errors
Ensure param keys exactly match folder name [slug].
Security & Performance
Navigate to /blog/first-post and verify metadata title.
Test unknown slug to ensure 404 page renders.
Add MDX parsing capabilities.
Add dark mode toggling.
Implement reading time estimation.
Interview Questions
Q: Why use generateStaticParams?
A: It allows Next.js to pre-render dynamic routes into static HTML at build time for maximum speed.