Documentation

Welcome to the comprehensive Next.js documentation. Next.js is a React framework that enables functionality such as server-side rendering and generating static websites for React based web applications.

Module 1: Next.js Fundamentals
Introduction to Next.js

Next.js is a full-stack React framework that provides everything you need to build production-ready web applications. It features automatic optimization, TypeScript support, and a powerful routing system.

Key features of Next.js:

  • Server-Side Rendering (SSR) - Render pages on the server for better performance and SEO
  • Static Site Generation (SSG) - Pre-build pages at build time
  • API Routes - Build API endpoints as part of your Next.js app
  • File-based Routing - Automatic routing based on file structure
  • TypeScript Support - Built-in TypeScript support
  • Image Optimization - Automatic image optimization and lazy loading
  • Code Splitting - Automatic code splitting for faster loading
Prerequisites

Make sure you have Node.js (version 18.17 or later) and npm installed:


# Check Node.js version
node --version

# Check npm version
npm --version
Create Next.js Project

# Create a new Next.js app
npx create-next-app@latest my-next-app

# Or with TypeScript
npx create-next-app@latest my-next-app --typescript

# Navigate to the project
cd my-next-app

# Start the development server
npm run dev
Manual Setup

# Initialize npm project
npm init -y

# Install Next.js
npm install next react react-dom

# Install additional packages
npm install @types/node @types/react @types/react-dom typescript eslint eslint-config-next

# Create necessary directories
mkdir pages components styles public
Package.json Scripts

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "export": "next export"
  }
}
Module 2: Pages and Routing
App Router Structure (Next.js 13+)

my-next-app/
├── app/
│   ├── layout.tsx
│   ├── page.tsx
│   ├── globals.css
│   ├── about/
│   │   └── page.tsx
│   └── api/
│       └── users/
│           └── route.ts
├── components/
│   ├── Header.tsx
│   ├── Footer.tsx
│   └── Button.tsx
├── lib/
│   └── utils.ts
├── public/
│   ├── favicon.ico
│   └── images/
├── styles/
│   └── Home.module.css
├── next.config.js
├── package.json
├── tsconfig.json
└── tailwind.config.js
Pages Router Structure (Legacy)

my-next-app/
├── pages/
│   ├── _app.tsx
│   ├── _document.tsx
│   ├── index.tsx
│   ├── about.tsx
│   └── api/
│       └── users.ts
├── components/
├── public/
├── styles/
├── next.config.js
└── package.json
Root Layout (App Router)

// app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'My Next.js App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    
      
        
{children}

© 2023 My App

) }
Page Component (App Router)

// app/page.tsx
import Link from 'next/link'

export default function Home() {
  return (
    

Welcome to Next.js

This is the home page.

Go to About
) }
Nested Routes

// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'

interface PageProps {
  params: {
    slug: string
  }
}

export default function BlogPost({ params }: PageProps) {
  const { slug } = params

  // Fetch blog post data
  const post = getBlogPost(slug)

  if (!post) {
    notFound()
  }

  return (
    

{post.title}

{post.content}

) } export async function generateStaticParams() { const posts = await getAllBlogPosts() return posts.map((post) => ({ slug: post.slug, })) }
Custom App (Pages Router)

// pages/_app.tsx
import type { AppProps } from 'next/app'
import '../styles/globals.css'

export default function App({ Component, pageProps }: AppProps) {
  return 
}
Custom Document (Pages Router)

// pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    
      
      
        
) }
Page Component (Pages Router)

// pages/index.tsx
import type { NextPage } from 'next'
import Link from 'next/link'

const Home: NextPage = () => {
  return (
    

Welcome to Next.js

About
) } export default Home
Module 3: Data Fetching
Server Components (App Router)

// app/users/page.tsx
async function getUsers() {
  const res = await fetch('https://api.example.com/users')
  if (!res.ok) {
    throw new Error('Failed to fetch users')
  }
  return res.json()
}

export default async function UsersPage() {
  const users = await getUsers()

  return (
    

Users

    {users.map((user) => (
  • {user.name}
  • ))}
) }
Client Components

// components/UserList.tsx
'use client'

import { useState, useEffect } from 'react'

