Skip to content
Devon Wijesinghe edited this page Oct 25, 2025 · 2 revisions

Next.js Full-Stack Starter Template

Table of Contents

  1. Overview
  2. Features
  3. Technology Stack
  4. Prerequisites
  5. Installation
  6. Configuration
  7. Project Structure
  8. Authentication
  9. Database Setup
  10. Styling
  11. Components
  12. Pages
  13. Utilities
  14. Middleware
  15. Development
  16. Building for Production
  17. Deployment
  18. API Routes
  19. Best Practices
  20. Troubleshooting
  21. Contributing

Overview

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

Features

🔐 Authentication

  • NextAuth.js integration for secure authentication
  • Multiple OAuth providers (Google, GitHub)
  • Session management
  • Protected routes and API endpoints
  • Customizable authentication flows

💻 Type Safety

  • TypeScript throughout the entire codebase
  • Type-safe API routes
  • IntelliSense support
  • Compile-time error checking

🗄️ Database

  • MongoDB integration with Mongoose ODM
  • Schema validation
  • Database connection pooling
  • Easy-to-extend data models

🎨 UI Components

  • Ant Design component library
  • Pre-built, customizable UI components
  • Responsive design out of the box
  • Professional-looking interface

🎯 Styling Options

  • CSS Modules
  • SASS/SCSS support
  • CSS-in-JS compatibility
  • Flexible styling architecture

Performance

  • Server-Side Rendering (SSR)
  • Static Site Generation (SSG)
  • Incremental Static Regeneration (ISR)
  • Automatic code splitting
  • Image optimization

Technology Stack

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

Prerequisites

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

Recommended Tools

  • VS Code with TypeScript and ESLint extensions
  • MongoDB Compass for database visualization
  • Postman or Thunder Client for API testing

Installation

1. Clone the Repository

git clone https://github.com/wdevon99/Next-js-starter.git
cd Next-js-starter

2. Install Dependencies

Using npm:

npm install

Using yarn:

yarn install

Using pnpm:

pnpm install

3. Environment Setup

Create a .env.local file in the root directory:

cp .env.example .env.local

Configuration

Environment Variables

Edit 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

Obtaining OAuth Credentials

Google OAuth

  1. Go to Google Cloud Console
  2. Create a new project or select existing one
  3. Enable Google+ API
  4. Go to "Credentials" → "Create Credentials" → "OAuth 2.0 Client ID"
  5. Add authorized redirect URI: http://localhost:3000/api/auth/callback/google
  6. Copy Client ID and Client Secret

GitHub OAuth

  1. Go to GitHub Developer Settings
  2. Click "New OAuth App"
  3. Fill in application details:
    • Application name: Your App Name
    • Homepage URL: http://localhost:3000
    • Authorization callback URL: http://localhost:3000/api/auth/callback/github
  4. Copy Client ID and Client Secret

MongoDB Setup

Option 1: Local MongoDB

# 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

Option 2: MongoDB Atlas (Cloud)

  1. Create account at MongoDB Atlas
  2. Create a new cluster (free tier available)
  3. Create database user
  4. Whitelist your IP address (0.0.0.0/0 for development)
  5. Get connection string and add to .env.local

Project Structure

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

Authentication

NextAuth.js Configuration

The template uses NextAuth.js for authentication. Configuration is located in src/app/api/auth/[...nextauth]/route.ts.

Protected Routes

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>;
}

Server-Side Authentication

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>;
}

Sign In/Sign Out

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>

Database Setup

MongoDB Schema Example

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;

Database Connection

// 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;

Using Database in API Routes

// 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 }
    );
  }
}

Styling

Using SASS/SCSS

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>;
}

Using Ant Design

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>
  );
}

Components

CreateTodoModal

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

TodosCard

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

TodosProgressCard

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

Customizing Ant Design Theme

// src/app/layout.tsx
import { ConfigProvider } from 'antd';

export default function RootLayout({ children }) {
  return (
    <ConfigProvider
      theme={{
        token: {
          colorPrimary: '#1890ff',
          borderRadius: 4,
        },
      }}
    >
      {children}
    </ConfigProvider>
  );
}

Pages

Home

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>
  );
}

Dashboard

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>
  );
}

Utilities

connectToDB

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

Middleware

middleware

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:

  1. 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();
}
  1. 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();
}
  1. 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

Development

Running the Development Server

npm run dev
# or
yarn dev
# or
pnpm dev

Open http://localhost:3000 in your browser.

Available Scripts

{
  "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
}

Hot Reload

Next.js supports Fast Refresh, which automatically updates your application when you save changes without losing component state.


Building for Production

Build Process

npm run build

This will:

  1. Type-check your TypeScript code
  2. Lint your code
  3. Optimize and bundle your application
  4. Generate static pages where possible

Start Production Server

npm start

Production Optimizations

  • Automatic code splitting
  • Image optimization with Next.js Image component
  • Font optimization
  • Script optimization
  • CSS minimization
  • Tree shaking

Deployment

Vercel (Recommended)

  1. Push your code to GitHub
  2. Go to Vercel
  3. Import your repository
  4. Add environment variables
  5. Deploy
# Or use Vercel CLI
npm i -g vercel
vercel

Other Platforms

Docker Deployment

# 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"]

AWS, DigitalOcean, or any VPS

# 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 startup

API Routes

HTTP Method Constants

The template uses standard HTTP method handlers for API routes. Next.js supports the following methods:

GET

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 }
    );
  }
}

POST

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 }
    );
  }
}

PUT

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 }
    );
  }
}

DELETE

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 }
    );
  }
}

Creating API Routes

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 });
}

Protected API Routes

// 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' });
}

Best Practices

Code Organization

  • Keep components small and focused
  • Use TypeScript interfaces for props
  • Separate business logic from UI components
  • Use custom hooks for reusable logic

Performance

  • Use Next.js Image component for images
  • Implement lazy loading for heavy components
  • Use React.memo() for expensive components
  • Optimize database queries

Security

  • 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

TypeScript

  • Define interfaces for all data structures
  • Avoid using any type
  • Use strict mode in tsconfig.json
  • Create type definitions for external libraries

Troubleshooting

Common Issues

Port Already in Use

# Kill process on port 3000
npx kill-port 3000
# Or run on different port
PORT=3001 npm run dev

MongoDB Connection Error

  • Verify MONGODB_URI in .env.local
  • Check MongoDB service is running
  • Verify network connectivity
  • Check IP whitelist in MongoDB Atlas

Authentication Not Working

  • Verify OAuth credentials
  • Check redirect URIs match exactly
  • Ensure NEXTAUTH_URL is correct
  • Clear browser cookies and try again

Build Errors

# Clear Next.js cache
rm -rf .next
# Reinstall dependencies
rm -rf node_modules package-lock.json
npm install

Getting Help


Contributing

Contributions are welcome! Here's how you can help:

Reporting Bugs

  1. Check if the issue already exists
  2. Create a detailed bug report with:
    • Steps to reproduce
    • Expected behavior
    • Actual behavior
    • Screenshots if applicable

Suggesting Features

  1. Open a GitHub issue
  2. Describe the feature and use case
  3. Explain why it would be beneficial

Pull Requests

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Commit your changes: git commit -m 'Add amazing feature'
  4. Push to the branch: git push origin feature/amazing-feature
  5. Open a Pull Request

Code Style

  • Follow existing code formatting
  • Run ESLint before committing
  • Add TypeScript types for new code
  • Write meaningful commit messages

License

This project is open source and available under the MIT License.


Acknowledgments


Support

⭐ 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

Clone this wiki locally