▲ Next.js 14+
Next.js Complete Cheatsheet
App Router, Server Components, data fetching, API routes, metadata and deployment.
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';// 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';
💡
Core Web Vitals: LCP (Largest Contentful Paint) < 2.5s, FID < 100ms, CLS < 0.1. Next.js Image + Font + Suspense help all three.
10
Mini Quizzes
▼
❓ Quiz 1
What is the difference between 'cache: force-cache' and 'cache: no-store' in Next.js fetch?
force-cache builds the page at build time and caches it (SSG). no-store fetches fresh data on every request (SSR). next:{revalidate:60} is ISR — rebuild in background every 60 seconds.
❓ Quiz 2
When should you use 'use client' in Next.js?
'use client' marks a component as a Client Component. Use it only when you need: React state/effects, event handlers, browser APIs, or UI libraries that require browser. Everything else should be a Server Component.