A modern, full-featured blogging platform built with Next.js 14, featuring authentication, rich text editing, comments, categories, and dark mode support. This project demonstrates best practices in React, Next.js App Router, Prisma ORM, MongoDB, and NextAuth.js.
- Overview
- Features
- Tech Stack
- Project Structure
- Getting Started
- Environment Variables
- Database Setup
- Running the Project
- Components Documentation
- API Endpoints
- Routes & Pages
- Key Functionalities
- Code Examples
- Reusing Components
- Keywords
- Conclusion
This is a complete blogging platform where users can:
- Read blog posts across multiple categories (Style, Fashion, Food, Travel, Culture, Coding)
- Create new blog posts with rich text editor and image uploads
- Comment on posts (requires authentication)
- Authenticate using Google OAuth or GitHub OAuth
- Browse posts by category with pagination
- Experience dark/light theme toggle
The application uses Next.js 14 App Router for server-side rendering, Prisma ORM with MongoDB for data persistence, NextAuth.js for authentication, Firebase Storage for image uploads, and React Quill for rich text editing.
- β User Authentication - Google & GitHub OAuth integration
- β Blog Post Management - Create, read, and view posts
- β Rich Text Editor - React Quill with HTML content support
- β Image Upload - Firebase Storage integration for post images
- β Comments System - Authenticated users can comment on posts
- β Category Filtering - Browse posts by category (Style, Fashion, Food, Travel, Culture, Coding)
- β Pagination - Navigate through multiple pages of posts
- β Dark Mode - Toggle between light and dark themes
- β View Counter - Track post views automatically
- β SEO Optimized - Dynamic metadata generation for all pages
- β Responsive Design - Mobile-first responsive layout
- β Server Components - Leverages Next.js 14 App Router for optimal performance
- β API Routes - RESTful API endpoints for data operations
- β Database Transactions - Atomic operations for data consistency
- β Image Optimization - Next.js Image component with automatic optimization
- β Type Safety - Prisma schema ensures type-safe database queries
- β Session Management - Secure session handling with NextAuth.js
- Next.js 14.2.35 - React framework with App Router
- React 18.3.1 - UI library
- CSS Modules - Scoped styling
- React Quill 2.0.0 - Rich text editor
- SWR 2.2.2 - Data fetching and caching
- Next.js API Routes - Serverless API endpoints
- Prisma 5.2.0 - Type-safe ORM
- MongoDB - NoSQL database
- NextAuth.js 4.23.1 - Authentication library
- Firebase Storage - Image hosting
- Google OAuth - Authentication provider
- GitHub OAuth - Authentication provider
- TypeScript/TSX - Type checking for seed scripts
lamablog/
βββ prisma/
β βββ schema.prisma # Database schema definition
β βββ seed.ts # Database seeding script
βββ public/ # Static assets (images, icons)
βββ src/
β βββ app/ # Next.js App Router pages
β β βββ api/ # API routes
β β β βββ auth/
β β β β βββ [...nextauth]/route.js
β β β βββ posts/
β β β β βββ route.js # GET, POST /api/posts
β β β β βββ [slug]/route.js # GET /api/posts/[slug]
β β β βββ categories/route.js
β β β βββ comments/route.js
β β βββ blog/page.jsx # Blog listing page
β β βββ posts/[slug]/page.jsx # Single post page
β β βββ write/page.jsx # Create post page
β β βββ login/page.jsx # Login page
β β βββ page.jsx # Homepage
β β βββ layout.js # Root layout
β β βββ globals.css # Global styles
β βββ components/ # Reusable React components
β β βββ navbar/
β β βββ footer/
β β βββ card/
β β βββ cardList/
β β βββ categoryList/
β β βββ featured/
β β βββ comments/
β β βββ pagination/
β β βββ authLinks/
β β βββ themeToggle/
β β βββ Menu/
β βββ context/
β β βββ ThemeContext.jsx # Theme state management
β βββ providers/
β β βββ AuthProvider.jsx # NextAuth session provider
β β βββ ThemeProvider.jsx # Theme class provider
β βββ utils/
β βββ connect.js # Prisma client singleton
β βββ auth.js # NextAuth configuration
β βββ firebase.js # Firebase configuration
βββ .env # Environment variables
βββ next.config.js # Next.js configuration
βββ package.json # Dependencies and scripts
βββ README.md # This file- Node.js 18.x or higher
- npm or yarn package manager
- MongoDB database (local or remote)
- Google OAuth credentials (for authentication)
- Firebase project (for image storage)
- Clone the repository
git clone <repository-url>
cd lamablog- Install dependencies
npm install-
Set up environment variables (see Environment Variables section)
-
Set up the database (see Database Setup section)
-
Run the development server
npm run dev- Open your browser
Navigate to http://localhost:3000
Create a .env file in the root directory with the following variables:
# Database Connection
DATABASE_URL=mongodb://username:password@host:port/database?authSource=database&replicaSet=rs0
# NextAuth Configuration
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-secret-key-here
# Google OAuth (for authentication)
GOOGLE_ID=your-google-client-id
GOOGLE_SECRET=your-google-client-secret
# GitHub OAuth (optional, for GitHub login)
GITHUB_ID=your-github-client-id
GITHUB_SECRET=your-github-client-secret
# Firebase Configuration (for image uploads)
FIREBASE=your-firebase-api-key
# Site URL (for production)
NEXT_PUBLIC_SITE_URL=https://your-domain.comFor MongoDB Atlas (Cloud):
mongodb+srv://username:password@cluster.mongodb.net/database?retryWrites=true&w=majority
For Local MongoDB:
mongodb://localhost:27017/database
For Self-Hosted VPS MongoDB:
mongodb://username:password@your-vps-ip:port/database?authSource=database&replicaSet=rs0
Generate a random secret:
openssl rand -base64 32Or use an online generator: https://generate-secret.vercel.app/32
- Go to Google Cloud Console
- Create a new project or select existing
- Enable Google+ API
- Go to Credentials β Create Credentials β OAuth 2.0 Client ID
- Configure consent screen
- Add authorized redirect URI:
http://localhost:3000/api/auth/callback/google - Copy Client ID and Client Secret
- Go to GitHub Developer Settings
- Click New OAuth App
- Fill in:
- Application name: Your app name
- Homepage URL:
http://localhost:3000 - Authorization callback URL:
http://localhost:3000/api/auth/callback/github
- Copy Client ID and generate Client Secret
- Go to Firebase Console
- Create a new project
- Go to Project Settings β General
- Scroll to Your apps section
- Click Web icon to add Firebase to your web app
- Copy the apiKey from the config object
- Development:
http://localhost:3000 - Production: Your production domain (e.g.,
https://yourdomain.com)
npm install -g prismanpx prisma generateMongoDB doesn't use migrations like SQL databases. Instead, use db push:
npx prisma db pushThis will:
- Create collections (tables) in MongoDB
- Create indexes as defined in schema
- Set up relationships between collections
If you have seed data, run:
npm run db:seedThis will populate your database with initial data (categories, sample posts, etc.).
The application uses the following MongoDB collections:
- User - User accounts (from NextAuth)
- Account - OAuth account links
- Session - User sessions
- Post - Blog posts
- Category - Post categories
- Comment - Post comments
- VerificationToken - Email verification tokens
npm run devStarts the development server at http://localhost:3000
npm run build
npm start# Lint code
npm run lint
# Seed database
npm run db:seed
# Open Prisma Studio (database GUI)
npx prisma studioPurpose: Main navigation bar with links and authentication buttons.
Features:
- Social media icons
- Logo/brand name
- Navigation links (Homepage, Contact, About)
- Theme toggle
- Authentication links (Login/Logout)
Usage:
import Navbar from "@/components/navbar/Navbar";
// Automatically included in root layoutReusability: Can be customized by modifying navbar.module.css for styling.
Purpose: Site footer with copyright and links.
Usage:
import Footer from "@/components/footer/Footer";
// Automatically included in root layoutPurpose: Displays a single blog post card with image, title, description, and link.
Props:
item(object) - Post data objectpriority(boolean, optional) - Prioritize image loading for LCP optimization
Usage:
import Card from "@/components/card/Card";
<Card item={post} priority={index === 0} />;Reusability: Perfect for any post listing page. Modify card.module.css for custom styling.
Purpose: Server component that displays a paginated list of blog post cards.
Props:
page(number) - Current page numbercat(string, optional) - Category filter
Usage:
import CardList from "@/components/cardList/CardList";
<CardList page={1} cat="style" />;Reusability: Can be used on any page that needs to display a list of posts. Modify the API endpoint URL if using a different data source.
Purpose: Hero section displaying a featured blog post on the homepage.
Usage:
import Featured from "@/components/featured/Featured";
<Featured />;Reusability: Customize the featured post by modifying the component to fetch from an API or use static content.
Purpose: Server component displaying all available blog categories.
Usage:
import CategoryList from "@/components/categoryList/CategoryList";
<CategoryList />;Reusability: Can be used anywhere categories need to be displayed. The component fetches from /api/categories.
Purpose: Client component for displaying and creating comments on blog posts.
Props:
postSlug(string) - Slug of the post to display comments for
Features:
- Fetches comments using SWR (stale-while-revalidate)
- Real-time comment updates
- Authentication check (shows login link if not authenticated)
- User avatars and names
Usage:
import Comments from "@/components/comments/Comments";
<Comments postSlug="my-post-slug" />;Reusability: Can be used on any page that needs a comment system. Just pass the appropriate identifier.
Purpose: Navigation component for paginated content.
Props:
page(number) - Current page numberhasPrev(boolean) - Whether previous page existshasNext(boolean) - Whether next page exists
Usage:
import Pagination from "@/components/pagination/Pagination";
<Pagination page={1} hasPrev={false} hasNext={true} />;Reusability: Works with any paginated content. Adjust the URL pattern in the component for different routes.
Purpose: Sidebar menu with popular posts and categories.
Usage:
import Menu from "@/components/Menu/Menu";
<Menu />;Reusability: Customize by modifying the component to fetch different data sources.
Purpose: Displays login/logout button based on authentication status.
Usage:
import AuthLinks from "@/components/authLinks/AuthLinks";
<AuthLinks />;Reusability: Can be placed anywhere authentication UI is needed.
Purpose: Button to toggle between light and dark themes.
Usage:
import ThemeToggle from "@/components/themeToggle/ThemeToggle";
<ThemeToggle />;Reusability: Can be placed anywhere theme switching is needed. Uses ThemeContext for state management.
Purpose: NextAuth.js authentication handler.
Endpoints:
/api/auth/signin- Sign in page/api/auth/signout- Sign out/api/auth/callback/google- Google OAuth callback/api/auth/callback/github- GitHub OAuth callback/api/auth/session- Get current session
Usage: Handled automatically by NextAuth.js.
Purpose: Fetch paginated blog posts with optional category filter.
Query Parameters:
page(number) - Page number (default: 1)cat(string, optional) - Category slug filter
Response:
{
"posts": [
{
"id": "post-id",
"title": "Post Title",
"slug": "post-slug",
"desc": "Post description",
"img": "image-url",
"views": 100,
"catSlug": "style",
"createdAt": "2024-01-01T00:00:00.000Z",
"userEmail": "user@example.com"
}
],
"count": 50
}Example:
const response = await fetch("/api/posts?page=1&cat=style");
const { posts, count } = await response.json();Purpose: Create a new blog post.
Authentication: Required (user must be logged in)
Request Body:
{
"title": "My New Post",
"desc": "<p>Post content in HTML</p>",
"img": "https://firebase-storage-url/image.jpg",
"slug": "my-new-post",
"catSlug": "style"
}Response:
{
"id": "new-post-id",
"title": "My New Post",
"slug": "my-new-post",
...
}Example:
const response = await fetch("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "My New Post",
desc: "<p>Content</p>",
img: "https://...",
slug: "my-new-post",
catSlug: "style",
}),
});Purpose: Fetch a single blog post by slug and increment view count.
URL Parameter:
slug(string) - Post slug
Response:
{
"id": "post-id",
"title": "Post Title",
"slug": "post-slug",
"desc": "<p>Full post content</p>",
"img": "image-url",
"views": 101,
"catSlug": "style",
"createdAt": "2024-01-01T00:00:00.000Z",
"user": {
"name": "Author Name",
"email": "author@example.com",
"image": "avatar-url"
}
}Example:
const response = await fetch("/api/posts/my-post-slug");
const post = await response.json();Purpose: Fetch all blog categories.
Response:
[
{
"id": "category-id",
"slug": "style",
"title": "Style",
"img": "category-image-url"
}
]Example:
const response = await fetch("/api/categories");
const categories = await response.json();Purpose: Fetch comments for a specific post.
Query Parameters:
postSlug(string) - Post slug to filter comments
Response:
[
{
"id": "comment-id",
"desc": "Comment text",
"createdAt": "2024-01-01T00:00:00.000Z",
"user": {
"name": "Commenter Name",
"email": "commenter@example.com",
"image": "avatar-url"
},
"postSlug": "post-slug"
}
]Example:
const response = await fetch("/api/comments?postSlug=my-post");
const comments = await response.json();Purpose: Create a new comment on a blog post.
Authentication: Required (user must be logged in)
Request Body:
{
"desc": "My comment text",
"postSlug": "post-slug"
}Response:
{
"id": "new-comment-id",
"desc": "My comment text",
"postSlug": "post-slug",
"userEmail": "user@example.com",
"createdAt": "2024-01-01T00:00:00.000Z"
}Example:
const response = await fetch("/api/comments", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
desc: "Great post!",
postSlug: "my-post",
}),
});File: src/app/page.jsx
Purpose: Main landing page with featured post, categories, and recent posts.
Components Used:
Featured- Hero sectionCategoryList- Category navigationCardList- Recent postsMenu- Sidebar
Query Parameters:
page(number) - Page number for pagination
File: src/app/blog/page.jsx
Purpose: Category-filtered blog listing page.
Query Parameters:
page(number) - Page numbercat(string) - Category slug filter
Example URLs:
/blog- All posts/blog?cat=style- Style category/blog?cat=style&page=2- Style category, page 2
File: src/app/posts/[slug]/page.jsx
Purpose: Display a single blog post with full content and comments.
Dynamic Route: [slug] - Post slug identifier
Features:
- Full post content (HTML rendered)
- Author information
- View counter (incremented on page load)
- Comments section
- SEO metadata generation
Example URLs:
/posts/my-awesome-post/posts/getting-started-with-nextjs
File: src/app/write/page.jsx
Purpose: Create a new blog post (requires authentication).
Features:
- Rich text editor (React Quill)
- Image upload to Firebase Storage
- Category selection
- Title input
- Auto-generated slug from title
Authentication: Redirects to homepage if not authenticated
File: src/app/login/page.jsx
Purpose: User authentication page.
Features:
- Google OAuth login
- GitHub OAuth login (if configured)
- Redirects to homepage after login
How it works:
- User clicks "Login" β Redirected to NextAuth sign-in page
- User selects OAuth provider (Google/GitHub)
- OAuth callback β NextAuth creates/updates user in database
- Session created β User is authenticated
- Session stored in database via PrismaAdapter
Code Location:
- Configuration:
src/utils/auth.js - Handler:
src/app/api/auth/[...nextauth]/route.js - Provider:
src/providers/AuthProvider.jsx
How it works:
- User navigates to
/write(must be authenticated) - User enters title, selects category, uploads image
- Image uploads to Firebase Storage β Returns URL
- User writes content in rich text editor (React Quill)
- On "Publish" β POST request to
/api/posts - API creates post in database with user email
- Redirects to new post page
Code Location:
- Page:
src/app/write/page.jsx - API:
src/app/api/posts/route.js(POST handler)
How it works:
- User selects image file in write page
useEffecthook triggers on file selection- Creates Firebase Storage reference
- Uploads file with
uploadBytesResumable(resumable upload) - Monitors upload progress
- On completion β Gets download URL from Firebase
- URL stored in component state β Used when creating post
Code Location:
src/app/write/page.jsx(lines 36-75)
How it works:
- User views post β Comments component fetches comments via SWR
- If authenticated β Shows comment input form
- User submits comment β POST to
/api/comments - Comment created with user email from session
- SWR
mutate()refreshes comments list - New comment appears immediately
Code Location:
- Component:
src/components/comments/Comments.jsx - API:
src/app/api/comments/route.js
How it works:
- Theme state managed in
ThemeContext - Theme preference stored in
localStorage ThemeProviderapplies theme class to DOM- CSS variables change based on theme class
- Toggle button updates context state
Code Location:
- Context:
src/context/ThemeContext.jsx - Provider:
src/providers/ThemeProvider.jsx - Toggle:
src/components/themeToggle/ThemeToggle.jsx
How it works:
- API endpoint calculates skip/take based on page number
- Fetches posts and total count in transaction
- Frontend calculates
hasPrevandhasNextflags - Pagination component renders prev/next buttons
- Clicking buttons updates URL query parameter
- Page re-renders with new data
Code Location:
- API:
src/app/api/posts/route.js(GET handler) - Component:
src/components/pagination/Pagination.jsx
How it works:
- User visits post page β GET
/api/posts/[slug] - API uses Prisma
updatewithincrementoperation - Atomically increments view count and fetches post
- View count displayed on post page
Code Location:
- API:
src/app/api/posts/[slug]/route.js
// src/app/blog/page.jsx
const BlogPage = async ({ searchParams }) => {
const page = parseInt(searchParams.page) || 1;
const { cat } = searchParams;
return (
<div>
<CardList page={page} cat={cat} />
</div>
);
};Key Points:
asynccomponent = Server ComponentsearchParamsfrom Next.js App Router- No
useEffectoruseStateneeded
// src/app/write/page.jsx
"use client";
const WritePage = () => {
const [title, setTitle] = useState("");
const [value, setValue] = useState("");
const handleSubmit = async () => {
await fetch("/api/posts", {
method: "POST",
body: JSON.stringify({ title, desc: value }),
});
};
return (
<div>
<input onChange={(e) => setTitle(e.target.value)} />
<ReactQuill value={value} onChange={setValue} />
<button onClick={handleSubmit}>Publish</button>
</div>
);
};Key Points:
"use client"directive required- Uses React hooks (
useState) - Handles user interactions
// src/app/api/posts/route.js
export const POST = async (req) => {
const session = await getAuthSession();
if (!session) {
return new NextResponse(JSON.stringify({ message: "Not Authenticated!" }), {
status: 401,
});
}
const body = await req.json();
const post = await prisma.post.create({
data: { ...body, userEmail: session.user.email },
});
return new NextResponse(JSON.stringify(post), { status: 201 });
};Key Points:
- Server-side authentication check
- Uses Prisma for database operations
- Returns JSON response
// src/components/comments/Comments.jsx
const Comments = ({ postSlug }) => {
const { data, mutate, isLoading } = useSWR(
`http://localhost:3000/api/comments?postSlug=${postSlug}`,
fetcher
);
const handleSubmit = async () => {
await fetch("/api/comments", {
method: "POST",
body: JSON.stringify({ desc, postSlug }),
});
mutate(); // Refresh data
};
return (
<div>
{isLoading ? "Loading..." : data?.map(comment => ...)}
</div>
);
};Key Points:
- SWR handles caching and revalidation
mutate()refreshes data after mutation- Automatic loading states
// Fetch post with related user data
const post = await prisma.post.findUnique({
where: { slug },
include: { user: true }, // Include related User data
});
// Fetch comments with user data
const comments = await prisma.comment.findMany({
where: { postSlug },
include: { user: true },
});Key Points:
includefetches related data- Type-safe queries
- Automatic relationship resolution
-
Copy the component:
cp src/components/card/Card.jsx your-project/components/ cp src/components/card/card.module.css your-project/components/
-
Import and use:
import Card from "@/components/card/Card"; <Card item={postData} priority={true} />;
-
Customize styling:
- Modify
card.module.cssfor your design - Adjust props if needed
- Modify
-
Copy files:
cp src/components/comments/Comments.jsx your-project/ cp src/components/comments/comments.module.css your-project/
-
Install dependencies:
npm install swr next-auth
-
Set up API endpoint:
- Create
/api/commentsroute - Match the expected response format
- Create
-
Use in your page:
import Comments from "@/components/comments/Comments"; <Comments postSlug="your-post-id" />;
-
Copy theme context and provider:
cp src/context/ThemeContext.jsx your-project/ cp src/providers/ThemeProvider.jsx your-project/ cp src/components/themeToggle/ThemeToggle.jsx your-project/
-
Wrap your app:
import { ThemeContextProvider } from "@/context/ThemeContext"; import ThemeProvider from "@/providers/ThemeProvider"; <ThemeContextProvider> <ThemeProvider>{children}</ThemeProvider> </ThemeContextProvider>;
-
Add theme toggle button:
import ThemeToggle from "@/components/themeToggle/ThemeToggle"; <ThemeToggle />;
-
Set up CSS variables:
- Define light/dark theme variables in your CSS
- Use
[data-theme="dark"]selector
-
Copy component:
cp src/components/pagination/Pagination.jsx your-project/ cp src/components/pagination/pagination.module.css your-project/
-
Use with your data:
import Pagination from "@/components/pagination/Pagination"; const hasPrev = page > 1; const hasNext = page * itemsPerPage < totalItems; <Pagination page={page} hasPrev={hasPrev} hasNext={hasNext} />;
-
Customize URL pattern:
- Modify the
hrefin Pagination component to match your routes
- Modify the
Technologies:
- Next.js
- React
- MongoDB
- Prisma
- NextAuth.js
- Firebase
- OAuth
- Server Components
- App Router
- TypeScript
- SWR
- React Quill
- CSS Modules
Concepts:
- Full-stack development
- Server-side rendering
- Authentication
- Authorization
- Database ORM
- RESTful API
- Pagination
- Rich text editing
- Image upload
- Dark mode
- SEO optimization
- Responsive design
Use Cases:
- Blogging platform
- Content management
- User authentication
- Comment system
- Category filtering
- Post management
This Blog App is a comprehensive example of a modern full-stack Next.js application. It demonstrates:
- β Best Practices: Server Components, API Routes, Type-safe database queries
- β Authentication: OAuth integration with NextAuth.js
- β Database: MongoDB with Prisma ORM
- β File Storage: Firebase Storage integration
- β User Experience: Dark mode, responsive design, SEO optimization
- β Code Organization: Modular components, reusable utilities
Learning Outcomes:
By studying this project, you'll learn:
- How to structure a Next.js 14 App Router application
- Server vs Client Components and when to use each
- Authentication implementation with NextAuth.js
- Database operations with Prisma ORM
- API route creation and best practices
- Image upload and storage with Firebase
- Rich text editing with React Quill
- State management with React Context
- Data fetching patterns (SWR, Server Components)
- SEO optimization with dynamic metadata
Next Steps:
- Customize the design and styling
- Add more features (likes, bookmarks, search)
- Implement admin dashboard
- Add email notifications
- Set up CI/CD pipeline
- Deploy to production
Feel free to use this project repository and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://arnob-mahmud.vercel.app/.
Enjoy building and learning! π
Thank you! π