export default function UserList() {
  const [users, setUsers] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    async function fetchUsers() {
      try {
        const res = await fetch('/api/users')
        const data = await res.json()
        setUsers(data)
      } catch (error) {
        console.error('Failed to fetch users:', error)
      } finally {
        setLoading(false)
      }
    }

    fetchUsers()
  }, [])

  if (loading) return 
Loading...
return (
    {users.map((user) => (
  • {user.name}
  • ))}
) }
SWR for Data Fetching

# Install SWR
npm install swr

// components/UserList.tsx
'use client'

import useSWR from 'swr'

const fetcher = (url: string) => fetch(url).then((res) => res.json())

export default function UserList() {
  const { data, error, isLoading } = useSWR('/api/users', fetcher)

  if (error) return 
Failed to load
if (isLoading) return
Loading...
return (
    {data.map((user) => (
  • {user.name}
  • ))}
) }
Data Fetching in Pages Router

// pages/users.tsx
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'

type User = {
  id: number
  name: string
}

type UsersPageProps = {
  users: User[]
}

export default function UsersPage({ users }: UsersPageProps) {
  return (
    

Users

    {users.map((user) => (
  • {user.name}
  • ))}
) } export const getServerSideProps: GetServerSideProps = async () => { const res = await fetch('https://api.example.com/users') const users: User[] = await res.json() return { props: { users, }, } }
Static Generation with getStaticProps

// pages/posts/[id].tsx
import type { GetStaticProps, GetStaticPaths, InferGetStaticPropsType } from 'next'

type Post = {
  id: number
  title: string
  content: string
}

type PostPageProps = {
  post: Post
}

export default function PostPage({ post }: PostPageProps) {
  return (
    

{post.title}

{post.content}

) } export const getStaticPaths: GetStaticPaths = async () => { const res = await fetch('https://api.example.com/posts') const posts: Post[] = await res.json() const paths = posts.map((post) => ({ params: { id: post.id.toString() }, })) return { paths, fallback: 'blocking', } } export const getStaticProps: GetStaticProps = async ({ params }) => { const res = await fetch(`https://api.example.com/posts/${params.id}`) const post: Post = await res.json() return { props: { post, }, revalidate: 60, // Re-generate the page every 60 seconds } }
Module 4: API Routes
App Router API Route

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const users = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Doe' },
  ]

  return NextResponse.json(users)
}

export async function POST(request: NextRequest) {
  const body = await request.json()
  const newUser = { id: Date.now(), ...body }

  // Save to database
  // await saveUser(newUser)

  return NextResponse.json(newUser, { status: 201 })
}
Pages Router API Route

// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next'

type User = {
  id: number
  name: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === 'GET') {
    const users: User[] = [
      { id: 1, name: 'John Doe' },
      { id: 2, name: 'Jane Doe' },
    ]
    res.status(200).json(users)
  } else if (req.method === 'POST') {
    const newUser: User = { id: Date.now(), name: req.body.name }
    res.status(201).json(newUser)
  } else {
    res.setHeader('Allow', ['GET', 'POST'])
    res.status(405).end(`Method ${req.method} Not Allowed`)
  }
}
Dynamic API Routes

// app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const id = params.id
  
  // Fetch user from database
  const user = await getUserById(id)
  
  if (!user) {
    return NextResponse.json({ error: 'User not found' }, { status: 404 })
  }
  
  return NextResponse.json(user)
}

export async function PUT(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const id = params.id
  const body = await request.json()
  
  // Update user in database
  const updatedUser = await updateUser(id, body)
  
  if (!updatedUser) {
    return NextResponse.json({ error: 'User not found' }, { status: 404 })
  }
  
  return NextResponse.json(updatedUser)
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const id = params.id
  
  // Delete user from database
  const success = await deleteUser(id)
  
  if (!success) {
    return NextResponse.json({ error: 'User not found' }, { status: 404 })
  }
  
  return NextResponse.json({ message: 'User deleted successfully' })
}
API Route with Authentication

// app/api/protected/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { getToken } from 'next-auth/jwt'

