▲ Next.js 14+
Next.js Complete Cheatsheet
App Router, Server Components, data fetching, API routes, metadata and deployment.
📖 10 sections
⏱ 22 min read
✅ Quizzes included
🌙 Dark mode
01 App Router Basics
JSXProject structure
# Create Next.js app
npx create-next-app@latest myapp --typescript --tailwind --app

# App Router structure (Next.js 13+)
/app
  layout.tsx       ← Root layout (wraps all pages)
  page.tsx         ← Homepage (/)
  loading.tsx      ← Loading UI
  error.tsx        ← Error boundary
  not-found.tsx    ← 404 page
  /about
    page.tsx       ← /about
  /blog/[slug]
    page.tsx       ← /blog/my-post (dynamic route)
  /api
    /users
      route.ts     ← API endpoint: GET/POST /api/users
/components        ← Reusable components
/lib               ← Utilities, db connections
/public            ← Static assets
next.config.js     ← Next.js config
💡
App Router (app/) is the modern approach (Next.js 13+). Pages Router (pages/) is the legacy approach. Start new projects with App Router.
02 Pages & Routing
JSXRouting patterns
// Static route: app/about/page.tsx
export default function AboutPage() {
  return 

About Us

; } // Dynamic route: app/blog/[slug]/page.tsx export default function BlogPost({ params }: { params: { slug: string } }) { return

Post: {params.slug}

; } // Catch-all: app/docs/[...slug]/page.tsx // Matches /docs/a, /docs/a/b, /docs/a/b/c // Route groups (folder with parens — not in URL) // app/(auth)/login/page.tsx → /login // app/(auth)/register/page.tsx → /register // Parallel routes: @modal, @sidebar — advanced // Navigation import Link from 'next/link'; import { useRouter } from 'next/navigation'; Go to post const router = useRouter(); router.push('/dashboard'); router.replace('/login'); router.back();
generateStaticParams
Pre-generate dynamic routes at build time for SSG
Route groups
(folder) groups routes without affecting URL structure
Parallel routes
@slot folders for complex layouts with multiple independent sections
03 Data Fetching
JSXData fetching strategies
// Server Component (default) — fetch on server
async function ProductPage({ params }: { params: { id: string } }) {
  // This runs on the server — no API call from browser!
  const product = await fetch(`https://api.example.com/products/${params.id}`, {
    cache: 'force-cache',         // SSG — cache indefinitely
    // cache: 'no-store',         // SSR — fresh every request
    // next: { revalidate: 60 }   // ISR — revalidate every 60s
  }).then(r => r.json());

  return 
{product.name}
; } // generateStaticParams — pre-render dynamic routes export async function generateStaticParams() { const products = await fetchAllProducts(); return products.map(p => ({ id: p.id.toString() })); } // Parallel data fetching async function Dashboard() { const [user, posts, analytics] = await Promise.all([ getUser(), getPosts(), getAnalytics() ]); return
...
; }
SSG
Static Site Generation. Build-time. Fastest. cache:'force-cache'
SSR
Server-Side Rendering. Per-request. cache:'no-store'
ISR
Incremental Static Regeneration. Rebuild in background. next:{revalidate:60}
PPR
Partial Pre-rendering (Next.js 14+). Static shell + async dynamic parts
04 Server Components
JSXServer Components
// Server Components (default — no 'use client')
// Can: async/await, access DB directly, read files, access env vars
// Cannot: useState, useEffect, event handlers, browser APIs

// app/dashboard/page.tsx
import { db } from '@/lib/db';

export default async function DashboardPage() {
  // Runs on server — no API round-trip!
  const users = await db.user.findMany();

  return (
    

Dashboard ({users.length} users)

{users.map(u => )}
); } // Server actions (Next.js 13+) — form submissions // app/actions.ts 'use server'; export async function createUser(formData: FormData) { const name = formData.get('name') as string; await db.user.create({ data: { name } }); revalidatePath('/users'); } // Use in form
💡
Server Components are the default. They don't add to the JS bundle — great for performance. Only add 'use client' when you need interactivity.
05 Client Components
JSXClient Components
// Add 'use client' directive at top — marks file as client-side
'use client';

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);

  return (
    

Count: {count}

); } // Pass data from Server Component to Client Component as props // app/page.tsx (Server Component) import ClientButton from './ClientButton'; // Client Component export default async function Page() { const data = await fetchData(); // Server-side return ; // pass as prop } // Client-side data fetching (in Client Components) import useSWR from 'swr'; const { data, error, isLoading } = useSWR('/api/users', fetcher);
06 API Routes
TSXAPI Routes
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';

// GET /api/users
export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const page = searchParams.get('page') ?? '1';

  const users = await db.user.findMany({
    skip: (parseInt(page) - 1) * 10,
    take: 10,
  });

  return NextResponse.json({ users }, { status: 200 });
}

// POST /api/users
export async function POST(request: NextRequest) {
  const body = await request.json();

  const user = await db.user.create({ data: body });

  return NextResponse.json({ user }, { status: 201 });
}

// app/api/users/[id]/route.ts
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const user = await db.user.findUnique({ where: { id: params.id } });
  if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 });
  return NextResponse.json({ user });
}
💡
Route handlers replace Next.js 12 API routes. One file per endpoint. Export functions named GET, POST, PUT, DELETE etc.
07 Metadata & SEO
TSXMetadata & SEO
// Static metadata
export const metadata = {
  title: 'BitWithBite — Learn Everything',
  description: 'AI-powered learning platform...',
  keywords: ['education', 'AI tutor', 'courses'],
  openGraph: {
    title: 'BitWithBite',
    description: 'Learn with AI',
    images: ['/og-image.jpg'],
    type: 'website',
  },
  twitter: {
    card: 'summary_large_image',
    title: 'BitWithBite',
    images: ['/twitter-image.jpg'],
  },
};

// Dynamic metadata
export async function generateMetadata(
  { params }: { params: { slug: string } }
): Promise {
  const post = await getPost(params.slug);

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      images: [post.coverImage],
    },
  };
}

// Root layout — applies to all pages
export const metadata: Metadata = {
  title: { template: '%s | BitWithBite', default: 'BitWithBite' },
};
08 Deployment
BASHDeployment (Vercel)
# Deploy to Vercel (zero-config)
npm i -g vercel
vercel

# Environment variables (.env.local — never commit!)
NEXT_PUBLIC_API_URL=https://api.example.com   # exposed to client
DATABASE_URL=postgresql://...                  # server only
JWT_SECRET=your-secret-here                    # server only

# Self-hosted with Docker
# next.config.js
module.exports = { output: 'standalone' };

# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM node:20-alpine AS runner
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]

# Build commands
npm run build       # production build
npm run start       # start production server
npm run dev         # development with hot reload
NEXT_PUBLIC_
Prefix exposes env var to browser (included in JS bundle)
No prefix
Server-only — never sent to client
next.config.js
Configure images, redirects, rewrites, headers, env
09 Performance
JSXPerformance optimization
// Image optimization
import Image from 'next/image';

Hero image

// Font optimization
import { Inter, Outfit } from 'next/font/google';
const outfit = Outfit({ subsets: ['latin'], variable: '--font-outfit' });

// Code splitting — dynamic import
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('./Chart'), {
  loading: () => 

Loading chart...

, ssr: false, // don't render on server }); // Suspense + streaming import { Suspense } from 'react'; }> {/* streams in when ready */} // next/script import Script from 'next/script';