-
Notifications
You must be signed in to change notification settings - Fork 129
Home
- Overview
- Features
- Technology Stack
- Prerequisites
- Installation
- Configuration
- Project Structure
- Authentication
- Database Setup
- Styling
- Components
- Pages
- Utilities
- Middleware
- Development
- Building for Production
- Deployment
- API Routes
- Best Practices
- Troubleshooting
- Contributing
The Next.js Full-Stack Starter Template is a production-ready boilerplate for building modern web applications. It combines the power of Next.js 14+ with essential tools and libraries to accelerate development while maintaining best practices for scalability, security, and performance.
Repository: https://github.com/wdevon99/Next-js-starter
This template is perfect for:
- SaaS applications
- E-commerce platforms
- Content management systems
- Social networks
- Dashboard applications
- Any full-stack web application requiring authentication and database integration
- NextAuth.js integration for secure authentication
- Multiple OAuth providers (Google, GitHub)
- Session management
- Protected routes and API endpoints
- Customizable authentication flows
- TypeScript throughout the entire codebase
- Type-safe API routes
- IntelliSense support
- Compile-time error checking
- MongoDB integration with Mongoose ODM
- Schema validation
- Database connection pooling
- Easy-to-extend data models
- Ant Design component library
- Pre-built, customizable UI components
- Responsive design out of the box
- Professional-looking interface
- CSS Modules
- SASS/SCSS support
- CSS-in-JS compatibility
- Flexible styling architecture
- Server-Side Rendering (SSR)
- Static Site Generation (SSG)
- Incremental Static Regeneration (ISR)
- Automatic code splitting
- Image optimization
| Technology | Version | Purpose |
|---|---|---|
| Next.js | 14+ | React framework with SSR/SSG |
| React | 18+ | UI library |
| TypeScript | 5+ | Type-safe JavaScript |
| NextAuth.js | 4+ | Authentication solution |
| MongoDB | 6+ | NoSQL database |
| Ant Design | 5+ | UI component library |
| SASS | Latest | CSS preprocessor |
Before you begin, ensure you have the following installed:
- Node.js: Version 18.x or higher
- npm or yarn or pnpm: Package manager
- MongoDB: Local instance or MongoDB Atlas account
- Git: Version control
- VS Code with TypeScript and ESLint extensions
- MongoDB Compass for database visualization
- Postman or Thunder Client for API testing
git clone https://github.com/wdevon99/Next-js-starter.git
cd Next-js-starterUsing npm:
npm installUsing yarn:
yarn installUsing pnpm:
pnpm installCreate a .env.local file in the root directory:
cp .env.example .env.localEdit your .env.local file with the following variables:
# Application URL (for local development)
NEXTAUTH_URL=http://localhost:3000
# NextAuth Secret (generate using: openssl rand -base64 32)
NEXTAUTH_SECRET=your-random-secret-key-here
# Google OAuth Credentials
GOOGLE_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# GitHub OAuth Credentials
GITHUB_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
# MongoDB Connection String
MONGODB_URI=mongodb://localhost:27017/your-database-name
# Or for MongoDB Atlas:
# MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/database-name- Go to Google Cloud Console
- Create a new project or select existing one
- Enable Google+ API
- Go to "Credentials" → "Create Credentials" → "OAuth 2.0 Client ID"
- 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 details:
- Application name: Your App Name
-
Homepage URL:
http://localhost:3000 -
Authorization callback URL:
http://localhost:3000/api/auth/callback/github
- Copy Client ID and Client Secret
# Install MongoDB (macOS)
brew tap mongodb/brew
brew install mongodb-community
# Start MongoDB
brew services start mongodb-community
# Connection string
MONGODB_URI=mongodb://localhost:27017/nextjs-starter- Create account at MongoDB Atlas
- Create a new cluster (free tier available)
- Create database user
- Whitelist your IP address (0.0.0.0/0 for development)
- Get connection string and add to
.env.local
Next-js-starter/
├── .vscode/ # VS Code configuration
├── public/ # Static files
│ ├── images/ # Image assets
│ └── favicon.ico # Favicon
├── src/
│ ├── app/ # Next.js 14 App Router
│ │ ├── api/ # API routes
│ │ │ └── auth/ # NextAuth API routes
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Home page
│ │ └── globals.css # Global styles
│ ├── components/ # React components
│ │ ├── auth/ # Authentication components
│ │ ├── layout/ # Layout components
│ │ └── ui/ # Reusable UI components
│ ├── lib/ # Utility functions
│ │ ├── db.ts # Database connection
│ │ └── utils.ts # Helper functions
│ ├── models/ # MongoDB schemas
│ │ └── User.ts # User model
│ ├── types/ # TypeScript type definitions
│ │ └── index.ts # Global types
│ └── styles/ # SASS/CSS files
│ └── variables.scss # Style variables
├── .env.example # Environment variables template
├── .eslintrc.json # ESLint configuration
├── .gitignore # Git ignore rules
├── next.config.mjs # Next.js configuration
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
└── README.md # Project README
The template uses NextAuth.js for authentication. Configuration is located in src/app/api/auth/[...nextauth]/route.ts.
To protect a page, use the useSession hook:
'use client';
import { useSession } from 'next-auth/react';
import { redirect } from 'next/navigation';
export default function ProtectedPage() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <div>Loading...</div>;
}
if (!session) {
redirect('/api/auth/signin');
}
return <div>Protected Content</div>;
}For server components or API routes:
import { getServerSession } from 'next-auth';
import { redirect } from 'next/navigation';
export default async function ProtectedServerPage() {
const session = await getServerSession();
if (!session) {
redirect('/api/auth/signin');
}
return <div>Protected Server Content</div>;
}import { signIn, signOut } from 'next-auth/react';
// Sign in with Google
<button onClick={() => signIn('google')}>
Sign in with Google
</button>
// Sign in with GitHub
<button onClick={() => signIn('github')}>
Sign in with GitHub
</button>
// Sign out
<button onClick={() => signOut()}>
Sign Out
</button>Create a model in src/models/:
// src/models/User.ts
import mongoose, { Schema, model, models } from 'mongoose';
export interface IUser {
name: string;
email: string;
image?: string;
createdAt: Date;
updatedAt: Date;
}
const UserSchema = new Schema<IUser>({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
image: { type: String },
}, {
timestamps: true
});
const User = models.User || model<IUser>('User', UserSchema);
export default User;// src/lib/db.ts
import mongoose from 'mongoose';
const MONGODB_URI = process.env.MONGODB_URI!;
if (!MONGODB_URI) {
throw new Error('Please define MONGODB_URI in .env.local');
}
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
async function connectDB() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
cached.promise = mongoose.connect(MONGODB_URI).then((mongoose) => {
return mongoose;
});
}
cached.conn = await cached.promise;
return cached.conn;
}
export default connectDB;// src/app/api/users/route.ts
import { NextResponse } from 'next/server';
import connectDB from '@/lib/db';
import User from '@/models/User';
export async function GET() {
try {
await connectDB();
const users = await User.find({});
return NextResponse.json({ users });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch users' },
{ status: 500 }
);
}
}Create .scss files in src/styles/:
// src/styles/components/button.scss
$primary-color: #1890ff;
.custom-button {
background-color: $primary-color;
padding: 10px 20px;
border-radius: 4px;
&:hover {
opacity: 0.8;
}
}Import in your component:
import '@/styles/components/button.scss';
export default function Button() {
return <button className="custom-button">Click Me</button>;
}import { Button, Card, Space } from 'antd';
export default function AntDesignExample() {
return (
<Card title="Example Card">
<Space>
<Button type="primary">Primary Button</Button>
<Button>Default Button</Button>
</Space>
</Card>
);
}A reusable modal component for creating new todo items. This component uses Ant Design's Modal and Form components.
Location: src/components/todos/CreateTodoModal.tsx
Usage:
import CreateTodoModal from '@/components/todos/CreateTodoModal';
export default function TodosPage() {
const [isModalOpen, setIsModalOpen] = useState(false);
const handleCreateTodo = async (values: TodoFormValues) => {
// Handle todo creation
await createTodo(values);
setIsModalOpen(false);
};
return (
<>
<Button onClick={() => setIsModalOpen(true)}>Create Todo</Button>
<CreateTodoModal
open={isModalOpen}
onClose={() => setIsModalOpen(false)}
onSubmit={handleCreateTodo}
/>
</>
);
}Props:
-
open(boolean): Controls modal visibility -
onClose(function): Callback when modal is closed -
onSubmit(function): Callback when form is submitted
A card component that displays a list of todo items with interactive features like marking as complete, editing, and deleting.
Location: src/components/todos/TodosCard.tsx
Usage:
import TodosCard from '@/components/todos/TodosCard';
export default function Dashboard() {
const [todos, setTodos] = useState([]);
return (
<TodosCard
todos={todos}
onToggleComplete={handleToggleComplete}
onDelete={handleDelete}
onEdit={handleEdit}
/>
);
}Props:
-
todos(array): Array of todo objects -
onToggleComplete(function): Callback to toggle todo completion status -
onDelete(function): Callback to delete a todo -
onEdit(function): Callback to edit a todo
A visual card component that displays todo completion statistics using charts and progress indicators.
Location: src/components/todos/TodosProgressCard.tsx
Usage:
import TodosProgressCard from '@/components/todos/TodosProgressCard';
export default function Dashboard() {
const todoStats = {
total: 10,
completed: 6,
pending: 4,
completionRate: 60
};
return <TodosProgressCard stats={todoStats} />;
}Props:
-
stats(object): Todo statistics including total, completed, pending counts -
showChart(boolean, optional): Whether to display the progress chart
Features:
- Visual progress bar
- Completion percentage
- Color-coded status indicators
- Responsive design
// src/app/layout.tsx
import { ConfigProvider } from 'antd';
export default function RootLayout({ children }) {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: '#1890ff',
borderRadius: 4,
},
}}
>
{children}
</ConfigProvider>
);
}The main landing page of the application. This is the entry point for unauthenticated users.
Location: src/app/page.tsx
Features:
- Hero section with call-to-action
- Feature highlights
- Authentication options
- Responsive design
Example:
// src/app/page.tsx
export default function Home() {
return (
<main>
<section className="hero">
<h1>Welcome to Next.js Starter</h1>
<p>Build full-stack applications faster</p>
<div className="cta-buttons">
<Button type="primary" href="/dashboard">
Get Started
</Button>
<Button href="/api/auth/signin">
Sign In
</Button>
</div>
</section>
<section className="features">
{/* Feature cards */}
</section>
</main>
);
}The main dashboard page for authenticated users. Displays user-specific data and provides access to core application features.
Location: src/app/dashboard/page.tsx
Features:
- Protected route (requires authentication)
- User profile display
- Todo management interface
- Statistics and progress tracking
Example:
// src/app/dashboard/page.tsx
import { getServerSession } from 'next-auth';
import { redirect } from 'next/navigation';
import TodosCard from '@/components/todos/TodosCard';
import TodosProgressCard from '@/components/todos/TodosProgressCard';
export default async function Dashboard() {
const session = await getServerSession();
if (!session) {
redirect('/api/auth/signin');
}
return (
<div className="dashboard">
<h1>Welcome, {session.user?.name}</h1>
<div className="dashboard-grid">
<TodosProgressCard />
<TodosCard />
</div>
</div>
);
}A utility function that establishes and manages the MongoDB database connection with connection pooling and caching.
Location: src/lib/db.ts
Usage:
import connectToDB from '@/lib/db';
// In API routes or server components
export async function GET() {
try {
await connectToDB();
// Perform database operations
const data = await Model.find({});
return NextResponse.json({ data });
} catch (error) {
return NextResponse.json(
{ error: 'Database error' },
{ status: 500 }
);
}
}Implementation:
// src/lib/db.ts
import mongoose from 'mongoose';
const MONGODB_URI = process.env.MONGODB_URI!;
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
async function connectToDB() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
bufferCommands: false,
};
cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
console.log('✅ Connected to MongoDB');
return mongoose;
});
}
try {
cached.conn = await cached.promise;
} catch (e) {
cached.promise = null;
throw e;
}
return cached.conn;
}
export default connectToDB;Features:
- Connection caching to prevent multiple connections
- Automatic reconnection handling
- Error handling and logging
- Type-safe with TypeScript
Next.js middleware function that runs before requests are completed. Used for authentication checks, redirects, and request processing.
Location: src/middleware.ts
Usage:
// src/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getToken } from 'next-auth/jwt';
export async function middleware(request: NextRequest) {
const token = await getToken({
req: request,
secret: process.env.NEXTAUTH_SECRET,
});
const { pathname } = request.nextUrl;
// Allow requests to these paths without authentication
if (
pathname.startsWith('/api/auth') ||
pathname === '/' ||
pathname.startsWith('/_next') ||
pathname.startsWith('/static')
) {
return NextResponse.next();
}
// Redirect to sign-in if not authenticated
if (!token && pathname.startsWith('/dashboard')) {
const signInUrl = new URL('/api/auth/signin', request.url);
signInUrl.searchParams.set('callbackUrl', pathname);
return NextResponse.redirect(signInUrl);
}
return NextResponse.next();
}
export const config = {
matcher: [
/*
* Match all request paths except for:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - public folder
*/
'/((?!_next/static|_next/image|favicon.ico|public).*)',
],
};Common Use Cases:
- Authentication Protection:
export async function middleware(request: NextRequest) {
const token = await getToken({ req: request });
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}- Role-Based Access Control:
export async function middleware(request: NextRequest) {
const token = await getToken({ req: request });
if (request.nextUrl.pathname.startsWith('/admin')) {
if (token?.role !== 'admin') {
return NextResponse.redirect(new URL('/unauthorized', request.url));
}
}
return NextResponse.next();
}- Custom Headers:
export async function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set('X-Custom-Header', 'value');
return response;
}Configuration:
-
matcher: Specifies which routes the middleware applies to - Supports pattern matching with wildcards
- Can be configured per route or globally
npm run dev
# or
yarn dev
# or
pnpm devOpen http://localhost:3000 in your browser.
{
"dev": "next dev", // Start development server
"build": "next build", // Build for production
"start": "next start", // Start production server
"lint": "next lint", // Run ESLint
"type-check": "tsc --noEmit" // Check TypeScript errors
}Next.js supports Fast Refresh, which automatically updates your application when you save changes without losing component state.
npm run buildThis will:
- Type-check your TypeScript code
- Lint your code
- Optimize and bundle your application
- Generate static pages where possible
npm start- Automatic code splitting
- Image optimization with Next.js Image component
- Font optimization
- Script optimization
- CSS minimization
- Tree shaking
- Push your code to GitHub
- Go to Vercel
- Import your repository
- Add environment variables
- Deploy
# Or use Vercel CLI
npm i -g vercel
vercel# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]# Build the application
npm run build
# Install PM2 for process management
npm install -g pm2
# Start with PM2
pm2 start npm --name "nextjs-app" -- start
# Save PM2 configuration
pm2 save
pm2 startupThe template uses standard HTTP method handlers for API routes. Next.js supports the following methods:
The GET method handler is used to retrieve data from the server.
// src/app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
try {
// Fetch data from database or external API
const users = await fetchUsers();
return NextResponse.json({ users });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch users' },
{ status: 500 }
);
}
}The POST method handler is used to create new resources or submit data.
// src/app/api/users/route.ts
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const newUser = await createUser(body);
return NextResponse.json({ user: newUser }, { status: 201 });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to create user' },
{ status: 500 }
);
}
}The PUT method handler is used to update existing resources completely.
// src/app/api/users/[id]/route.ts
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const body = await request.json();
const updatedUser = await updateUser(params.id, body);
return NextResponse.json({ user: updatedUser });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to update user' },
{ status: 500 }
);
}
}The DELETE method handler is used to remove resources.
// src/app/api/users/[id]/route.ts
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
await deleteUser(params.id);
return NextResponse.json(
{ message: 'User deleted successfully' },
{ status: 200 }
);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to delete user' },
{ status: 500 }
);
}
}Create files in src/app/api/:
// src/app/api/hello/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
return NextResponse.json({ message: 'Hello World' });
}
export async function POST(request: NextRequest) {
const body = await request.json();
return NextResponse.json({ received: body });
}// src/app/api/protected/route.ts
import { getServerSession } from 'next-auth';
import { NextResponse } from 'next/server';
export async function GET() {
const session = await getServerSession();
if (!session) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
return NextResponse.json({ data: 'Protected data' });
}- Keep components small and focused
- Use TypeScript interfaces for props
- Separate business logic from UI components
- Use custom hooks for reusable logic
- Use Next.js Image component for images
- Implement lazy loading for heavy components
- Use React.memo() for expensive components
- Optimize database queries
- Never expose API keys in client-side code
- Validate all user inputs
- Use HTTPS in production
- Implement rate limiting on API routes
- Keep dependencies updated
- Define interfaces for all data structures
- Avoid using
anytype - Use strict mode in tsconfig.json
- Create type definitions for external libraries
# Kill process on port 3000
npx kill-port 3000
# Or run on different port
PORT=3001 npm run dev- Verify MONGODB_URI in
.env.local - Check MongoDB service is running
- Verify network connectivity
- Check IP whitelist in MongoDB Atlas
- Verify OAuth credentials
- Check redirect URIs match exactly
- Ensure NEXTAUTH_URL is correct
- Clear browser cookies and try again
# Clear Next.js cache
rm -rf .next
# Reinstall dependencies
rm -rf node_modules package-lock.json
npm install- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Next.js Docs: https://nextjs.org/docs
- NextAuth.js Docs: https://next-auth.js.org/
Contributions are welcome! Here's how you can help:
- Check if the issue already exists
- Create a detailed bug report with:
- Steps to reproduce
- Expected behavior
- Actual behavior
- Screenshots if applicable
- Open a GitHub issue
- Describe the feature and use case
- Explain why it would be beneficial
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
- Follow existing code formatting
- Run ESLint before committing
- Add TypeScript types for new code
- Write meaningful commit messages
This project is open source and available under the MIT License.
- Next.js - The React Framework
- NextAuth.js - Authentication for Next.js
- MongoDB - Database Platform
- Ant Design - UI Component Library
- Vercel - Deployment Platform
⭐ If you find this template helpful, please give it a star on GitHub!
For questions or support, please open an issue on GitHub.
Last Updated: January 2025
Template Version: 1.0.0
Maintained by: @wdevon99