export async function GET(request: NextRequest) {
  // Get the token from the request
  const token = await getToken({ req: request })
  
  // If no token, return unauthorized
  if (!token) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }
  
  // If token exists, return protected data
  return NextResponse.json({ 
    message: 'This is protected data',
    user: token 
  })
}
API Route with Error Handling

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    
    // Validate input
    if (!body.name || body.name.trim() === '') {
      return NextResponse.json(
        { error: 'Name is required' },
        { status: 400 }
      )
    }
    
    // Create new user
    const newUser = { id: Date.now(), name: body.name }
    
    // Save to database
    // await saveUser(newUser)
    
    return NextResponse.json(newUser, { status: 201 })
  } catch (error) {
    console.error('Error creating user:', error)
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}
Module 5: Static Site Generation (SSG)
Static Site Generation with App Router

// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'

interface PageProps {
  params: {
    slug: string
  }
}

// This function runs at build time
export async function generateStaticParams() {
  const posts = await getAllBlogPosts()
  
  return posts.map((post) => ({
    slug: post.slug,
  }))
}

// This function runs at build time for each params
async function getBlogPost(slug: string) {
  // Fetch blog post data
  const res = await fetch(`https://api.example.com/posts/${slug}`)
  
  if (!res.ok) {
    return null
  }
  
  return res.json()
}

export default async function BlogPost({ params }: PageProps) {
  const { slug } = params
  const post = await getBlogPost(slug)

  if (!post) {
    notFound()
  }

  return (
    

{post.title}

{post.content}

) }
Static Site Generation with Pages Router

// pages/blog/[slug].tsx
import type { GetStaticProps, GetStaticPaths, InferGetStaticPropsType } from 'next'

type Post = {
  id: number
  title: string
  content: string
  slug: string
}

type BlogPostProps = {
  post: Post
}

export default function BlogPost({ post }: BlogPostProps) {
  return (
    

{post.title}

{post.content}

) } // This function tells Next.js which pages to build at build time export const getStaticPaths: GetStaticPaths = async () => { const res = await fetch('https://api.example.com/posts') const posts: Post[] = await res.json() const paths = posts.map((post) => ({ params: { slug: post.slug }, })) return { paths, fallback: 'blocking', // or true or false } } // This function fetches data for a specific post at build time export const getStaticProps: GetStaticProps = async ({ params }) => { const res = await fetch(`https://api.example.com/posts/${params.slug}`) const post: Post = await res.json() return { props: { post, }, revalidate: 60, // Re-generate the page every 60 seconds (ISR) } }
Incremental Static Regeneration (ISR)

// pages/blog/[slug].tsx
import type { GetStaticProps, GetStaticPaths } from 'next'

// ... component code ...

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const res = await fetch(`https://api.example.com/posts/${params.slug}`)
  const post = await res.json()

  return {
    props: {
      post,
    },
    revalidate: 60, // Re-generate the page every 60 seconds
  }
}

// ... getStaticPaths ...
Static Generation with Data Fetching

// app/posts/page.tsx
async function getPosts() {
  const res = await fetch('https://api.example.com/posts', {
    cache: 'force-cache', // Cache the response
  })
  
  if (!res.ok) {
    throw new Error('Failed to fetch posts')
  }
  
  return res.json()
}

export default async function PostsPage() {
  const posts = await getPosts()

  return (
    

Posts

    {posts.map((post) => (
  • {post.title}
  • ))}
) }
Exporting Static Site

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
  trailingSlash: true,
  images: {
    unoptimized: true,
  },
}

module.exports = nextConfig

// package.json
{
  "scripts": {
    "build": "next build",
    "export": "next export"
  }
}
Module 6: Server-Side Rendering (SSR)
Server-Side Rendering with App Router

// app/posts/[slug]/page.tsx
import { notFound } from 'next/navigation'

interface PageProps {
  params: {
    slug: string
  }
  searchParams: {
    [key: string]: string | string[] | undefined
  }
}

// This function runs on the server for each request
export default async function BlogPost({ params, searchParams }: PageProps) {
  const { slug } = params
  
  // Fetch data on the server for each request
  const res = await fetch(`https://api.example.com/posts/${slug}`, {
    cache: 'no-store', // Disable caching
  })
  
  if (!res.ok) {
    notFound()
  }
  
  const post = await res.json()

  return (
    

{post.title}

{post.content}

View count: {post.views + 1}

) }
Server-Side Rendering with Pages Router

// pages/posts/[slug].tsx
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'

type Post = {
  id: number
  title: string
  content: string
  views: number
}

type BlogPostProps = {
  post: Post
}

