Next.js tutorial with api and dynamic page routing implement with react and server client page
Building a Modern Blog with Next.js 15: Complete Guide to App Router, Server & Client Components, Dynamic Routes, and React Hooks
Next.js has become the de facto framework for building fast, SEO-friendly React applications. In this massive tutorial, we’ll explore the App Router, Server Components, Client Components, dynamic routing, and real-world usage of React hooks like useState, useEffect, useParams, etc.
You’ll get production-grade code examples for a Blog Application.
1. Next.js Project Structure (App Router)
1my-blog-app/ 2├── app/ 3│ ├── api/ # Route Handlers 4│ ├── blog/ 5│ │ └── [slug]/ 6│ │ └── page.tsx # Dynamic route 7│ ├── layout.tsx # Root layout 8│ ├── page.tsx # Home page (Server Component by default) 9│ ├── globals.css 10│ └── loading.tsx # Loading UI 11├── components/ # Reusable React components 12├── lib/ # Utilities, API calls 13├── public/ # Static assets 14├── types/ # TypeScript definitions 15├── next.config.mjs 16├── tsconfig.json 17└── package.json
Key Files Explained:
app/page.tsx→ Renders the homepage. Server Component by default.app/[slug]/page.tsx→ Dynamic route for individual blog posts.app/layout.tsx→ Wraps all pages with shared UI (navbar, footer).- Server Components → Run on the server, no bundle size impact.
- Client Components → Use
"use client"directive.
2. Complete app/layout.tsx
1// app/layout.tsx (Server Component) 2import type { Metadata } from 'next'; 3import { Inter } from 'next/font/google'; 4import './globals.css'; 5import Navbar from '@/components/Navbar'; 6import Footer from '@/components/Footer'; 7 8const inter = Inter({ subsets: ['latin'] }); 9 10export const metadata: Metadata = { 11 title: 'DevBlog | Modern Tech Insights', 12 description: 'A Next.js 15 Blog with Server & Client Components', 13 openGraph: { 14 images: '/og-image.png', 15 }, 16}; 17 18export default function RootLayout({ 19 children, 20}: { 21 children: React.ReactNode; 22}) { 23 return ( 24 <html lang="en"> 25 <body className={inter.className}> 26 <Navbar /> 27 <main className="min-h-screen">{children}</main> 28 <Footer /> 29 </body> 30 </html> 31 ); 32}
3. Homepage – app/page.tsx (Server Component)
1// app/page.tsx 2import Link from 'next/link'; 3import { getAllPosts } from '@/lib/posts'; 4 5export default async function Home() { 6 const posts = await getAllPosts(); 7 8 return ( 9 <div className="max-w-6xl mx-auto px-6 py-12"> 10 <h1 className="text-5xl font-bold mb-4">Latest Articles</h1> 11 <p className="text-xl text-gray-600 mb-12"> 12 Built with Next.js App Router + React Server Components 13 </p> 14 15 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> 16 {posts.map((post) => ( 17 <article key={post.slug} className="border rounded-xl overflow-hidden hover:shadow-xl transition"> 18 <img src={post.image} alt={post.title} className="w-full h-48 object-cover" /> 19 <div className="p-6"> 20 <div className="text-sm text-blue-600 mb-2">{post.category}</div> 21 <Link href={`/blog/${post.slug}`}> 22 <h2 className="text-2xl font-semibold mb-3 hover:text-blue-600">{post.title}</h2> 23 </Link> 24 <p className="text-gray-600 line-clamp-3 mb-4">{post.excerpt}</p> 25 <div className="flex items-center justify-between text-sm"> 26 <span>{post.author}</span> 27 <span>{new Date(post.date).toLocaleDateString()}</span> 28 </div> 29 </div> 30 </article> 31 ))} 32 </div> 33 </div> 34 ); 35}
How it works: This is a Server Component. Data fetching happens on the server at request time (or build time with caching).
4. Dynamic Route – app/blog/[slug]/page.tsx
1// app/blog/[slug]/page.tsx 2import { getPostBySlug, getAllPosts } from '@/lib/posts'; 3import { notFound } from 'next/navigation'; 4import ClientCommentSection from '@/components/ClientCommentSection'; 5import RelatedPosts from '@/components/RelatedPosts'; 6 7export async function generateStaticParams() { 8 const posts = await getAllPosts(); 9 return posts.map((post) => ({ slug: post.slug })); 10} 11 12export default async function BlogPost({ params }: { params: { slug: string } }) { 13 const post = await getPostBySlug(params.slug); 14 15 if (!post) notFound(); 16 17 return ( 18 <article className="max-w-4xl mx-auto px-6 py-12"> 19 <header className="mb-12"> 20 <div className="text-sm uppercase tracking-widest text-blue-600 mb-4">{post.category}</div> 21 <h1 className="text-5xl font-bold leading-tight mb-6">{post.title}</h1> 22 <div className="flex items-center gap-4 text-gray-500"> 23 <span>By {post.author}</span> 24 <span>•</span> 25 <time>{new Date(post.date).toLocaleDateString('en-US', { dateStyle: 'long' })}</time> 26 </div> 27 </header> 28 29 <img src={post.image} alt={post.title} className="w-full rounded-2xl mb-12" /> 30 31 <div className="prose prose-lg max-w-none" dangerouslySetInnerHTML={{ __html: post.content }} /> 32 33 {/* Client Component for interactivity */} 34 <ClientCommentSection postSlug={params.slug} /> 35 36 <RelatedPosts currentSlug={params.slug} /> 37 </article> 38 ); 39}
Dynamic Route Explanation:
[slug]folder creates a dynamic segment.params.sluggives the value from URL.generateStaticParams()enables Static Generation for known slugs.
5. Client Component Example – components/ClientCommentSection.tsx
1// components/ClientCommentSection.tsx 2'use client'; 3 4import { useState, useEffect } from 'react'; 5 6type Comment = { 7 id: number; 8 author: string; 9 content: string; 10 date: string; 11}; 12 13export default function ClientCommentSection({ postSlug }: { postSlug: string }) { 14 const [comments, setComments] = useState<Comment[]>([]); 15 const [newComment, setNewComment] = useState(''); 16 const [isLoading, setIsLoading] = useState(false); 17 const [user, setUser] = useState('Guest'); 18 19 // useEffect example - Fetch comments 20 useEffect(() => { 21 const fetchComments = async () => { 22 setIsLoading(true); 23 try { 24 const res = await fetch(`/api/comments/${postSlug}`); 25 const data = await res.json(); 26 setComments(data); 27 } catch (error) { 28 console.error('Failed to fetch comments', error); 29 } finally { 30 setIsLoading(false); 31 } 32 }; 33 34 fetchComments(); 35 }, [postSlug]); 36 37 const handleSubmit = async (e: React.FormEvent) => { 38 e.preventDefault(); 39 if (!newComment.trim()) return; 40 41 setIsLoading(true); 42 try { 43 const res = await fetch(`/api/comments/${postSlug}`, { 44 method: 'POST', 45 headers: { 'Content-Type': 'application/json' }, 46 body: JSON.stringify({ content: newComment, author: user }), 47 }); 48 49 if (res.ok) { 50 const comment = await res.json(); 51 setComments((prev) => [comment, ...prev]); 52 setNewComment(''); 53 } 54 } catch (error) { 55 alert('Failed to post comment'); 56 } finally { 57 setIsLoading(false); 58 } 59 }; 60 61 return ( 62 <div className="mt-16 border-t pt-12"> 63 <h3 className="text-3xl font-semibold mb-8">Comments ({comments.length})</h3> 64 65 {/* Comment Form */} 66 <form onSubmit={handleSubmit} className="mb-12"> 67 <textarea 68 value={newComment} 69 onChange={(e) => setNewComment(e.target.value)} 70 placeholder="Write your thoughts..." 71 className="w-full p-4 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500" 72 rows={4} 73 /> 74 <button 75 type="submit" 76 disabled={isLoading} 77 className="mt-4 px-8 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 disabled:opacity-50" 78 > 79 {isLoading ? 'Posting...' : 'Post Comment'} 80 </button> 81 </form> 82 83 {/* Comments List */} 84 {comments.map((comment) => ( 85 <div key={comment.id} className="border-b pb-8 mb-8"> 86 <div className="flex justify-between"> 87 <strong>{comment.author}</strong> 88 <span className="text-sm text-gray-500">{comment.date}</span> 89 </div> 90 <p className="mt-3 text-gray-700">{comment.content}</p> 91 </div> 92 ))} 93 </div> 94 ); 95}
React Hooks Used:
useState→ Manage comments and form state.useEffect→ Fetch data on mount and when slug changes.
6. More Client Component Examples
Search Bar with useState + useEffect Debounce
1// components/SearchBar.tsx 2'use client'; 3 4import { useState, useEffect } from 'react'; 5import { useRouter } from 'next/navigation'; 6 7export default function SearchBar() { 8 const [query, setQuery] = useState(''); 9 const [results, setResults] = useState<any[]>([]); 10 const router = useRouter(); 11 12 useEffect(() => { 13 const timer = setTimeout(async () => { 14 if (query.length > 2) { 15 const res = await fetch(`/api/search?q=${query}`); 16 const data = await res.json(); 17 setResults(data); 18 } else { 19 setResults([]); 20 } 21 }, 300); 22 23 return () => clearTimeout(timer); 24 }, [query]); 25 26 return ( 27 <div className="relative"> 28 <input 29 type="text" 30 value={query} 31 onChange={(e) => setQuery(e.target.value)} 32 placeholder="Search articles..." 33 className="w-full px-5 py-3 border rounded-full" 34 /> 35 {results.length > 0 && ( 36 <div className="absolute mt-2 w-full bg-white border rounded-xl shadow-xl z-50 max-h-96 overflow-auto"> 37 {results.map((post) => ( 38 <div 39 key={post.slug} 40 onClick={() => { 41 router.push(`/blog/${post.slug}`); 42 setQuery(''); 43 setResults([]); 44 }} 45 className="p-4 hover:bg-gray-100 cursor-pointer" 46 > 47 {post.title} 48 </div> 49 ))} 50 </div> 51 )} 52 </div> 53 ); 54}
7. Data Fetching Utilities (lib/posts.ts)
1// lib/posts.ts (Server-side) 2export async function getAllPosts() { 3 // Simulate database or fetch from CMS 4 return [ 5 { 6 slug: 'nextjs-15-deep-dive', 7 title: 'Next.js 15: What’s New in App Router', 8 excerpt: 'Server Actions, Partial Prerendering, and more...', 9 image: '/images/nextjs15.jpg', 10 author: 'Grok', 11 category: 'Web Development', 12 date: '2026-06-01', 13 content: '<p>Full HTML content here...</p>', 14 }, 15 // ... more posts 16 ]; 17} 18 19export async function getPostBySlug(slug: string) { 20 const posts = await getAllPosts(); 21 return posts.find((p) => p.slug === slug); 22}
8. API Route Handler Example
1// app/api/comments/[slug]/route.ts 2import { NextRequest, NextResponse } from 'next/server'; 3 4let commentsDB: any = {}; 5 6export async function GET( 7 request: NextRequest, 8 { params }: { params: { slug: string } } 9) { 10 const slugComments = commentsDB[params.slug] || []; 11 return NextResponse.json(slugComments); 12} 13 14export async function POST( 15 request: NextRequest, 16 { params }: { params: { slug: string } } 17) { 18 const body = await request.json(); 19 const newComment = { 20 id: Date.now(), 21 ...body, 22 date: new Date().toISOString(), 23 }; 24 25 if (!commentsDB[params.slug]) commentsDB[params.slug] = []; 26 commentsDB[params.slug].push(newComment); 27 28 return NextResponse.json(newComment, { status: 201 }); 29}
9. Additional Components
Navbar (components/Navbar.tsx) – Client Component for mobile menu with useState.
Footer, RelatedPosts, Loading Skeleton, etc.
10. Best Practices in Next.js 15 (2026)
- Prefer Server Components — Keep them default.
- Use
"use client"only when interactivity is needed. - Streaming & Partial Prerendering for better UX.
- Server Actions for forms (no need for full API routes in many cases).
- Caching strategies with
fetch+revalidatePath. - TypeScript everywhere.
11. Running the Project
1npx create-next-app@latest my-blog-app --typescript --tailwind --eslint --app 2cd my-blog-app 3npm run dev
Visit:
http://localhost:3000→ Homepage (Server)http://localhost:3000/blog/nextjs-15-deep-dive→ Dynamic post