Next.js
Mini Project – Blog
Build a complete blog system for NewsWave using all the concepts you've learned from routing to data fetching to API routes.
You've learned individual pieces of Next.js like routing, data fetching, and API creation. But real applications combine all these concepts together. Think of building a car — you know how the engine works, how the transmission functions, and how the brakes operate. Now you need to assemble them into something that actually drives.
The NewsWave blog system demonstrates exactly this integration. Your marketing team wants a place to publish company updates, development insights, and industry analysis. Readers should browse articles by category, read full posts, and subscribe to updates. The editorial team needs an API to manage content from their CMS.
This project connects every major Next.js concept into one cohesive application. You'll create static article pages with getStaticProps, dynamic category filtering with getServerSideProps, newsletter signups via API routes, and proper SEO optimization throughout.
Project Architecture
Before writing code, understand the complete system architecture. Every successful project starts with a clear blueprint — like an architect's drawings before construction begins. The NewsWave blog consists of several interconnected pieces that work together seamlessly.
The blog has four main user-facing pages plus two API endpoints. The home page displays recent articles with category filtering. Individual blog posts live at /blog/[slug] using static generation for speed. Category pages at /blog/category/[name] filter articles dynamically. A search page provides real-time article discovery.
Behind the scenes, the blog uses a mock database of articles stored in JSON format. Real applications would connect to databases like PostgreSQL or MongoDB, but JSON keeps this project focused on Next.js concepts rather than database management. Each article contains metadata like title, author, publication date, category, and content.
Setting Up the Blog Structure
Start by creating the folder structure and mock data. Organization matters enormously in Next.js because the file system becomes your routing system. Unlike traditional web development where you configure routes separately, Next.js derives URLs directly from your folder names and file locations.
Create the blog data first. This JSON file simulates what you'd get from a headless CMS like Contentful or Strapi. Each article needs enough metadata for filtering, sorting, and SEO optimization. The content field would normally contain markdown or rich text from your CMS.
// data/blogPosts.js - Mock blog data for NewsWave
export const blogPosts = [
{
id: 1,
slug: 'next-js-app-router-guide', // URL-friendly version of title
title: 'Complete Guide to Next.js App Router',
excerpt: 'Master the new App Router with practical examples and best practices.',
content: `The App Router revolutionizes how we build Next.js applications...`,
author: {
name: 'Sarah Chen',
avatar: '/authors/sarah.jpg'
},
category: 'Development', // For filtering and organization
publishedAt: '2024-01-15',
readTime: 8,
views: 1240
}
// More articles would go here
];The slug field deserves special attention. URLs need to be clean and SEO-friendly, so "Complete Guide to Next.js App Router" becomes "next-js-app-router-guide". This transformation removes special characters, converts to lowercase, and replaces spaces with hyphens. Many CMSs handle this automatically.
// lib/blogUtils.js - Helper functions for blog operations
export function generateSlug(title) {
return title
.toLowerCase() // Convert to lowercase first
.replace(/[^\w\s-]/g, '') // Remove special characters
.replace(/\s+/g, '-') // Replace spaces with hyphens
.trim(); // Clean up edges
}
export function getPostBySlug(slug) {
return blogPosts.find(post => post.slug === slug);
}
export function getPostsByCategory(category) {
return blogPosts.filter(post =>
post.category.toLowerCase() === category.toLowerCase()
);
}You created the foundation data structure for the blog. The slug field enables clean URLs, category field allows filtering, and metadata supports rich displays. Try this: Add more sample articles with different categories and publication dates.
Blog Home Page
The blog home page showcases your content strategy. Visitors should immediately understand what NewsWave publishes and find articles that interest them. This page needs to balance visual appeal with performance — showing enough content to engage readers without overwhelming them or slowing page loads.
Use static generation here because the blog home doesn't need real-time updates. Articles publish occasionally, not every second like stock prices or live chat. Static generation means this page loads instantly from CDN cache, giving visitors an excellent first impression of NewsWave's performance.
// app/blog/page.js - Blog home page with featured articles
import Link from 'next/link';
import { blogPosts } from '../../data/blogPosts';
export default function BlogHome() {
const featuredPosts = blogPosts
.sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt)) // Most recent first
.slice(0, 6); // Show top 6 articles
return (
<div className="max-w-6xl mx-auto px-4 py-12">
<header className="text-center mb-12">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
NewsWave Blog
</h1>
<p className="text-xl text-gray-600">
Insights on technology, development, and digital trends
</p>
</header>Notice the sorting logic. JavaScript's sort() method compares dates to put newest articles first. The slice(0, 6) limits results to six articles, preventing the page from becoming unwieldy. Users can always click through to see more.
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{featuredPosts.map(post => (
<article key={post.id} className="bg-white rounded-lg shadow-md overflow-hidden">
<div className="p-6">
<div className="flex items-center gap-2 mb-3">
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded">
{post.category}
</span>
<span className="text-gray-500 text-sm">
{post.readTime} min read
</span>
</div>
<h2 className="text-xl font-semibold mb-3 hover:text-blue-600">
<Link href={`/blog/${post.slug}`}>
{post.title}
</Link>
</h2>You created a responsive blog home page that sorts articles by date and displays them in a clean grid. The category tags and read time help visitors choose content that interests them. Try this: Add a featured article section at the top for your most important content.
Individual Blog Posts
Individual blog posts deserve the full static generation treatment. These pages benefit enormously from pre-rendering because they rarely change after publication. A blog post published today looks identical to every visitor — perfect for static generation's strengths. Search engines also prefer fast-loading pages, making SSG ideal for SEO.
The challenge lies in generating all possible blog post pages at build time. Next.js needs to know every valid slug before it can create static pages. Think of it like printing a newspaper — you need to know every article title before you can design the layout and send it to print.
// app/blog/[slug]/page.js - Individual blog post pages
import { blogPosts } from '../../../data/blogPosts';
import { notFound } from 'next/navigation';
// Tell Next.js which blog post URLs to generate at build time
export function generateStaticParams() {
return blogPosts.map(post => ({
slug: post.slug // Creates /blog/next-js-app-router-guide etc.
}));
}
export default function BlogPost({ params }) {
const post = blogPosts.find(p => p.slug === params.slug);
if (!post) {
notFound(); // Show 404 page for invalid slugs
}The generateStaticParams function tells Next.js every possible slug value. During build time, Next.js calls this function, gets the array of slugs, then generates a static HTML file for each one. This happens once at build time, not on every request.
return (
<article className="max-w-4xl mx-auto px-4 py-12">
<header className="mb-8">
<div className="flex items-center gap-4 mb-6">
<span className="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm">
{post.category}
</span>
<time className="text-gray-500">
{new Date(post.publishedAt).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</time>
</div>
<h1 className="text-4xl font-bold text-gray-900 mb-4">
{post.title}
</h1>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-gray-200 rounded-full"></div>
<div>
<p className="font-medium">{post.author.name}</p>
<p className="text-gray-500 text-sm">{post.readTime} min read</p>
</div>
</div>
<div className="text-gray-500 text-sm">
{post.views.toLocaleString()} views
</div>
</div>
</header> <div className="prose prose-lg max-w-none">
{/* In a real app, you'd render markdown content here */}
<div className="text-gray-700 leading-relaxed space-y-6">
{post.content.split('\n\n').map((paragraph, index) => (
<p key={index}>{paragraph}</p>
))}
</div>
</div>
<footer className="mt-12 pt-8 border-t">
<div className="flex items-center justify-between">
<Link
href="/blog"
className="text-blue-600 hover:text-blue-800 font-medium"
>
← Back to Blog
</Link>
<div className="flex gap-4">
<button className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
Share
</button>
</div>
</div>
</footer>
</article>
);
}Newsletter API Route
API routes handle server-side operations like newsletter signups. Unlike pages that return HTML, API routes return JSON data and handle HTTP methods like POST, GET, and DELETE. Think of them as the backend functions that power your frontend interactions.
Newsletter signups need server-side processing because email validation and database operations shouldn't happen in the browser. Sensitive operations like connecting to email services require API keys that must stay secret on the server. Client-side JavaScript exposes everything to visitors.
// app/api/newsletter/route.js - Newsletter signup endpoint
export async function POST(request) {
try {
const { email, name } = await request.json(); // Get form data from request
// Basic email validation
if (!email || !email.includes('@')) {
return Response.json(
{ error: 'Valid email address required' },
{ status: 400 }
);
}
// In a real app, you'd save to database or call email service API
console.log('New newsletter signup:', { email, name });
return Response.json({
message: 'Successfully subscribed to NewsWave newsletter!'
});
} catch (error) {
return Response.json(
{ error: 'Failed to process subscription' },
{ status: 500 }
);
}
}Now create the frontend signup form that calls this API. Forms need proper error handling and loading states to provide good user experience. Users should know when their submission succeeded or failed, and they shouldn't be able to submit twice while processing.
// components/NewsletterSignup.js - Newsletter form component
'use client';
import { useState } from 'react';
export default function NewsletterSignup() {
const [email, setEmail] = useState('');
const [name, setName] = useState('');
const [status, setStatus] = useState('idle'); // idle, loading, success, error
const [message, setMessage] = useState('');
async function handleSubmit(e) {
e.preventDefault();
setStatus('loading');
try {
const response = await fetch('/api/newsletter', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, name })
});
const data = await response.json();
if (response.ok) {
setStatus('success');
setMessage(data.message);
setEmail(''); // Clear form
setName('');
} else {
setStatus('error');
setMessage(data.error);
}
} catch (error) {
setStatus('error');
setMessage('Network error. Please try again.');
}
}You built a complete newsletter signup system with API endpoint and frontend form. The form handles loading states, shows success/error messages, and prevents double submissions. Try this: Add email format validation and integrate with a service like Mailchimp or ConvertKit.
SEO and Metadata
SEO makes your blog discoverable through search engines. Without proper metadata, even great content remains invisible to potential readers. Search engines rely on title tags, meta descriptions, and structured data to understand and rank your pages. Think of SEO as the storefront window that attracts people to come inside your blog.
Next.js provides built-in SEO support through the metadata API. This system generates proper HTML meta tags automatically, ensuring search engines and social media platforms can properly display your content when shared. Each blog post needs unique metadata based on its content.
// app/blog/[slug]/page.js - Add metadata generation
export async function generateMetadata({ params }) {
const post = blogPosts.find(p => p.slug === params.slug);
if (!post) {
return {
title: 'Post Not Found - NewsWave Blog'
};
}
return {
title: `${post.title} - NewsWave Blog`, // Browser tab title
description: post.excerpt, // Search result snippet
authors: [{ name: post.author.name }],
keywords: [post.category, 'development', 'technology', 'blog'],
openGraph: { // Social media sharing
title: post.title,
description: post.excerpt,
type: 'article',
publishedTime: post.publishedAt,
authors: [post.author.name],
section: post.category
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt
}
};
}The generateMetadata function runs for each blog post page, creating customized SEO tags. OpenGraph tags control how your articles appear when shared on Facebook, LinkedIn, and other social platforms. Twitter cards provide rich previews in tweets.
// app/blog/page.js - SEO for blog home page
export const metadata = {
title: 'NewsWave Blog - Technology and Development Insights',
description: 'Discover the latest in web development, technology trends, and programming best practices. Expert insights from the NewsWave team.',
keywords: ['blog', 'technology', 'web development', 'programming', 'tutorials'],
openGraph: {
title: 'NewsWave Blog',
description: 'Technology and development insights',
type: 'website',
url: 'https://newswave.com/blog'
}
};
// Add structured data for better search engine understanding
export default function BlogHome() {
const structuredData = {
"@context": "https://schema.org",
"@type": "Blog",
"name": "NewsWave Blog",
"description": "Technology and development insights",
"url": "https://newswave.com/blog",
"author": {
"@type": "Organization",
"name": "NewsWave"
}
};You added comprehensive SEO metadata to your blog. Each post gets unique title tags, meta descriptions, and social sharing previews. Search engines can now properly index and display your content. Try this: Use Google's Rich Results Test to validate your structured data.
Bringing It All Together
Your NewsWave blog system now combines multiple Next.js concepts into one cohesive application. The blog home uses static generation for speed, individual posts pre-render at build time, the newsletter API handles server-side operations, and SEO metadata ensures discoverability. This integration demonstrates how real Next.js applications work.
But the project could expand further. You could add category filtering pages, search functionality, comment systems, or admin dashboards. Each feature would use different Next.js patterns — server-side rendering for real-time search, API routes for comments, middleware for authentication. The foundation you've built supports any direction.
Static generation, image optimization, automatic code splitting, and CDN caching for lightning-fast page loads.
Metadata generation, structured data, social sharing tags, and clean URLs for maximum search visibility.
Server-side newsletter processing, form handling, and error management with proper HTTP status codes.
Responsive design, loading states, error handling, and intuitive navigation throughout the blog system.
The beauty of this approach lies in its scalability. You started with a simple blog but created architecture that handles thousands of articles, hundreds of categories, and millions of visitors. Next.js provides the performance optimizations automatically — you focus on content and features rather than infrastructure concerns.
For production deployment, consider adding error boundaries, monitoring, analytics tracking, and content management integration. Test thoroughly on mobile devices and ensure accessibility compliance.
Quiz
1. The NewsWave blog uses dynamic routes like /blog/[slug]. What is the purpose of the generateStaticParams function in this context?
2. Why does the NewsWave newsletter signup use an API route instead of handling the form submission entirely on the client side?
3. How does the generateMetadata function improve SEO for individual NewsWave blog posts?
Up Next: Mini Project – Dashboard
Build an admin dashboard with real-time data, charts, user management, and advanced authentication using Next.js server components and API routes.