export default function BlogPost({ post }: BlogPostProps) {
  return (
    

{post.title}

{post.content}

View count: {post.views}

) } // This function runs on the server for each request export const getServerSideProps: GetServerSideProps = async ({ params, req, res }) => { const { slug } = params // Fetch data on the server for each request const res = await fetch(`https://api.example.com/posts/${slug}`) if (!res.ok) { return { notFound: true, } } const post = await res.json() // Increment view count await fetch(`https://api.example.com/posts/${slug}/increment-views`, { method: 'POST', }) // Set cache control headers res.setHeader( 'Cache-Control', 'public, s-maxage=10, stale-while-revalidate=59' ) return { props: { post, }, } }
Server-Side Rendering with Authentication

// pages/dashboard.tsx
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import { getSession } from 'next-auth/react'

type DashboardProps = {
  user: {
    id: string
    name: string
    email: string
  }
}

export default function Dashboard({ user }: DashboardProps) {
  return (
    

Dashboard

Welcome, {user.name}!

) } export const getServerSideProps: GetServerSideProps = async (context) => { const session = await getSession(context) if (!session) { return { redirect: { destination: '/login', permanent: false, }, } } // Fetch user data const res = await fetch(`https://api.example.com/users/${session.user.id}`) const user = await res.json() return { props: { user, }, } }
Server-Side Rendering with Dynamic Data

// pages/search.tsx
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import { useState } from 'react'

type SearchResult = {
  id: number
  title: string
  description: string
}

type SearchPageProps = {
  initialResults: SearchResult[]
  query: string
}

export default function SearchPage({ initialResults, query }: SearchPageProps) {
  const [results, setResults] = useState(initialResults)
  const [loading, setLoading] = useState(false)
  
  const handleSearch = async (newQuery: string) => {
    setLoading(true)
    
    const res = await fetch(`/api/search?q=${newQuery}`)
    const data = await res.json()
    
    setResults(data.results)
    setLoading(false)
  }
  
  return (
    

Search Results for "{query}"

{loading ? (

Loading...

) : (
    {results.map((result) => (
  • {result.title}

    {result.description}

  • ))}
)}
) } export const getServerSideProps: GetServerSideProps = async (context) => { const query = context.query.q as string || '' if (!query) { return { props: { initialResults: [], query, }, } } // Fetch initial search results const res = await fetch(`https://api.example.com/search?q=${query}`) const data = await res.json() return { props: { initialResults: data.results, query, }, } }
Module 7: Styling with CSS Modules
CSS Modules

/* styles/Home.module.css */
.container {
  padding: 0 2rem;
}

.main {
  min-height: 100vh;
  padding: 4rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.title {
  margin: 0;
  line-height: 1.15;
  font-size: 4rem;
  text-align: center;
}

.description {
  margin: 4rem 0;
  line-height: 1.5;
  font-size: 1.5rem;
  text-align: center;
}

.grid {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  max-width: 800px;
}

.card {
  margin: 1rem;
  padding: 1.5rem;
  text-align: left;
  color: inherit;
  text-decoration: none;
  border: 1px solid #eaeaea;
  border-radius: 10px;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.card:hover,
.card:focus,
.card:active {
  border-color: #0070f3;
}

.card h2 {
  margin: 0 0 1rem 0;
  font-size: 1.5rem;
}

.card p {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.5;
}

// pages/index.tsx
import type { NextPage } from 'next'
import Head from 'next/head'
import styles from '../styles/Home.module.css'

const Home: NextPage = () => {
  return (
    
  )
}

export default Home
Tailwind CSS

# Install Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    './app/**/*.{js,ts,jsx,tsx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

// components/Button.tsx
import { ButtonHTMLAttributes } from 'react'

interface ButtonProps extends ButtonHTMLAttributes {
  variant?: 'primary' | 'secondary'
  size?: 'sm' | 'md' | 'lg'
}

export default function Button({
  variant = 'primary',
  size = 'md',
  children,
  className = '',
  ...props
}: ButtonProps) {
  const baseClasses = 'rounded font-medium transition-colors'
  const variantClasses = {
    primary: 'bg-blue-500 text-white hover:bg-blue-600',
    secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
  }
  const sizeClasses = {
    sm: 'px-3 py-1 text-sm',
    md: 'px-4 py-2',
    lg: 'px-6 py-3 text-lg',
  }

  return (
    
  )
}
Styled Components

# Install styled-components
npm install styled-components
npm install -D @types/styled-components

// components/StyledButton.tsx
import styled from 'styled-components'

const StyledButton = styled.button<{ variant?: 'primary' | 'secondary' }>`
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
  font-weight: 500;
  transition: background-color 0.2s;
  
  ${({ variant = 'primary' }) => {
    if (variant === 'primary') {
      return `
        background-color: #3b82f6;
        color: white;
        
        &:hover {
          background-color: #2563eb;
        }
      `
    } else {
      return `
        background-color: #e5e7eb;
        color: #1f2937;
        
        &:hover {
          background-color: #d1d5db;
        }
      `
    }
  }}
`

export default StyledButton

// pages/_app.tsx
import type { AppProps } from 'next/app'
import { createGlobalStyle } from 'styled-components'

const GlobalStyle = createGlobalStyle`
  body {
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
      'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
      sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }
`

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      
      
    
  )
}
CSS-in-JS with Emotion

# Install Emotion
npm install @emotion/react @emotion/styled
npm install -D @emotion/babel-plugin

// .babelrc
{
  "presets": ["next/babel"],
  "plugins": ["@emotion/babel-plugin"]
}

// components/EmotionButton.tsx
import styled from '@emotion/styled'

const EmotionButton = styled.button<{ variant?: 'primary' | 'secondary' }>`
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
  font-weight: 500;
  transition: background-color 0.2s;
  
  ${({ variant = 'primary' }) => {
    if (variant === 'primary') {
      return `
        background-color: #3b82f6;
        color: white;
        
        &:hover {
          background-color: #2563eb;
        }
      `
    } else {
      return `
        background-color: #e5e7eb;
        color: #1f2937;
        
        &:hover {
          background-color: #d1d5db;
        }
      `
    }
  }}
`

export default EmotionButton
Module 8: Authentication
NextAuth.js Setup

# Install NextAuth.js
npm install next-auth

// pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
import CredentialsProvider from 'next-auth/providers/credentials'

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    CredentialsProvider({
      name: 'credentials',
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        // Add your own logic here to validate credentials
        // This is just a placeholder
        if (credentials?.email === 'user@example.com' && credentials?.password === 'password') {
          return {
            id: '1',
            name: 'User',
            email: 'user@example.com',
          }
        }
        return null
      },
    }),
  ],
  pages: {
    signIn: '/auth/signin',
    signUp: '/auth/signup',
  },
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id
      }
      return token
    },
    async session({ session, token }) {
      if (token) {
        session.user.id = token.id as string
      }
      return session
    },
  },
  session: {
    strategy: 'jwt',
  },
})
Authentication Provider Setup

// pages/_app.tsx
import type { AppProps } from 'next/app'
import { SessionProvider } from 'next-auth/react'
import '../styles/globals.css'

export default function App({ Component, pageProps: { session: any } }: AppProps) {
  return (
    
      
    
  )
}
Login and Sign Up Pages

// pages/auth/signin.tsx
import { useState } from 'react'
import { signIn, getSession } from 'next-auth/react'
import { GetServerSideProps } from 'next'
import { useRouter } from 'next/router'
import Link from 'next/link'

export default function SignIn() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState('')
  const router = useRouter()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    setError('')

    const result = await signIn('credentials', {
      redirect: false,
      email,
      password,
    })

    if (result?.error) {
      setError('Invalid email or password')
    } else {
      router.push('/dashboard')
    }
  }

  return (
    

Sign In

{error &&

{error}

}
setEmail(e.target.value)} required />
setPassword(e.target.value)} required />

Or sign in with:

Don't have an account? Sign up

) } export const getServerSideProps: GetServerSideProps = async (context) => { const session = await getSession(context) if (session) { return { redirect: { destination: '/dashboard', permanent: false, }, } } return { props: {}, } }
Protected Routes

// pages/dashboard.tsx
import { useSession } from 'next-auth/react'
import { GetServerSideProps } from 'next'
import { getSession } from 'next-auth/react'

export default function Dashboard() {
  const { data: session, status } = useSession({
    required: true,
    onUnauthenticated() {
      // Redirect to sign in page if not authenticated
      window.location.href = '/auth/signin'
    },
  })

  if (status === 'loading') {
    return 

Loading...

} return (

Dashboard

Welcome, {session?.user?.name}!

) } export const getServerSideProps: GetServerSideProps = async (context) => { const session = await getSession(context) if (!session) { return { redirect: { destination: '/auth/signin', permanent: false, }, } } return { props: {}, } }
Custom Authentication Hook

// hooks/useAuth.ts
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/router'

export function useAuth(redirectTo?: string) {
  const { data: session, status } = useSession()
  const router = useRouter()
  const isAuthenticated = status === 'authenticated'
  const isLoading = status === 'loading'

  if (isLoading) {
    return { isAuthenticated: false, isLoading: true, session: null }
  }

  if (!isAuthenticated && redirectTo) {
    router.push(redirectTo)
  }

  return { isAuthenticated, isLoading: false, session }
}

// pages/profile.tsx
import { useAuth } from '../hooks/useAuth'

export default function Profile() {
  const { isAuthenticated, isLoading, session } = useAuth('/auth/signin')

  if (isLoading) {
    return 

Loading...

} if (!isAuthenticated) { return null // Will redirect to sign in } return (

Profile

Name: {session?.user?.name}

Email: {session?.user?.email}

) }
Logout Functionality

// components/LogoutButton.tsx
import { signOut, useSession } from 'next-auth/react'

export default function LogoutButton() {
  const { data: session } = useSession()

  const handleLogout = async () => {
    await signOut({ redirect: false })
    window.location.href = '/'
  }

  if (!session) {
    return null
  }

  return (
    
  )
}
Module 9: Database Integration
Prisma Setup

# Install Prisma
npm install prisma --save-dev
npm install @prisma/client

# Initialize Prisma
npx prisma init

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  posts     Post[]
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

// lib/prisma.ts
import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}

export const prisma = globalForPrisma.prisma ?? new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
Database Operations with Prisma

// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'

export async function GET(request: NextRequest) {
  try {
    const posts = await prisma.post.findMany({
      include: {
        author: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
      orderBy: {
        createdAt: 'desc',
      },
    })

    return NextResponse.json(posts)
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to fetch posts' },
      { status: 500 }
    )
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const { title, content, authorId } = body

    if (!title || !authorId) {
      return NextResponse.json(
        { error: 'Title and authorId are required' },
        { status: 400 }
      )
    }

    const post = await prisma.post.create({
      data: {
        title,
        content,
        authorId,
      },
      include: {
        author: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })

    return NextResponse.json(post, { status: 201 })
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to create post' },
      { status: 500 }
    )
  }
}

// app/api/posts/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const post = await prisma.post.findUnique({
      where: { id: params.id },
      include: {
        author: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })

    if (!post) {
      return NextResponse.json(
        { error: 'Post not found' },
        { status: 404 }
      )
    }

    return NextResponse.json(post)
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to fetch post' },
      { status: 500 }
    )
  }
}

export async function PUT(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const body = await request.json()
    const { title, content, published } = body

    const post = await prisma.post.update({
      where: { id: params.id },
      data: {
        title,
        content,
        published,
      },
      include: {
        author: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })

    return NextResponse.json(post)
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to update post' },
      { status: 500 }
    )
  }
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    await prisma.post.delete({
      where: { id: params.id },
    })

    return NextResponse.json({ message: 'Post deleted successfully' })
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to delete post' },
      { status: 500 }
    )
  }
}
MongoDB with Mongoose

# Install Mongoose
npm install mongoose

// lib/mongodb.ts
import mongoose from 'mongoose'

const MONGODB_URI = process.env.MONGODB_URI!

if (!MONGODB_URI) {
  throw new Error('Please define the MONGODB_URI environment variable')
}

let cached = global.mongoose

if (!cached) {
  cached = global.mongoose = { conn: null, promise: null }
}

export async function connectToDatabase() {
  if (cached.conn) {
    return cached.conn
  }

  if (!cached.promise) {
    const opts = {
      bufferCommands: false,
    }

    cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
      return mongoose
    })
  }

  try {
    cached.conn = await cached.promise
  } catch (e) {
    cached.promise = null
    throw e
  }

  return cached.conn
}

// models/Post.ts
import mongoose, { Schema, Document, Model } from 'mongoose'

export interface IPost extends Document {
  title: string
  content: string
  published: boolean
  author: string
  createdAt: Date
  updatedAt: Date
}

const PostSchema: Schema = new Schema({
  title: {
    type: String,
    required: true,
  },
  content: {
    type: String,
    default: '',
  },
  published: {
    type: Boolean,
    default: false,
  },
  author: {
    type: String,
    required: true,
  },
}, {
  timestamps: true,
})

const Post: Model = mongoose.models.Post || mongoose.model('Post', PostSchema)

export default Post

// pages/api/posts.ts
import type { NextApiRequest, NextApiResponse } from 'next'
import connectToDatabase from '@/lib/mongodb'
import Post, { IPost } from '@/models/Post'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  await connectToDatabase()

  if (req.method === 'GET') {
    try {
      const posts = await Post.find({}).sort({ createdAt: -1 })
      res.status(200).json(posts)
    } catch (error) {
      res.status(500).json({ error: 'Failed to fetch posts' })
    }
  } else if (req.method === 'POST') {
    try {
      const post = await Post.create(req.body)
      res.status(201).json(post)
    } catch (error) {
      res.status(500).json({ error: 'Failed to create post' })
    }
  } else {
    res.setHeader('Allow', ['GET', 'POST'])
    res.status(405).end(`Method ${req.method} Not Allowed`)
  }
}
Database Connection Pooling

// lib/db.ts
import { Pool } from 'pg'

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20, // Maximum number of connections in the pool
  idleTimeoutMillis: 30000, // How long a connection can be idle before being closed
  connectionTimeoutMillis: 2000, // How long to wait when connecting a new client
})

export default {
  query: (text: string, params?: any[]) => pool.query(text, params),
  end: () => pool.end(),
}

// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next'
import db from '@/lib/db'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === 'GET') {
    try {
      const { rows } = await db.query('SELECT * FROM users ORDER BY created_at DESC')
      res.status(200).json(rows)
    } catch (error) {
      res.status(500).json({ error: 'Failed to fetch users' })
    }
  } else if (req.method === 'POST') {
    try {
      const { name, email } = req.body
      const { rows } = await db.query(
        'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
        [name, email]
      )
      res.status(201).json(rows[0])
    } catch (error) {
      res.status(500).json({ error: 'Failed to create user' })
    }
  } else {
    res.setHeader('Allow', ['GET', 'POST'])
    res.status(405).end(`Method ${req.method} Not Allowed`)
  }
}
Module 10: Deployment and Optimization
Vercel Deployment

# Install Vercel CLI
npm install -g vercel

# Deploy
vercel

# For production deployment
vercel --prod
Build Configuration

// vercel.json
{
  "buildCommand": "npm run build",
  "outputDirectory": ".next",
  "framework": "nextjs",
  "regions": ["iad1"],
  "env": {
    "NODE_ENV": "production"
  }
}
Environment Variables

# .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgresql://...
SECRET_KEY=your-secret-key
Performance Optimization

// components/OptimizedImage.tsx
import Image from 'next/image'

export default function OptimizedImage() {
  return (
    Hero image
  )
}
Code Splitting

// components/LazyComponent.tsx
import dynamic from 'next/dynamic'

const LazyComponent = dynamic(() => import('./HeavyComponent'), {
  loading: () => 
Loading...
, ssr: false, }) export default function Page() { return (
) }
Analytics and Monitoring

// app/layout.tsx
import { Analytics } from '@vercel/analytics/react'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    
      
        {children}
        
      
    
  )
}
Docker Deployment

# Dockerfile
FROM node:18-alpine AS base

# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN npm run build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["node", "server.js"]

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
    depends_on:
      - db

  db:
    image: postgres:13
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
Next.js Configuration for Production

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  images: {
    domains: ['example.com'],
    formats: ['image/webp', 'image/avif'],
  },
  compiler: {
    removeConsole: process.env.NODE_ENV === 'production',
  },
  experimental: {
    optimizeCss: true,
  },
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'https://api.example.com/:path*',
      },
    ]
  },
  async redirects() {
    return [
      {
        source: '/home',
        destination: '/',
        permanent: true,
      },
    ]
  },
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff',
          },
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
          {
            key: 'X-XSS-Protection',
            value: '1; mode=block',
          },
        ],
      },
    ]
  },
}

module.exports = nextConfig