technology · nextjs

Next.js Cheatsheets

KDP Book Manifest & Metadata
Click to Expand & CopyManifest

Use the copy buttons below to copy metadata verbatim into the Amazon KDP Publishing forms.

Book Title
Subtitle
Target Audience
BISAC Subject Code
Keywords (Comma Separated)
Book Description (HTML)
KDP Categories
  • Books > Computers & Technology > Programming > Next.js
  • Books > Computers & Technology > Web Development

Next.js Cheatsheets

One-Page Quick References from Core Syntax to Advanced Patterns

usefulcheatsheets.com

Next.js Cheatsheets

First Edition: 2026

Copyright © 2026 by usefulcheatsheets.com. All rights reserved.

No part of this book may be reproduced in any form or by any electronic or mechanical means, including information storage and retrieval systems, without written permission from the publisher, except for the use of brief quotations in a book review.

Publisher: usefulcheatsheets.comISBN: Not ApplicableBISAC Subject Code: COM051000

Welcome to Next.js

Next.js is a key topic in Technology development.

This reference book compiles comprehensive cheatsheets covering everything from fundamentals to advanced patterns.

Use this book as a daily reference or read it linearly to build your knowledge.

How to Use This Book

Each page is a visual cheatsheet with core concepts, practical steps, code snippets, and warnings.

usefulcheatsheets.com | Introduction
Useful Cheatsheetsusefulcheatsheets.com
Next.js API Routes Patterns
Chapter 01 · Page 7
Beginner

Next.js API Routes Patterns

Build REST APIs with route handlers, middleware, CORS, and auth patterns.

TL;DR

  1. 01Create API routes in app/api folder for backend endpoints.
  2. 02Use request and response objects to handle HTTP methods.
  3. 03Set up CORS headers to allow safe cross-origin API requests.

Tips

  1. 01Keep API logic separate by using helper functions and middleware to avoid duplicating common concerns like auth and CORS across routes.

Warnings

  1. 01Never expose secrets or sensitive data in API responses — always validate input and use proper authentication before returning user data.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js API Routes Patterns
Chapter 01 · Page 8
Beginner

Next.js API Routes Patterns

(continued)

Basic Route Handlers

  • Create a GET endpoint in app/api folder.
    // app/api/users/route.ts
    export async function GET(request: Request) {
      return Response.json({ users: [] });
    }
  • Handle different HTTP methods in the same file.
    export async function GET(request: Request) {
      return Response.json({ method: "GET" });
    }
    
    export async function POST(request: Request) {
      const data = await request.json();
      return Response.json({ created: data });
    }
  • Route handlers receive a Request and return a Response.
  • Use Response.json() for JSON responses.
  • Each function name corresponds to an HTTP method.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js API Routes Patterns
Chapter 01 · Page 9
Beginner

Next.js API Routes Patterns

(continued)

Params and Catch-All Routes

  • Access dynamic route params by awaiting the params object in Next.js 15.
    // app/api/users/[id]/route.ts
    export async function GET(
      request: Request,
      { params }: { params: Promise<{ id: string }> }
    ) {
      const { id } = await params; // params is a Promise in Next.js 15
      return Response.json({ userId: id });
    }
  • Use the same await pattern for PUT, DELETE, and PATCH handlers.
    export async function PUT(
      request: Request,
      { params }: { params: Promise<{ id: string }> }
    ) {
      const { id } = await params;
      const data = await request.json();
      return Response.json({ updated: id });
    }
  • Return 404 when the resource doesn't exist.
    const { id } = await params;
    const user = await getUser(id);
    if (!user) {
      return Response.json({ error: "Not found" }, { status: 404 });
    }
    return Response.json(user);
  • Use catch-all [...path] segments for deeply nested API paths.
    // app/api/docs/[...path]/route.ts
    export async function GET(
      _req: Request,
      { params }: { params: Promise<{ path: string[] }> }
    ) {
      const { path } = await params;
      return Response.json({ segments: path });
    }
  • Use optional catch-all [[...path]] when the base segment should also match.
    app/api/search/[[...filters]]/route.ts
    # matches /api/search AND /api/search/active/recent
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js API Routes Patterns
Chapter 01 · Page 10
Beginner

Next.js API Routes Patterns

(continued)

Request Body Parsing

  • Parse JSON from the request body.
    export async function POST(request: Request) {
      const data = await request.json();
      console.log(data); // { name: "Alice" }
      return Response.json({ success: true });
    }
  • Handle form data for multipart uploads.
    export async function POST(request: Request) {
      const formData = await request.formData();
      const file = formData.get("file");
      return Response.json({ uploaded: true });
    }
  • Parse query parameters from the URL.
    export async function GET(request: Request) {
      const { searchParams } = new URL(request.url);
      const query = searchParams.get("q");
      return Response.json({ query });
    }
  • Validate required fields before processing and return 400 for bad input.
    const { name, email } = await request.json();
    if (!name || !email) {
      return Response.json({ error: "name and email are required" }, { status: 400 });
    }
  • Read a plain text body with request.text() for webhooks or raw payloads.
    export async function POST(request: Request) {
      const raw = await request.text(); // e.g. Stripe webhook payload
      const sig = request.headers.get("stripe-signature");
      return Response.json({ received: true });
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js API Routes Patterns
Chapter 01 · Page 11
Beginner

Next.js API Routes Patterns

(continued)

CORS and Headers

  • Set CORS headers for cross-origin requests.
    export async function GET(request: Request) {
      const response = Response.json({ data: "hello" });
      response.headers.set("Access-Control-Allow-Origin", "*");
      response.headers.set("Access-Control-Allow-Methods", "GET, POST");
      return response;
    }
  • Use middleware for consistent header handling.
    // middleware.ts
    export function middleware(request: Request) {
      const response = Response.next();
      response.headers.set("Access-Control-Allow-Origin", "*");
      return response;
    }
  • Return OPTIONS response for preflight requests with a 204 status.
    export async function OPTIONS() {
      return new Response(null, {
        status: 204,
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
          "Access-Control-Allow-Headers": "Content-Type, Authorization"
        }
      });
    }
  • Restrict CORS to specific origins instead of wildcard for production.
    const origin = request.headers.get("origin");
    const allowed = ["https://myapp.com", "https://staging.myapp.com"];
    const allowOrigin = allowed.includes(origin ?? "") ? origin : "";
    response.headers.set("Access-Control-Allow-Origin", allowOrigin ?? "");
  • Add Cache-Control and custom response headers to control caching and metadata.
    return new Response(JSON.stringify(data), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "no-store",
        "X-Request-Id": crypto.randomUUID()
      }
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js API Routes Patterns
Chapter 01 · Page 12
Beginner

Next.js API Routes Patterns

(continued)

Auth and Error Patterns

  • Database queries in API routes.
    import { prisma } from "@/lib/prisma";
    
    export async function GET() {
      const users = await prisma.user.findMany();
      return Response.json(users);
    }
  • Error handling with status codes.
    export async function POST(request: Request) {
      try {
        const data = await request.json();
        if (!data.name) {
          return Response.json(
            { error: "Name required" },
            { status: 400 }
          );
        }
        return Response.json({ success: true });
      } catch (error) {
        return Response.json(
          { error: "Invalid request" },
          { status: 500 }
        );
      }
    }
  • Authentication in API routes.
    export async function GET(request: Request) {
      const token = request.headers.get("authorization");
      if (!token) {
        return Response.json({ error: "Unauthorized" }, { status: 401 });
      }
      return Response.json({ data: "protected" });
    }
  • Extract API keys from headers and compare against an environment variable.
    const apiKey = request.headers.get("x-api-key");
    if (apiKey !== process.env.API_SECRET) {
      return Response.json({ error: "Forbidden" }, { status: 403 });
    }
  • Log errors server-side before returning a generic 500 message to the client.
    } catch (err) {
      console.error("[api/users] error:", err);
      return Response.json({ error: "Internal server error" }, { status: 500 });
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js API Routes Patterns
Chapter 01 · Page 13
Beginner

Next.js API Routes Patterns

(FAQ)

FAQ

Export named functions matching each HTTP method from your route.ts file, such as export async function GET(request: Request) and export async function POST(request: Request). Requests with unhandled methods automatically return a 405 response.

In the App Router, params are passed as the second argument to the handler. In Next.js 15, params is a Promise — type it as { params: Promise<{ id: string }> } and await it before accessing properties: const { id } = await params. The route file goes at app/api/users/[id]/route.ts.

In App Router route handlers, the body is a ReadableStream — you must call await request.json() or await request.text() to consume it. Also ensure the client sends a Content-Type: application/json header when posting JSON.

Export a dedicated OPTIONS handler that returns the required CORS headers with a 204 status: return new Response(null, { status: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,POST', 'Access-Control-Allow-Headers': 'Content-Type' } }). You must also include these headers on your actual GET/POST responses.

Pages Router uses pages/api/ with Node.js-style req/res objects, while App Router uses app/api/ with the standard Web Request/Response API. App Router route handlers support edge runtime and streaming natively, making them the better choice for new Next.js 13+ projects.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Directives
Chapter 02 · Page 14
Beginner

Next.js Directives

Use use server and use client directives to control rendering and data access.

TL;DR

  1. 01"use client" marks components to render on the client.
  2. 02"use server" marks functions to run only on the server.
  3. 03Server components are the default in the App Router.

Tips

  1. 01Use server components for fetching data and accessing secrets — it's faster and more secure than client components.

Warnings

  1. 01"use client" at the top of a file makes the entire file and its imports client-side — keep client-heavy components in separate files.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Directives
Chapter 02 · Page 15
Beginner

Next.js Directives

(continued)

Default Server Rendering

  • Every component in the App Router is a Server Component by default — no directive needed.
    // app/page.tsx — server component, no directive required
    export default async function Home() {
      const res = await fetch("https://api.example.com/data", { cache: "no-store" });
      const data = await res.json();
      return <div>{data.title}</div>;
    }
  • Server Components run only on the server — no JavaScript is shipped to the browser for them.
  • They can use async/await, read environment variables, and query databases securely.
    export default async function AdminPage() {
      const users = await db.user.findMany();
      return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
    }
  • Server Components cannot use React hooks or event handlers — add a directive to opt into client rendering only when needed.
    // ❌ Not allowed in a Server Component — move to a Client Component
    // const [count, setCount] = useState(0);
    // window.addEventListener(...);
  • Use React cache() to deduplicate repeated server-side data calls across components.
    import { cache } from "react";
    
    export const getUser = cache(async (id: string) => db.user.findUnique({ where: { id } }));
    // getUser(id) called in Header and Sidebar = only one DB query
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Directives
Chapter 02 · Page 16
Beginner

Next.js Directives

(continued)

Client Directive Scope

  • "use client" must be the very first line of the file — before any imports.
    "use client";
    // ↑ must appear before any import statements
    import { useState } from "react";
    
    export default function Counter() {
      const [count, setCount] = useState(0);
      return <button onClick={() => setCount(count + 1)}>{count}</button>;
    }
  • The directive marks the entire file AND all modules it imports as client-side — contamination spreads through imports.
    "use client";
    // Both files below become part of the client bundle:
    import { Chart } from "./Chart";       // ← client-side
    import { DataGrid } from "./DataGrid";   // ← client-side
  • Avoid importing heavy utilities into a "use client" file — they inflate the browser bundle.
    "use client";
    // ❌ Pulls entire lodash into the client bundle
    import _ from "lodash";
    
    // ✅ Move heavy computation to a Server Component or Server Action
  • Split a large client component into smaller files to minimise the client boundary.
    // Good: only the interactive button is a Client Component
    // app/post/page.tsx — Server Component (no directive)
    import { LikeButton } from "./LikeButton"; // only this file has "use client"
    export default async function PostPage() {
      const post = await getPost();
      return <article>{post.body}<LikeButton id={post.id} /></article>;
    }
  • Third-party components that use hooks or browser APIs must be wrapped with "use client".
    // components/MapWrapper.tsx
    "use client";
    import { MapComponent } from "some-map-library"; // uses window internally
    export default function MapWrapper({ center }) {
      return <MapComponent center={center} />;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Directives
Chapter 02 · Page 17
Beginner

Next.js Directives

(continued)

Server Functions

  • Mark functions to run only on the server with "use server".
    "use server";
    
    export async function submitForm(formData: FormData) {
      const email = formData.get("email");
      // Save to database securely
      return { success: true };
    }
  • Server functions can be called from client components.
    "use client";
    
    async function handleSubmit(formData: FormData) {
      const result = await submitForm(formData);
    }
  • Functions run securely on the server.
  • Use server functions with HTML form actions for progressive enhancement.
    // app/form.tsx (server component)
    import { saveContact } from "./actions";
    
    export default function ContactForm() {
      return <form action={saveContact}><input name="email" /><button>Send</button></form>;
    }
  • Validate input inside server functions before writing to the database.
    "use server";
    
    export async function createPost(formData: FormData) {
      const title = formData.get("title") as string;
      if (!title || title.length < 3) return { error: "Title too short" };
      await db.post.create({ data: { title } });
      return { ok: true };
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Directives
Chapter 02 · Page 18
Beginner

Next.js Directives

(continued)

Directive Boundary Patterns

  • A Client Component can receive a Server Component as a child via props — the child still runs on the server.
    "use client";
    // ClientShell.tsx — the children prop can be a Server Component
    export default function ClientShell({ children }) {
      return <div>{children}</div>;
    }
    
    // app/page.tsx (Server Component) — ServerSidebar stays on the server
    <ClientShell><ServerSidebar /></ClientShell>
  • A Client Component cannot directly import a Server Component — this would pull server code into the client bundle.
    "use client";
    // ❌ Cannot import a server component directly from a client file
    import { ServerSidebar } from "./ServerSidebar"; // runtime error
  • Functions cannot cross the server-client boundary as props — use Server Actions instead.
    // ❌ Plain server function cannot be passed as a prop
    <ClientComp onClick={serverFunction} />
    
    // ✅ Server Action CAN be passed as a prop
    "use server";
    export async function deleteItem(id: string) { await db.item.delete({ where: { id } }); }
    // Then in a Server Component: <ClientComp action={deleteItem} />
  • Only JSON-serialisable values can be passed as props across the server-client boundary.
    // ✅ OK: strings, numbers, arrays, plain objects, Promises
    <ClientChart data={data} title="Sales" />
    
    // ❌ Not OK: class instances, Maps, Sets, functions (unless Server Actions)
    <ClientComp handler={someClassInstance} />
  • Verify the boundary by checking the Network tab — server component code should not appear in JS bundles.
    # Open DevTools > Network > JS
    # Server-only modules (db, secrets) should NOT appear in client chunks
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Directives
Chapter 02 · Page 19
Beginner

Next.js Directives

(continued)

Directive Design Patterns

  • Use server components by default for performance.
    // Good: server component fetches data
    export default async function Posts() {
      const posts = await getPosts();
      return posts.map(post => <Post key={post.id} post={post} />);
    }
  • Mark leaf components as "use client" for interactivity.
    "use client";
    // Only interactive components
    export function Favorite({ postId }) {
      const [liked, setLiked] = useState(false);
      return <button onClick={() => setLiked(!liked)}>{liked ? "Liked" : "Like"}</button>;
    }
  • Use server-only package to prevent accidental client imports.
    import "server-only";
    // This file throws an error if imported in a client bundle
    export async function getSecretData() { ... }
  • Avoid passing non-serializable values like functions as props.
    // Wrong: functions can't cross the server-client boundary as props
    <ClientComp onClick={serverFunction} /> // ❌
    // Use server actions instead
    <ClientComp action={serverAction} />    // ✅
  • Test directive placement by checking the Network tab for unexpected JS.
    # Open browser DevTools > Network > JS
    # Check that server-only components don't appear in client bundles
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Directives
Chapter 02 · Page 20
Beginner

Next.js Directives

(FAQ)

FAQ

Add 'use client' when your component uses browser APIs, React hooks like useState or useEffect, or event listeners. Components that only fetch data or render static content should stay as server components.

Yes — Server Components can import and render Client Components, but not the other way around. Pass Server Components as children or props to Client Components if you need to compose them.

Placing 'use server' at the top of a file marks all exported functions as Server Actions, while placing it inside an individual async function marks only that function. Use the inline form when you need just one Server Action inside a Client Component.

If the component accessing the secret has 'use client' or is imported by a Client Component, it runs on the client. Move secret access into a Server Component or Server Action where the code never leaves the server.

No — Server Components can call fetch or query a database directly without any directive, since they already run only on the server by default in the App Router. 'use server' is specifically for Server Actions (functions called from the client).

Useful Cheatsheetsusefulcheatsheets.com
Next.js Dynamic Routes
Chapter 03 · Page 21
Beginner

Next.js Dynamic Routes

Create dynamic pages with brackets, access route params, and generate static paths with generateStaticParams.

TL;DR

  1. 01Wrap folder names in brackets to create dynamic segments.
  2. 02Access params through the params prop passed to pages.
  3. 03Use generateStaticParams to pre-build dynamic pages at compile time.

Tips

  1. 01Use <code>generateStaticParams()</code> for high-traffic pages like blog posts to pre-build them at deploy time for instant page loads.
  2. 02Combine <code>generateStaticParams()</code> with a <code>revalidate</code> export so high-traffic dynamic pages stay pre-built while still picking up fresh content automatically.

Warnings

  1. 01If generateStaticParams doesn't include a route, it will be generated on first request which causes a slow cold start on serverless.
  2. 02The object keys returned by <code>generateStaticParams()</code> must exactly match the dynamic segment names in the folder, or Next.js throws a build error: "generateStaticParams returned invalid params."
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Dynamic Routes
Chapter 03 · Page 22
Beginner

Next.js Dynamic Routes

(continued)

Basic Dynamic Routes

  • One folder name decides whether a route is static or matches infinite URLs — wrap it in square brackets to make it dynamic.
    app/
      posts/
        [id]/
          page.tsx
  • This matches /posts/1, /posts/2, or any value for the id segment.
  • Access the dynamic parameter through the params prop, which is now an async Promise you must await.
    export default async function Post({ params }: { params: Promise<{ id: string }> }) {
      const { id } = await params;
      return <h1>Post {id}</h1>;
    }
  • Each dynamic segment creates a separate route that can load different data.
  • URL parameters are always strings, so parse them as needed.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Dynamic Routes
Chapter 03 · Page 23
Beginner

Next.js Dynamic Routes

(continued)

Accessing Route Parameters

  • Nest two dynamic segments and the params object resolves both at once, once you await it.
    // Route: app/users/[userId]/posts/[postId]/page.tsx
    export default async function PostPage({ 
      params 
    }: { 
      params: Promise<{ userId: string; postId: string }> 
    }) {
      const { userId, postId } = await params;
      return <div>User {userId}, Post {postId}</div>;
    }
  • Fetch data using the resolved params to load content specific to that route.
    const { id } = await params;
    const post = await getPost(id);
  • Pass params to layout components if they need this information.
  • Nested dynamic segments work just like single-level ones.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Dynamic Routes
Chapter 03 · Page 24
Beginner

Next.js Dynamic Routes

(continued)

Catch-All Routes

  • Need one file to match /docs/a, /docs/a/b, and /docs/a/b/c alike? Catch-all segments do exactly that with [...slug].
    app/
      docs/
        [...slug]/
          page.tsx
  • This matches /docs/a, /docs/a/b, /docs/a/b/c, etc.
  • The slug param is always an array of path segments, available after awaiting params.
    export default async function Docs({ params }: { params: Promise<{ slug: string[] }> }) {
      const { slug } = await params;
      const path = slug.join("/");
      return <h1>{path}</h1>;
    }
  • Use catch-all routes for nested documentation or content sites.
  • Combine with optional catch-all [[...slug]] for breadcrumb navigation.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Dynamic Routes
Chapter 03 · Page 25
Beginner

Next.js Dynamic Routes

(continued)

Optional Catch-All Routes

  • What if one page needs to handle both /blog and /blog/2025/january/post? Double the brackets to [[...slug]] and the catch-all becomes optional.
    app/
      blog/
        [[...slug]]/
          page.tsx
  • This matches /blog, /blog/post-1, /blog/2025/january/post, etc.
  • The slug param is an array only if segments exist, otherwise undefined — await params first to read it.
    const { slug } = await params;
    const segments = slug ?? [];
    const depth = segments.length;
  • Use optional catch-all when a single page handles multiple URL patterns.
  • Great for flexible navigation menus and hierarchical content.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Dynamic Routes
Chapter 03 · Page 26
Beginner

Next.js Dynamic Routes

(continued)

Static Generation with Dynamic Routes

  • Skip the per-request server round trip entirely — export generateStaticParams() to pre-build specific dynamic pages at deploy time.
    export async function generateStaticParams() {
      const posts = await getPosts();
      return posts.map((post) => ({
        id: post.id.toString(),
      }));
    }
    
    export default async function Post({ params }: { params: Promise<{ id: string }> }) {
      const { id } = await params;
      // This page is pre-built for every post at build time
      return <h1>Post {id}</h1>;
    }
  • Pre-built pages load instantly with no server delay.
  • Pages not in generateStaticParams are generated on-demand the first time.
  • Use revalidate for incremental static regeneration to update stale pages.
    export const revalidate = 3600; // Rebuild every hour
  • Great for blogs, product pages, and public documentation.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Dynamic Routes
Chapter 03 · Page 27
Beginner

Next.js Dynamic Routes

(FAQ)

FAQ

Create a folder with the segment name wrapped in square brackets, like app/blog/[slug]/page.tsx. The bracket syntax tells Next.js that this segment is dynamic and will match any value at that URL position.

Dynamic page components receive a params prop, which is an async Promise containing your segment names as keys. For a route like [slug], type it as async function Page({ params }: { params: Promise<{ slug: string }> }), then run const { slug } = await params before using it.

The [...slug] (catch-all) requires at least one segment and returns 404 for the base path, while [[...slug]] (optional catch-all) also matches the parent route with no segments. Use optional catch-all when you want one component to handle both /docs and /docs/getting-started.

Yes, you can nest multiple dynamic folders, such as app/shop/[category]/[productId]/page.tsx. Both category and productId will be available on the params object simultaneously.

By default, Next.js falls back to server-rendering the missing page on first request. You can change this behavior by exporting export const dynamicParams = false from the page, which will make unlisted routes return a 404 instead.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Environment Variables
Chapter 04 · Page 28
Beginner

Next.js Environment Variables

Manage environment variables and configure Next.js for different environments.

TL;DR

  1. 01Use .env.local for environment variables in development.
  2. 02Variables prefixed NEXT_PUBLIC_ are exposed to browser.
  3. 03Server-only variables are only available on the server.

Tips

  1. 01Use .env.example to document which environment variables are needed — commit it to version control.

Warnings

  1. 01Never commit .env.local files — they contain secrets. Use .gitignore to exclude them.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Environment Variables
Chapter 04 · Page 29
Beginner

Next.js Environment Variables

(continued)

Environment Files

  • Create .env.local for local development variables.
    # .env.local
    DATABASE_URL=postgresql://user:password@localhost/db
    API_SECRET=my-secret-key
  • Create .env.production.local for production secrets — Next.js loads it only in production and it is gitignored by default.
    # .env.production.local
    DATABASE_URL=postgresql://user:password@prod/db
    API_SECRET=production-secret
  • Variables are loaded at build time for static files.
  • Use .env.development to set variables for npm run dev only.
    # .env.development
    NEXT_PUBLIC_API_URL=http://localhost:4000
    LOG_LEVEL=debug
  • Use .env for shared defaults across all environments.
    # .env (committed — no secrets)
    NEXT_PUBLIC_APP_NAME=My App
    NEXT_PUBLIC_SUPPORT_EMAIL=support@example.com
  • Next.js uses first-definition-wins: files are checked highest to lowest priority.
    # Priority (highest to lowest):
    # 1. .env.{NODE_ENV}.local  (e.g. .env.development.local)
    # 2. .env.local             (skipped in test environment)
    # 3. .env.{NODE_ENV}        (e.g. .env.development)
    # 4. .env                   (shared defaults)
    # The FIRST file that defines a variable wins — later files cannot override it.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Environment Variables
Chapter 04 · Page 30
Beginner

Next.js Environment Variables

(continued)

Public Variables

  • Prefix with NEXT_PUBLIC_ to expose to browser.
    # .env.local
    NEXT_PUBLIC_API_URL=https://api.example.com
    NEXT_PUBLIC_APP_NAME=My App
  • Access in client and server code.
    function Component() {
      const apiUrl = process.env.NEXT_PUBLIC_API_URL;
      return <p>API: {apiUrl}</p>;
    }
  • Use for non-sensitive data only.
  • Public variables are inlined at build time, not at runtime.
    // process.env.NEXT_PUBLIC_API_URL becomes a string literal in the bundle
    const url = process.env.NEXT_PUBLIC_API_URL; // "https://api.example.com"
  • Use for things like Stripe publishable keys or analytics IDs.
    NEXT_PUBLIC_STRIPE_KEY=pk_live_abc123
    NEXT_PUBLIC_GA_ID=G-XXXXXXX
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Environment Variables
Chapter 04 · Page 31
Beginner

Next.js Environment Variables

(continued)

Server-Only Variables

  • Variables without NEXT_PUBLIC_ are server-only.
    # .env.local
    DATABASE_URL=postgresql://...
    API_KEY=secret-key
  • Access only in server-side code.
    export async function GET() {
      const apiKey = process.env.API_KEY;
      // Safe: runs on server only
    }
  • Never available in browser or client components.
  • Use server-only variables in server actions and route handlers.
    "use server";
    
    export async function deleteUser(id: string) {
      await db.user.delete({ where: { id } });
      // process.env.DATABASE_URL is safe here
    }
  • Attempting to access server-only vars in client code returns undefined.
    "use client";
    // process.env.API_KEY is undefined — not leaked to browser
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Environment Variables
Chapter 04 · Page 32
Beginner

Next.js Environment Variables

(continued)

Runtime Config and Validation

  • Expose build-time variables to the client via the env key in next.config.js.
    // next.config.js
    module.exports = {
      env: {
        APP_VERSION: process.env.npm_package_version, // inlined at build time
        FEATURE_FLAG: process.env.FEATURE_FLAG
      }
    };
  • Use serverRuntimeConfig for values available only on the server at runtime.
    // next.config.js
    module.exports = {
      serverRuntimeConfig: {
        mySecret: process.env.MY_SECRET // server-only, not in browser
      },
      publicRuntimeConfig: {
        apiUrl: process.env.NEXT_PUBLIC_API_URL // shared with client
      }
    };
  • Validate required environment variables at startup to catch missing config immediately.
    // lib/env.ts — imported in app/layout.tsx
    const required = ["DATABASE_URL", "AUTH_SECRET"];
    for (const key of required) {
      if (!process.env[key]) throw new Error(`Missing env var: ${key}`);
    }
  • Use the t3-env or envalid library for type-safe, schema-validated environment variables.
    import { createEnv } from "@t3-oss/env-nextjs";
    import { z } from "zod";
    
    export const env = createEnv({
      server: { DATABASE_URL: z.string().url() },
      client: { NEXT_PUBLIC_API_URL: z.string().url() },
      runtimeEnv: process.env
    });
  • Restart the dev server after changing .env files — Next.js does not hot-reload environment variables.
    # After editing .env.local or adding new variables:
    npm run dev  # restart required for new vars to take effect
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Environment Variables
Chapter 04 · Page 33
Beginner

Next.js Environment Variables

(continued)

Secrets and Version Control

  • Never commit .env.local or .env.production.local to version control.
    # .gitignore
    .env.local
    .env.development.local
    .env.test.local
    .env.production.local
  • Commit .env.example as documentation of required variables — never put real values in it.
    # .env.example (safe to commit)
    DATABASE_URL=postgresql://user:password@localhost/db
    NEXT_PUBLIC_API_URL=https://api.example.com
    AUTH_SECRET=
  • Never access process.env in a Client Component for secrets — server-only variables return undefined in the browser and do NOT get bundled.
    "use client";
    // ❌ DATABASE_URL is undefined in the browser — not leaked, just missing
    const url = process.env.DATABASE_URL;
  • Use NEXT_PUBLIC_ only for truly public values — these are inlined as string literals and visible to anyone who reads the JS bundle.
    # Safe to expose
    NEXT_PUBLIC_STRIPE_KEY=pk_live_abc123
    NEXT_PUBLIC_GA_ID=G-XXXXXXX
    
    # Never prefix with NEXT_PUBLIC_
    STRIPE_SECRET=sk_live_secret
    DATABASE_URL=postgres://prod-secret
  • Set environment variables in your CI/CD platform rather than committing .env.production files.
    # Vercel CLI
    vercel env add DATABASE_URL production
    
    # GitHub Actions secret
    # Settings > Secrets > Actions > New repository secret
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Environment Variables
Chapter 04 · Page 34
Beginner

Next.js Environment Variables

(FAQ)

FAQ

Prefix the variable name with NEXT_PUBLIC_ (e.g., NEXT_PUBLIC_API_URL) and Next.js will inline its value at build time, making it available in both client and server code. Variables without this prefix are stripped from the client bundle entirely.

.env is for default values shared across environments and can be committed to version control, while .env.local overrides those values locally and should never be committed. Next.js loads both files, but .env.local always takes precedence.

Make sure the variable is defined in your .env.local or .env file and that you are not accidentally prefixing it with NEXT_PUBLIC_ while only expecting server-side access. Also confirm the dev server was restarted after adding or changing the variable, since Next.js does not hot-reload env files.

Yes — use the env key in next.config.js to explicitly expose specific variables to the client, or use serverRuntimeConfig for server-only values and publicRuntimeConfig for values shared with the client at runtime rather than build time.

Next.js loads environment files in priority order from highest to lowest: .env.{NODE_ENV}.local, then .env.local (skipped in test), then .env.{NODE_ENV}, then .env. The first file that defines a variable wins — lower-priority files cannot override it. Use .env.local for secrets that should never be committed, and .env for shared defaults.

Useful Cheatsheetsusefulcheatsheets.com
Next.js File Structure
Chapter 05 · Page 35
Beginner

Next.js File Structure

Organize Next.js projects for maintainability and performance.

TL;DR

  1. 01Use app router for file-based routing with folders.
  2. 02Group related files with parentheses to exclude from routing.
  3. 03Colocate components, hooks, and utilities near where they're used.

Tips

  1. 01Organize by feature or domain first, not by file type — this keeps related code together and makes refactoring easier.

Warnings

  1. 01Don't create too many deeply nested folder levels — keep structures 3–4 levels deep for clarity.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js File Structure
Chapter 05 · Page 36
Beginner

Next.js File Structure

(continued)

App Router Basics

  • Create routes by adding a folder and a page.tsx file inside it.

    app/
      page.tsx              # / route
      about/
        page.tsx            # /about route
      blog/
        page.tsx            # /blog route
        [slug]/
          page.tsx          # /blog/:slug route
    
  • Map URL path segments directly to folder names in the app directory.

    app/users/[id]/posts/page.tsx  →  /users/:id/posts
    
  • Add a root layout.tsx to wrap every page with shared HTML structure.

    // app/layout.tsx
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return <html lang="en"><body>{children}</body></html>;
    }
    
  • Use page.tsx for visible UI and route.ts for API route handlers.

    app/
      api/
        users/
          route.ts    # GET /api/users, POST /api/users
      users/
        page.tsx      # /users page rendered in the browser
    
  • Add a loading.tsx next to any page to show a Suspense fallback.

    // app/blog/loading.tsx
    export default function Loading() {
      return <p>Loading posts...</p>;
    }
    
  • Use dynamic [slug] segments to match variable URL parts — in Next.js 15, params is a Promise.

    // app/blog/[slug]/page.tsx
    export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
      const { slug } = await params;
      return <h1>Post: {slug}</h1>;
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js File Structure
Chapter 05 · Page 37
Beginner

Next.js File Structure

(continued)

Special Files

  • Use layout.tsx to share persistent UI across child routes without remounting.

    // app/layout.tsx
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html>
          <body>
            <Header />
            {children}
            <Footer />
          </body>
        </html>
      );
    }
    
  • Use error.tsx as an error boundary — it must be a Client Component.

    // app/blog/error.tsx
    "use client";
    export default function Error({ error, reset }: { error: Error; reset: () => void }) {
      return (
        <div>
          <h2>Something went wrong</h2>
          <button onClick={reset}>Try again</button>
        </div>
      );
    }
    
  • Use loading.tsx to create an automatic Suspense boundary for the segment.

    // app/blog/loading.tsx
    export default function Loading() {
      return <p>Loading posts...</p>;
    }
    
  • Use not-found.tsx to render a custom 404 for each route segment.

    // app/blog/[slug]/not-found.tsx
    export default function NotFound() {
      return <h1>Post not found</h1>;
    }
    
  • Use template.tsx when the layout must remount on each navigation.

    // app/dashboard/template.tsx
    export default function Template({ children }: { children: React.ReactNode }) {
      // Re-runs useEffect and re-mounts on every route change
      return <div>{children}</div>;
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js File Structure
Chapter 05 · Page 38
Beginner

Next.js File Structure

(continued)

Route Groups

  • Wrap folder names in parentheses to exclude them from the URL path.

    app/
      (marketing)/
        page.tsx          # / (still at root URL)
        about/page.tsx    # /about
        contact/page.tsx  # /contact
      (dashboard)/
        layout.tsx        # separate layout, no URL impact
        page.tsx          # /dashboard
    
  • Give each route group its own layout for auth vs app separation.

    app/
      (auth)/
        layout.tsx        # auth layout with centered card
        login/page.tsx    # /login
        signup/page.tsx   # /signup
      (app)/
        layout.tsx        # app layout with sidebar nav
        dashboard/page.tsx
    
  • Scope middleware-like access control by grouping protected routes.

    app/
      (protected)/
        dashboard/page.tsx
        settings/page.tsx
      (public)/
        page.tsx
        about/page.tsx
    
  • Define a layout per group without affecting the URL structure.

    // app/(auth)/layout.tsx
    export default function AuthLayout({ children }: { children: React.ReactNode }) {
      return <main className="auth-bg">{children}</main>;
    }
    
  • Combine route groups with parallel routes using the @ prefix.

    app/
      (dashboard)/
        @analytics/page.tsx
        @overview/page.tsx
        layout.tsx
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js File Structure
Chapter 05 · Page 39
Beginner

Next.js File Structure

(continued)

Module and Export Patterns

  • Use TypeScript path aliases to avoid deep relative imports across the project.

    // tsconfig.json
    {
      "compilerOptions": {
        "paths": { "@/*": ["./src/*"] }
      }
    }
    
  • Create barrel files with index.ts to expose a clean public API for each feature.

    // features/auth/index.ts
    export { LoginForm } from "./components/LoginForm";
    export { useAuth } from "./hooks/useAuth";
    export type { AuthUser } from "./types";
    
  • Mark server-only modules with the server-only package to prevent client imports.

    // lib/db.ts
    import "server-only";
    export const db = new PrismaClient();
    
  • Use a global types/ folder for shared TypeScript interfaces across features.

    types/
      api.ts      # shared API response shapes
      user.ts     # User interface used app-wide
      post.ts     # Post interface used app-wide
    
  • Re-export shared UI primitives from a single index to keep import paths short.

    app/
      ui/
        Button.tsx
        Card.tsx
        Modal.tsx
        index.ts    # export { Button, Card, Modal }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js File Structure
Chapter 05 · Page 40
Beginner

Next.js File Structure

(continued)

Scaling Patterns

  • Use feature folders to group all files for one domain in one place.

    app/
      features/
        auth/
          page.tsx
          components/
          hooks/
          utils.ts
        posts/
          page.tsx
          components/
          hooks/
    
  • Keep all Next.js special files inside the feature folder.

    features/users/
      page.tsx
      layout.tsx
      error.tsx
      loading.tsx
      components/
        UserCard.tsx
      hooks/
        useUser.ts
    
  • Create barrel files with index.ts to simplify imports from feature folders.

    // features/auth/index.ts
    export { LoginForm } from "./components/LoginForm";
    export { useAuth } from "./hooks/useAuth";
    
  • Place server actions in a dedicated actions/ folder inside each feature.

    features/posts/
      actions/
        createPost.ts
        deletePost.ts
      components/
      page.tsx
    
  • Put cross-feature utilities in a root lib/ folder accessible to all routes.

    lib/
      format.ts      # shared date/number formatting
      fetcher.ts     # shared fetch wrapper
      constants.ts   # app-wide constants
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js File Structure
Chapter 05 · Page 41
Beginner

Next.js File Structure

(FAQ)

FAQ

Page-specific components should live next to the route they belong to, inside the same folder. Shared components used across multiple routes go in a top-level 'components' directory or a '_components' folder inside 'app'.

Folders wrapped in parentheses like '(marketing)' are excluded from the URL path but still organize your file system. Use them to group routes by section or layout without affecting the actual routes users see.

Use the 'app' directory — it's the current standard and enables React Server Components, nested layouts, and streaming. The 'pages' router is still supported but is considered legacy for new projects.

'layout.tsx' persists across navigations and doesn't remount, making it ideal for sidebars and headers. 'template.tsx' creates a fresh instance on every navigation, so use it when you need effects or animations to re-run between routes.

Yes — only files named as special files (page.tsx, layout.tsx, etc.) are treated as routes. You can safely place hooks, utilities, and components alongside your route files and they won't become endpoints.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Font Optimization
Chapter 06 · Page 42
Beginner

Next.js Font Optimization

Load web fonts efficiently with next/font for performance and UX.

TL;DR

  1. 01Use next/font to load Google Fonts automatically.
  2. 02Font files are hosted locally for faster loading.
  3. 03Subset and weight fonts to reduce bundle size.

Tips

  1. 01Use next/font to load Google Fonts — it handles optimization automatically and improves Core Web Vitals.

Warnings

  1. 01Don't load too many font weights or families — each adds overhead. Stick to 2–3 fonts with a few weights each.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Font Optimization
Chapter 06 · Page 43
Beginner

Next.js Font Optimization

(continued)

Google Fonts

  • Import and use Google Fonts with next/font.
    import { Inter } from "next/font/google";
    
    const inter = Inter({ subsets: ["latin"] });
    
    export default function RootLayout({ children }) {
      return (
        <html className={inter.className}>
          <body>{children}</body>
        </html>
      );
    }
  • Fonts are downloaded at build time and hosted locally.
  • No additional network requests for fonts during page loads.
  • Automatic font fallback to prevent layout shifts.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Font Optimization
Chapter 06 · Page 44
Beginner

Next.js Font Optimization

(continued)

Multiple Fonts

  • Use multiple Google Fonts in the same project.
    import { Inter, Playfair_Display } from "next/font/google";
    
    const inter = Inter({ subsets: ["latin"] });
    const playfair = Playfair_Display({
      weight: ["400", "700"],
      subsets: ["latin"]
    });
    
    export default function Page() {
      return (
        <div>
          <h1 className={playfair.className}>Title</h1>
          <p className={inter.className}>Body text</p>
        </div>
      );
    }
  • Apply different fonts to different elements.
  • Reduce bundle size by specifying only needed weights.
  • Use CSS variables to apply fonts globally via Tailwind or CSS.
    const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
    
    export default function RootLayout({ children }) {
      return <html className={inter.variable}>{children}</html>;
    }
    // In CSS: font-family: var(--font-inter);
  • Export fonts from a shared module to avoid duplicate declarations.
    // lib/fonts.ts
    import { Inter, Roboto_Mono } from "next/font/google";
    
    export const inter = Inter({ subsets: ["latin"] });
    export const mono = Roboto_Mono({ subsets: ["latin"] });
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Font Optimization
Chapter 06 · Page 45
Beginner

Next.js Font Optimization

(continued)

Font Weights and Subsets

  • Specify weights to load only what you need.
    const inter = Inter({
      weight: ["400", "500", "700"],
      subsets: ["latin"]
    });
  • Use subsets to reduce font file size.
    const playfair = Playfair_Display({
      subsets: ["latin"],  // Only Latin characters
      display: "swap"      // Show fallback while loading
    });
  • Display property controls fallback behavior.
    const font = Inter({
      display: "swap",     // Show fallback immediately
      // "auto" (default), "block", "fallback", "optional"
    });
  • Use adjustFontFallback to reduce cumulative layout shift further.
    const inter = Inter({
      subsets: ["latin"],
      adjustFontFallback: true // adjusts metrics of fallback font
    });
  • Load a variable font instead of multiple weights for best performance.
    const inter = Inter({
      subsets: ["latin"]
      // Inter is a variable font — no weight array needed
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Font Optimization
Chapter 06 · Page 46
Beginner

Next.js Font Optimization

(continued)

Custom Fonts

  • Load custom fonts from local files.
    import localFont from "next/font/local";
    
    const customFont = localFont({
      src: [
        {
          path: "../fonts/custom.woff2",
          weight: "400",
          style: "normal"
        },
        {
          path: "../fonts/custom-bold.woff2",
          weight: "700",
          style: "normal"
        }
      ]
    });
  • Place font files in the public folder.
    public/
      fonts/
        custom.woff2
        custom-bold.woff2
  • Use the custom font in components.
  • Apply a custom font via className on a wrapper element.
    export default function RootLayout({ children }) {
      return (
        <html className={customFont.className}>
          <body>{children}</body>
        </html>
      );
    }
  • Use a CSS variable with a local font to integrate with Tailwind.
    const customFont = localFont({
      src: "../fonts/custom.woff2",
      variable: "--font-custom"
    });
    // tailwind.config: fontFamily: { brand: ["var(--font-custom)"] }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Font Optimization
Chapter 06 · Page 47
Beginner

Next.js Font Optimization

(continued)

Performance Best Practices

  • Use font display swap to prevent layout shift.
    const inter = Inter({
      display: "swap",  // Shows fallback font immediately
      subsets: ["latin"]
    });
  • Load fonts early in the layout for best results.
    // app/layout.tsx (root layout)
    import { Inter } from "next/font/google";
    
    const inter = Inter();
    
    export default function RootLayout({ children }) {
      return <html className={inter.className}>{children}</html>;
    }
  • Subset fonts to languages you support.
    const inter = Inter({
      subsets: ["latin", "latin-ext"]  // Add extended Latin
    });
  • Measure font impact on Core Web Vitals using web-vitals.
    import { getCLS } from "web-vitals";
    getCLS(console.log); // CLS score should be near 0 with font optimization
  • Use preload: false to skip preloading a font that is not above the fold.
    const mono = Roboto_Mono({
      subsets: ["latin"],
      preload: false // Only loaded when used, not preloaded
    });
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Font Optimization
Chapter 06 · Page 48
Beginner

Next.js Font Optimization

(FAQ)

FAQ

Import the font from next/font/google and call it as a function with your desired options — Next.js automatically downloads the font files at build time and serves them from your own domain, eliminating third-party requests.

Yes, import and instantiate each font separately from next/font/google, then apply their .className or CSS variable to the relevant elements. Keep the total count to 2–3 families to avoid unnecessary payload growth.

A standard @import fetches fonts from Google's CDN at runtime, which adds a render-blocking network request and can hurt Core Web Vitals. next/font self-hosts the files and injects optimized <link> tags with zero layout shift.

Pass weight and subsets arrays when instantiating the font, e.g. weight: ['400', '700'] and subsets: ['latin'] — only those variants are bundled, keeping the font payload as small as possible.

Import localFont from next/font/local and point src at your font file (or an array of files for multiple weights/styles) inside the public or app directory — you get the same automatic optimization as with Google Fonts.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Getting Started
Chapter 07 · Page 49
Beginner

Next.js Getting Started

Set up a Next.js project and start building pages and API routes quickly.

TL;DR

  1. 01Create a new Next.js project using create-next-app command.
  2. 02Pages go in the app folder and routes are file-based.
  3. 03Run npm run dev to start the development server.

Tips

  1. 01Start with create-next-app for fastest setup — it includes all necessary configurations and best practices.

Warnings

  1. 01Remember that files in the app folder automatically become routes — organize carefully to avoid unexpected routes.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Getting Started
Chapter 07 · Page 50
Beginner

Next.js Getting Started

(continued)

Installation

  • Create a new Next.js project.
    npx create-next-app@latest my-app
    cd my-app
    npm run dev
  • Answer prompts to configure TypeScript, ESLint, etc.
  • Dev server runs at http://localhost:3000.
  • Use --ts flag to skip prompts and create a TypeScript project.
    npx create-next-app@latest my-app --ts --app --tailwind
  • Install dependencies and start the dev server in one step.
    npm install && npm run dev
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Getting Started
Chapter 07 · Page 51
Beginner

Next.js Getting Started

(continued)

Project Structure

  • Pages in app folder become routes automatically.
    my-app/
      app/
        page.tsx        # / route
        about/
          page.tsx      # /about route
        blog/
          [slug]/
            page.tsx    # /blog/:slug route
      public/           # Static files
      package.json
  • Each page.tsx file is a route.
  • Place static assets like images in the public folder.
    public/
      logo.png          # /logo.png
      favicon.ico       # /favicon.ico
  • The layout.tsx at the root wraps every page in the app.
    // app/layout.tsx
    export default function RootLayout({ children }) {
      return <html lang="en"><body>{children}</body></html>;
    }
  • Use the app/api folder to create API route handlers.
    // app/api/health/route.ts
    export async function GET() {
      return Response.json({ status: "ok" });
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Getting Started
Chapter 07 · Page 52
Beginner

Next.js Getting Started

(continued)

Creating a Page

  • Create a simple page component.
    // app/page.tsx
    export default function Home() {
      return (
        <main>
          <h1>Welcome to Next.js</h1>
          <p>Get started by editing this page</p>
        </main>
      );
    }
  • Pages are server components by default.
    // app/about/page.tsx
    export default function About() {
      return <h1>About Us</h1>;
    }
  • Fetch data inside a server page using async/await.
    export default async function Blog() {
      const posts = await getPosts();
      return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
    }
  • Add metadata to set the page title and description.
    export const metadata = {
      title: "Home",
      description: "Welcome to my Next.js site"
    };
  • Create a client component for interactive pages.
    "use client";
    import { useState } from "react";
    
    export default function Counter() {
      const [count, setCount] = useState(0);
      return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Getting Started
Chapter 07 · Page 53
Beginner

Next.js Getting Started

(continued)

Building and Deployment

  • Build for production and start the server locally to verify the output.
    npm run build
    npm start
    # Route           Size   First Load JS
    # /               3 kB         87 kB  ← aim for < 130 kB
  • Check the build output table after every build — First Load JS above 130 kB signals bundle bloat.
    npm run build 2>&1 | grep "First Load JS"
    # Flag any route above 130 kB for optimisation
  • Deploy to Vercel in seconds — it auto-detects Next.js and requires no config.
    npm install -g vercel
    vercel  # auto-detected, deployed immediately
  • Set environment variables before the first deployment — .env.local is not deployed.
    vercel env add DATABASE_URL production
    vercel env add NEXT_PUBLIC_API_URL production
  • Set output:standalone in next.config.js when building a Docker image — reduces image size by excluding node_modules.
    // next.config.js
    module.exports = { output: "standalone" };
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Getting Started
Chapter 07 · Page 54
Beginner

Next.js Getting Started

(continued)

Essential Add-ons

  • Add Tailwind CSS for utility-first styling — available as a prompt option in create-next-app.
    npm install -D tailwindcss postcss autoprefixer
    npx tailwindcss init -p
  • Use the Link component for client-side navigation — never use plain <a> tags for internal routes.
    import Link from "next/link";
    
    export default function Nav() {
      return <nav><Link href="/about">About</Link></nav>;
    }
  • Use Prisma as your database ORM — integrates directly with Server Components and Server Actions.
    npm install prisma @prisma/client
    npx prisma init
    npx prisma db push  # sync schema to database
  • Add NextAuth.js or Clerk for authentication — both integrate with the App Router middleware.
    npm install next-auth
    # or
    npm install @clerk/nextjs
  • Use next/image for optimised images — automatically serves WebP and prevents layout shift.
    import Image from "next/image";
    <Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Getting Started
Chapter 07 · Page 55
Beginner

Next.js Getting Started

(FAQ)

FAQ

Run npx create-next-app@latest my-app and follow the interactive prompts to configure TypeScript, ESLint, and Tailwind CSS. The scaffolded project includes the app router, proper folder structure, and all dependencies pre-installed.

The app directory uses the newer App Router with React Server Components by default, while pages uses the legacy Pages Router. New projects should use app — it supports co-located layouts, loading UI, and server actions that the Pages Router lacks.

Create nested folders inside app, and any folder containing a page.tsx file becomes a URL segment. For example, app/dashboard/settings/page.tsx maps to /dashboard/settings, and you can add a shared layout.tsx at any level to wrap child routes.

Run npm run build to generate an optimized production build in the .next folder, then npm start to serve it. For Vercel deployments, pushing to your connected repo triggers both steps automatically with zero configuration.

The App Router only treats a file named exactly page.tsx (or page.js) as a route — other filenames like index.tsx or home.tsx inside a folder are ignored as routes. Make sure the file is named page.tsx and lives inside the app directory, then restart the dev server.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Image Optimization
Chapter 08 · Page 56
Beginner

Next.js Image Optimization

Optimize images in Next.js using the Image component for better performance.

TL;DR

  1. 01Use next/image instead of img tags for automatic optimization.
  2. 02Lazy loading and responsive resizing happen automatically.
  3. 03Specify width and height to prevent layout shift and improve scores.

Tips

  1. 01Always specify width and height — it prevents layout shift and improves performance significantly.

Warnings

  1. 01Don't use Image for images that change dimensions frequently — use regular img tags instead.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Image Optimization
Chapter 08 · Page 57
Beginner

Next.js Image Optimization

(continued)

Basic Image Component

  • Import and use Image component from next/image.
    import Image from 'next/image';
    
    export default function Hero() {
      return (
        <Image
          src="/hero.jpg"
          alt="Hero image"
          width={1200}
          height={600}
        />
      );
    }
  • Always specify width and height for best performance.
  • alt text is required for accessibility.
  • Images are served as WebP automatically for supported browsers.
  • Use the placeholder prop to show a blur while the image loads.
    import Image from 'next/image';
    import heroImg from '@/public/hero.jpg';
    
    <Image
      src={heroImg}
      alt="Hero"
      placeholder="blur" // works with static imports automatically
    />
  • Use blurDataURL to provide a custom blur placeholder for external images.
    <Image
      src="https://cdn.example.com/photo.jpg"
      alt="Photo"
      width={800}
      height={600}
      placeholder="blur"
      blurDataURL="data:image/png;base64,..."
    />
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Image Optimization
Chapter 08 · Page 58
Beginner

Next.js Image Optimization

(continued)

Local vs External Images

  • Local images in public folder.
    import Image from 'next/image';
    import heroImage from '@/public/hero.jpg';
    
    <Image src={heroImage} alt="Hero" />
  • Use remotePatterns to allowlist external image domains.
    // next.config.js
    module.exports = {
      images: {
        remotePatterns: [{ hostname: "cdn.example.com", pathname: "/uploads/**" }]
      }
    };
  • Match multiple domains with separate remotePatterns entries.
    module.exports = {
      images: {
        remotePatterns: [
          { hostname: "cdn.example.com" },
          { hostname: "images.example.com", protocol: "https" }
        ]
      }
    };
  • Static imports infer width, height, and enable blur placeholder automatically.
    import logo from '@/public/logo.png'; // width/height inferred
    <Image src={logo} alt="Logo" />
  • Use unoptimized to skip optimization for SVGs or animated GIFs.
    <Image
      src="/icon.svg"
      alt="Icon"
      width={32}
      height={32}
      unoptimized
    />
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Image Optimization
Chapter 08 · Page 59
Beginner

Next.js Image Optimization

(continued)

Responsive Images

  • Use fill prop for responsive sizing.
    <Image
      src="/hero.jpg"
      alt="Hero"
      fill
      style={{ objectFit: 'cover' }}
    />
  • Specify sizes for responsive images.
    <Image
      src="/card.jpg"
      alt="Card"
      width={300}
      height={200}
      sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
    />
  • The fill image must be inside a positioned container.
    <div style={{ position: "relative", width: "100%", height: "400px" }}>
      <Image src="/banner.jpg" alt="Banner" fill style={{ objectFit: "cover" }} />
    </div>
  • Use objectPosition to control the focal point of a filled image.
    <Image
      src="/portrait.jpg"
      alt="Portrait"
      fill
      style={{ objectFit: "cover", objectPosition: "top center" }}
    />
  • Use sizes to help the browser choose the right srcset variant.
    <Image
      src="/hero.jpg"
      alt="Hero"
      fill
      sizes="100vw" // full-width image
    />
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Image Optimization
Chapter 08 · Page 60
Beginner

Next.js Image Optimization

(continued)

Lazy Loading

  • Lazy loading is automatic by default.
    <Image
      src="/below-fold.jpg"
      alt="Below fold image"
      width={800}
      height={600}
      loading="lazy" // Default behavior
    />
  • Disable lazy loading for above-fold images.
    <Image
      src="/hero.jpg"
      alt="Hero"
      width={1200}
      height={600}
      priority // Load immediately
    />
  • Use priority on the largest above-fold image to improve LCP score.
  • Eager loading loads the image immediately without waiting for viewport.
    <Image
      src="/logo.png"
      alt="Logo"
      width={120}
      height={40}
      loading="eager" // Don't defer
    />
  • Images outside the viewport are deferred until the user scrolls near them.
    <Image
      src="/infographic.png"
      alt="Infographic"
      width={800}
      height={600}
      // loading="lazy" is the default — no need to set it explicitly
    />
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Image Optimization
Chapter 08 · Page 61
Beginner

Next.js Image Optimization

(continued)

Image Quality

  • Control image quality with quality prop.
    <Image
      src="/photo.jpg"
      alt="Photo"
      width={500}
      height={300}
      quality={80} // 1-100, default 75
    />
  • Format automatically optimized for browser.
    // Automatically serves WebP to browsers that support it
    <Image
      src="/photo.jpg"
      alt="Photo"
      width={500}
      height={300}
    />
  • Lower quality to 60–70 for hero images that cover large areas.
    <Image src="/hero.jpg" alt="Hero" fill quality={65} />
    // Good visual quality at smaller file sizes for large backgrounds
  • Configure global image quality defaults in next.config.js.
    module.exports = {
      images: {
        qualities: [75, 90], // only generate these quality variants
        minimumCacheTTL: 86400 // cache optimized images for 1 day
      }
    };
  • Use quality={100} for images where accuracy matters like product photos.
    <Image
      src="/product-detail.png"
      alt="Product"
      width={600}
      height={600}
      quality={100}
    />
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Image Optimization
Chapter 08 · Page 62
Beginner

Next.js Image Optimization

(FAQ)

FAQ

Yes — add the external hostname to the remotePatterns array in next.config.js under the images key. Without this, Next.js will block external image requests for security reasons.

Use the fill prop on the Image component and set the parent element to position: relative with a defined size. Then use sizes prop to hint the browser about the rendered width at different breakpoints for optimal file delivery.

Missing or incorrect sizes prop is the most common culprit — without it, Next.js serves a full-resolution image regardless of display size. Define sizes to match your layout breakpoints so the right image size is requested.

The default quality is 75, which balances file size and visual fidelity for most cases. Pass quality={90} or higher only for images where compression artifacts are noticeable, such as product photos or hero images.

Add the priority prop to images that appear above the fold — like hero banners or LCP candidates — so they are eagerly fetched instead of deferred. Only use it for the first few visible images; overusing it defeats the performance benefit of lazy loading.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Link and Navigation
Chapter 09 · Page 63
Beginner

Next.js Link and Navigation

Use the Link component and useRouter for fast client-side navigation in Next.js.

TL;DR

  1. 01Use Link for client-side navigation instead of <a> tags.
  2. 02Prefetch future pages automatically when links enter the viewport.
  3. 03Use useRouter hook to navigate programmatically after user actions.

Tips

  1. 01Always use Link for internal navigation — it enables better performance and a smoother user experience.

Warnings

  1. 01Avoid using <a> tags for internal links as they cause full page reloads and lose state.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Link and Navigation
Chapter 09 · Page 64
Beginner

Next.js Link and Navigation

(continued)

Link Component

  • Use Link for client-side navigation without page reload.
    import Link from "next/link";
    
    export default function Navigation() {
      return (
        <nav>
          <Link href="/">Home</Link>
          <Link href="/about">About</Link>
          <Link href="/blog">Blog</Link>
        </nav>
      );
    }
  • Link enables faster navigation and better performance.
  • Only the needed data is fetched, not the whole page.
  • Uses client-side rendering for instant transitions.
  • Pass custom className or style props directly to Link.
    <Link href="/contact" className="nav-link">Contact</Link>
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Link and Navigation
Chapter 09 · Page 65
Beginner

Next.js Link and Navigation

(continued)

Dynamic Links

  • Create links with dynamic parameters.
    import Link from "next/link";
    
    export default function PostList({ posts }) {
      return (
        <ul>
          {posts.map(post => (
            <li key={post.id}>
              <Link href={`/blog/${post.slug}`}>
                {post.title}
              </Link>
            </li>
          ))}
        </ul>
      );
    }
  • Use href with objects for typed routes.
    <Link
      href={{
        pathname: "/blog/[slug]",
        query: { slug: post.slug }
      }}
    >
      {post.title}
    </Link>
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Link and Navigation
Chapter 09 · Page 66
Beginner

Next.js Link and Navigation

(continued)

Prefetching

  • Prefetch pages to load them in the background.
    <Link href="/about" prefetch={true}>
      About
    </Link>
  • Prefetching is enabled by default for links in viewport.
  • Disable prefetching for expensive operations.
    <Link href="/api/heavy-data" prefetch={false}>
      Load Data
    </Link>
  • Manually prefetch with router.prefetch().
    import { useRouter } from "next/navigation";
    
    const router = useRouter();
    
    function prefetchAbout() {
      router.prefetch("/about");
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Link and Navigation
Chapter 09 · Page 67
Beginner

Next.js Link and Navigation

(continued)

Programmatic Navigation

  • Use useRouter for navigating with code.
    import { useRouter } from "next/navigation";
    
    export default function LoginForm() {
      const router = useRouter();
      
      async function handleSubmit(e) {
        e.preventDefault();
        // authenticate...
        router.push("/dashboard");
      }
      
      return <form onSubmit={handleSubmit}>...</form>;
    }
  • Router methods: push, replace, back, forward.
    router.push("/new-page");        // Add to history
    router.replace("/new-page");     // Replace history
    router.back();                   // Go back
    router.forward();                // Go forward
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Link and Navigation
Chapter 09 · Page 68
Beginner

Next.js Link and Navigation

(continued)

Active Links

  • Highlight current page in navigation.
    "use client";
    
    import { usePathname } from "next/navigation";
    import Link from "next/link";
    
    export default function Navigation() {
      const pathname = usePathname();
      
      return (
        <nav>
          <Link
            href="/"
            className={pathname === "/" ? "active" : ""}
          >
            Home
          </Link>
          <Link
            href="/about"
            className={pathname === "/about" ? "active" : ""}
          >
            About
          </Link>
        </nav>
      );
    }
  • Use usePathname to get the current route.
  • Compare pathname to set active classes.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Link and Navigation
Chapter 09 · Page 69
Beginner

FAQ

FAQ

FAQ

FAQ

FAQ

FAQ

Server Action Error Handling

Global Error Handling

FAQ

Locale Detection

FAQ

Useful Cheatsheetsusefulcheatsheets.com
Next.js Metadata and SEO
Chapter 20 · Page 140
Intermediate

Next.js Metadata and SEO

Generate metadata, open graph tags, and improve SEO automatically.

TL;DR

  1. 01Export metadata constant to set page titles and descriptions.
  2. 02Use generateMetadata for dynamic metadata from data.
  3. 03Use Open Graph tags for social media sharing.

Tips

  1. 01Use generateMetadata with dynamic data to create unique, SEO-friendly titles and descriptions for each page.

Warnings

  1. 01Always include Open Graph images with correct dimensions (1200x630) to ensure proper display on social media.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Metadata and SEO
Chapter 20 · Page 141
Intermediate

Next.js Metadata and SEO

(continued)

Static Metadata

  • Export metadata constant for static pages.
    import { Metadata } from "next";
    
    export const metadata: Metadata = {
      title: "About Us",
      description: "Learn more about our company and mission.",
      keywords: ["about", "company", "mission"]
    };
    
    export default function About() {
      return <div>About content</div>;
    }
  • Metadata sets HTML head tags automatically.
  • Improves SEO and social media sharing.
  • Use a layout-level metadata object to apply defaults across all pages.
    // app/layout.tsx
    export const metadata: Metadata = {
      title: { default: "My Site", template: "%s | My Site" },
      description: "Default description for all pages."
    };
  • Child page metadata merges with and overrides layout metadata.
    // app/about/page.tsx
    export const metadata: Metadata = {
      title: "About" // becomes "About | My Site" via template
    };
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Metadata and SEO
Chapter 20 · Page 142
Intermediate

Next.js Metadata and SEO

(continued)

Dynamic Metadata

  • Generate metadata from data or parameters.
    import { Metadata } from "next";
    
    export async function generateMetadata({
      params
    }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
      const { slug } = await params; // params is a Promise in Next.js 15
      const post = await getPost(slug);
      
      return {
        title: post.title,
        description: post.excerpt,
        authors: [{ name: post.author }]
      };
    }
    
    export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
      const { slug } = await params;
      const post = await getPost(slug);
      return <article>{post.content}</article>;
    }
  • generateMetadata runs on the server.
  • Has access to params and other request data.
  • Fetch and cache data inside generateMetadata to avoid duplicate requests.
    export async function generateMetadata({ params }): Promise<Metadata> {
      const product = await getProduct(params.id); // Next.js deduplicates this fetch
      return { title: product.name, description: product.description };
    }
    export default async function Page({ params }) {
      const product = await getProduct(params.id); // same fetch, cached
      return <ProductDetail product={product} />;
    }
  • Return notFound() inside generateMetadata to trigger a 404 page early.
    export async function generateMetadata({ params }): Promise<Metadata> {
      const post = await getPost(params.slug);
      if (!post) notFound();
      return { title: post.title };
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Metadata and SEO
Chapter 20 · Page 143
Intermediate

Next.js Metadata and SEO

(continued)

Open Graph Tags

  • Set Open Graph tags for social sharing.
    export const metadata: Metadata = {
      title: "My Post",
      description: "Read my latest blog post",
      openGraph: {
        title: "My Post",
        description: "Read my latest blog post",
        url: "https://example.com/blog/my-post",
        siteName: "My Blog",
        images: [
          {
            url: "https://example.com/og-image.png",
            width: 1200,
            height: 630
          }
        ],
        type: "article"
      }
    };
  • Open Graph improves how links look on social media.
  • Include images for better engagement.
  • Use dynamic OG images with Next.js ImageResponse for per-page previews.
    // app/og/route.tsx
    import { ImageResponse } from "next/og";
    export async function GET(req: Request) {
      const { searchParams } = new URL(req.url);
      return new ImageResponse(<div>{searchParams.get("title")}</div>);
    }
  • Reference the dynamic OG route in your metadata image field.
    openGraph: {
      images: [`/og?title=${encodeURIComponent(post.title)}`]
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Metadata and SEO
Chapter 20 · Page 144
Intermediate

Next.js Metadata and SEO

(continued)

Twitter Card Tags

  • Add Twitter Card metadata for tweets.
    export const metadata: Metadata = {
      title: "My Post",
      description: "Read my latest blog post",
      twitter: {
        card: "summary_large_image",
        title: "My Post",
        description: "Read my latest blog post",
        images: ["https://example.com/og-image.png"],
        creator: "@myhandle"
      }
    };
  • Twitter Cards make tweets more engaging.
  • Use summary_large_image for best results.
  • Set twitter.site to your handle for attribution on shared links.
    twitter: {
      card: "summary_large_image",
      site: "@mycompany",
      creator: "@authorhandle"
    }
  • Reuse the same image URL for both OG and Twitter to reduce duplication.
    const ogImage = "https://example.com/og-image.png";
    export const metadata: Metadata = {
      openGraph: { images: [ogImage] },
      twitter: { images: [ogImage] }
    };
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Metadata and SEO
Chapter 20 · Page 145
Intermediate

Next.js Metadata and SEO

(continued)

Structured Data

  • Add JSON-LD structured data for search engines.
    export default async function BlogPost({
      params
    }: { params: Promise<{ slug: string }> }) {
      const { slug } = await params;
      const post = await getPost(slug);
      
      const schema = {
        "@context": "https://schema.org",
        "@type": "BlogPosting",
        headline: post.title,
        description: post.excerpt,
        image: post.image,
        author: {
          "@type": "Person",
          name: post.author
        },
        datePublished: post.date
      };
      
      return (
        <>
          <script
            type="application/ld+json"
            dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
          />
          <article>{post.content}</article>
        </>
      );
    }
  • Structured data helps search engines understand content.
  • Use schema.org types for consistency.
  • Add BreadcrumbList schema to help search engines show site hierarchy.
    const breadcrumb = {
      "@context": "https://schema.org",
      "@type": "BreadcrumbList",
      itemListElement: [
        { "@type": "ListItem", position: 1, name: "Home", item: "/" },
        { "@type": "ListItem", position: 2, name: "Blog", item: "/blog" }
      ]
    };
  • Validate structured data with Google's Rich Results Test tool before deploying.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Metadata and SEO
Chapter 20 · Page 146
Intermediate

Next.js Metadata and SEO

(FAQ)

FAQ

Export an async generateMetadata function from your page component that receives params and searchParams, fetches any needed data, and returns a metadata object with title and description fields. This runs server-side and generates unique metadata per route.

The exported metadata constant is for static values known at build time, while generateMetadata is an async function for values that depend on dynamic data like route params or database fetches. Use metadata when the values never change, generateMetadata when they do.

Include an openGraph object inside your metadata export or generateMetadata return, with fields like title, description, url, and images. The images array should contain objects with url, width: 1200, and height: 630 for correct social previews.

Render a <script type='application/ld+json'> tag inside your page component using JSX, with dangerouslySetInnerHTML set to __html: JSON.stringify(yourSchemaObject). Place it in the component's return alongside your content, not inside the metadata export.

Yes, add a twitter object to your metadata with fields like card, title, description, and images. If omitted, many platforms fall back to Open Graph values, but explicitly setting twitter gives you control over how your content appears specifically on X/Twitter.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Middleware
Chapter 21 · Page 147
Intermediate

Next.js Middleware

Use Next.js middleware for redirects, auth checks, headers, and request rewriting in the App Router.

TL;DR

  1. 01Create a middleware.ts file at the project root to intercept requests.
  2. 02Use middleware to check auth, redirect, or modify headers.
  3. 03Middleware runs in the Edge Runtime — keep logic fast, Node.js built-ins are unavailable.

Tips

  1. 01Use the <code>matcher</code> config to narrow which routes trigger middleware, since it runs before every request and impacts performance.
  2. 02Next.js 16 renames <code>middleware.ts</code> to <code>proxy.ts</code> going forward — the old filename still works but is deprecated, so plan to migrate.

Warnings

  1. 01Middleware runs for every request, so expensive operations will slow your app down — keep logic fast and simple.
  2. 02Middleware also runs on every matched prefetch, not just full navigations, so expensive checks can fire far more often than you expect.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Middleware
Chapter 21 · Page 148
Intermediate

Next.js Middleware

(continued)

File and Runtime Setup

  • Want to run logic before every route renders, with zero changes to your pages? Create one middleware.ts file at the root of your project, next to the app directory.
    // middleware.ts
    import { NextRequest, NextResponse } from "next/server";
    
    export function middleware(request: NextRequest) {
      return NextResponse.next();
    }
  • Middleware runs on every request before it reaches your routes.
  • Use the matcher export to specify which paths trigger the middleware.
    export const config = {
      matcher: ['/api/:path*', '/admin/:path*']
    };
  • Return NextResponse.next() to continue to the next handler.
  • Middleware runs in the Edge Runtime — a lightweight V8-based environment, not full Node.js. Most Node.js built-in modules (fs, path, crypto) are unavailable.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Middleware
Chapter 21 · Page 149
Intermediate

Next.js Middleware

(continued)

Authentication Checks

  • Skip writing auth checks into every protected page — check for a token in cookies once, in middleware, and redirect if it's missing.
    export function middleware(request: NextRequest) {
      const token = request.cookies.get('auth')?.value;
      if (!token && request.nextUrl.pathname.startsWith('/admin')) {
        return NextResponse.redirect(new URL('/login', request.url));
      }
      return NextResponse.next();
    }
  • Verify JWT tokens or check session validity before route access.
  • Redirect unauthenticated users to a login page automatically.
  • Use middleware to protect entire route segments without code in each page.
  • Combine with headers to check authentication across the app.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Middleware
Chapter 21 · Page 150
Intermediate

Next.js Middleware

(continued)

Redirects and Rewrites

  • A redirect changes the URL bar, a rewrite doesn't — send users to a different URL using NextResponse.redirect() when you want that visible change.
    if (request.nextUrl.pathname === '/old-page') {
      return NextResponse.redirect(new URL('/new-page', request.url));
    }
  • Rewrite the request internally without changing the user's URL.
    if (request.nextUrl.pathname.startsWith('/api')) {
      return NextResponse.rewrite(new URL('/api-v2/handler', request.url));
    }
  • Use redirects for public URL changes and SEO-friendly migrations.
  • Use rewrites to hide internal routing or proxy external APIs.
  • Both preserve the original URL in the browser when rewriting.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Middleware
Chapter 21 · Page 151
Intermediate

Next.js Middleware

(continued)

Custom Headers

  • Need a security header on every single response without touching each route handler? Add or modify headers once in middleware.
    export function middleware(request: NextRequest) {
      const response = NextResponse.next();
      response.headers.set('X-Custom-Header', 'value');
      return response;
    }
  • Add security headers like CORS, CSP, or rate-limiting headers.
  • Read incoming headers to customize behavior based on the request.
    const userAgent = request.headers.get('user-agent');
  • Middleware runs before routes see the request, so headers apply everywhere.
  • Use response headers to control caching and security policies.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Middleware
Chapter 21 · Page 152
Intermediate

Next.js Middleware

(continued)

Geolocation and Advanced Patterns

  • Middleware can branch on more than the URL path — check locale or country from the request and redirect to the right version.
    const locale = request.headers.get('accept-language')?.split(',')[0].split('-')[0];
    if (locale === 'es') {
      return NextResponse.redirect(new URL('/es' + request.nextUrl.pathname, request.url));
    }
  • request.geo and request.ip were removed from NextRequest in Next.js 15 — use the geolocation() helper from @vercel/functions to route based on the user's country instead.
    import { geolocation } from '@vercel/functions';
    
    export function middleware(request: NextRequest) {
      const { country } = geolocation(request);
      if (country === 'DE') {
        return NextResponse.redirect(new URL('/de', request.url));
      }
    }
  • Rate limit requests based on IP address or user ID — use ipAddress() from @vercel/functions now that request.ip is gone.
    import { ipAddress } from '@vercel/functions';
    
    const ip = ipAddress(request) ?? request.headers.get('x-forwarded-for') ?? 'unknown';
    // Check rate limit store keyed by ip...
  • Combine multiple checks in sequence for layered security.
  • Keep middleware logic fast — it runs on the Edge Runtime before every matched request and adds latency proportional to its execution time.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Middleware
Chapter 21 · Page 153
Intermediate

Next.js Middleware

(FAQ)

FAQ

Place middleware.ts (or middleware.js) in the root of your project, at the same level as your app or pages directory. Only one middleware file is supported per project.

Export a matcher config from your middleware file to target specific paths, e.g. export const config = { matcher: ['/dashboard/:path*', '/api/protected/:path*'] }. Without a matcher, middleware runs on every single request including static assets.

Read your auth token from cookies or headers using request.cookies.get() or request.headers.get(), then return NextResponse.redirect(new URL('/login', request.url)) if the check fails.

Yes — use NextResponse.next() and call response.headers.set('X-Custom-Header', 'value') before returning it. This is useful for adding security headers like Content-Security-Policy or X-Frame-Options globally.

A redirect sends the browser to a new URL (visible in the address bar), while a rewrite proxies the request to a different path internally without changing the URL the user sees. Use NextResponse.rewrite() for URL masking and NextResponse.redirect() when you want the client to navigate.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Server Components
Chapter 22 · Page 154
Intermediate

Next.js Server Components

A practical guide to server components, the use client directive, and rendering boundaries.

TL;DR

  1. 01Server components run on the server and send HTML to the browser.
  2. 02Use client components with the "use client" directive for interactivity.
  3. 03Mix both types to optimize performance and security.

Tips

  1. 01Keep the "use client" boundary as low as possible in the tree to maximize server rendering and minimize the client bundle size.
  2. 02Use the <code>server-only</code> and <code>client-only</code> packages together so the build fails fast if a module crosses the wrong boundary.

Warnings

  1. 01Never include environment secrets in client components, even as props, since they become visible in the browser bundle and HTML.
  2. 02Adding <code>&quot;use client&quot;</code> too high in the tree makes every component it imports client-side too, silently bloating the JavaScript bundle.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Server Components
Chapter 22 · Page 155
Intermediate

Next.js Server Components

(continued)

Rendering Model

  • A Server Component never sends JSX or markdown to the browser — React serializes its output into a streamable RSC payload, not regular JSON.
    // app/page.tsx — runs entirely on the server, no client JS for this tree
    export default async function Page() {
      const post = await db.post.findFirst();
      return <article>{post.title}</article>;
    }
    
  • The payload uses the React Flight protocol: each chunk maps to one resolved component, so the client renders pieces as they arrive instead of waiting for the whole tree.
  • Client Components referenced inside the payload show up as module references the browser fetches separately, keeping server-only code out of that bundle.
  • Because rendering happens once on the server, Server Components can be async functions and await data directly — there is no hydration step for their output.
  • Nothing in a Server Component re-executes in the browser, so state, refs, and lifecycle hooks have no meaning there.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Server Components
Chapter 22 · Page 156
Intermediate

Next.js Server Components

(continued)

Server vs Client Tradeoffs

  • Every component starts as a Server Component until something forces it to the client — so the real question is what justifies that cost, not how to flip the switch.
    // Stays a Server Component: no interactivity, no hooks needed
    export default async function PriceTag({ productId }) {
      const price = await getPrice(productId);
      return <span>${price}</span>;
    }
    
  • Choose a Client Component only when the UI needs useState, useEffect, event handlers, or browser-only APIs like localStorage.
  • Server Components shrink the JavaScript bundle because their code and dependencies never ship — a heavy markdown parser or date library used only on the server costs the client nothing.
  • Server Components can read environment secrets and query databases directly; Client Components can only see what is explicitly passed to them as props.
  • A page can mix both freely — the tradeoff is per-component, not per-page, so isolate interactivity into the smallest possible Client Component.
    // Only LikeButton ships JS; the rest of the page stays server-rendered
    import LikeButton from "./LikeButton"; // "use client" lives inside this file
    export default async function Post({ id }) {
      const post = await getPost(id);
      return <article>{post.body}<LikeButton postId={id} /></article>;
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Server Components
Chapter 22 · Page 157
Intermediate

Next.js Server Components

(continued)

Composition Boundaries

  • A Client Component can render a Server Component without importing it — pass it through children or another prop instead.
    // ClientShell.tsx ("use client") never imports ServerSidebar directly
    export default function ClientShell({ children }) {
      return <div className="shell">{children}</div>;
    }
    // app/page.tsx (Server Component) composes them together
    <ClientShell><ServerSidebar /></ClientShell>
    
  • The nested Server Component still renders on the server first — only its finished output crosses into the Client Component as a slot.
  • A Client Component can never import a Server Component file directly; that import would pull server code into the client module graph and fail to build.
  • Props flowing from a Server Component into a Client Component must be plain serializable values: strings, numbers, arrays, dates, and plain objects.
    // OK — every value below survives the server-to-client boundary
    <ClientChart data={rows} title="Sales" updatedAt={new Date()} />
    // Not OK — functions and class instances cannot cross as props
    // <ClientChart onSort={sortRows} />
    
  • This children-as-slot pattern is the main way teams keep a heavy data-fetching tree server-rendered while still nesting it inside an interactive shell.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Server Components
Chapter 22 · Page 158
Intermediate

Next.js Server Components

(continued)

Protecting Server-Only Code

  • A Server Component's database calls and API keys are invisible to the browser by default — but only if nothing pulls that module into a Client Component's import graph.
    import "server-only";
    // Any client-side import of this file now fails at build time, not runtime
    export async function getSecretData() {
      return db.query("SELECT * FROM secrets");
    }
    
  • A single accidental import of a server-only utility from a Client Component silently ships that code, and anything it touches, to every visitor's browser.
  • The server-only package converts that silent leak into a build error, so the mistake is caught in CI instead of in production.
  • Pair it with client-only in browser-specific utilities so the two packages catch boundary mistakes in both directions.
  • Treat any value passed as a prop to a Client Component as public — never pass raw secrets, tokens, or full database rows; pass only the sanitized fields the UI needs.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Server Components
Chapter 22 · Page 159
Intermediate

Next.js Server Components

(continued)

Caching Server Output

  • Server Components re-run on every request by default — without caching, a page with five database calls makes five fresh queries on every visit.
    import { cacheLife, cacheTag } from "next/cache";
    async function getPost(slug: string) {
      "use cache"; // marks this function's result as cacheable
      cacheTag(`post-${slug}`);
      cacheLife("hours");
      return db.post.findUnique({ where: { slug } });
    }
    
  • The use cache directive is the current recommended way to cache fetches, components, or whole routes, replacing the older unstable_cache API for new code.
  • Revalidate a specific cache entry on demand with revalidateTag("post-my-slug") after a mutation, instead of waiting for cacheLife to expire.
  • fetch calls still support the older { next: { revalidate: 3600 } } option directly, which works without enabling the use cache directive.
    const res = await fetch("https://api.example.com/data", { next: { revalidate: 3600 } });
    
  • Wrap slow server queries in <Suspense> so the rest of the page streams in while one section is still loading.
    <Suspense fallback={<p>Loading…</p>}>
      <SlowServerSection />
    </Suspense>
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Server Components
Chapter 22 · Page 160
Intermediate

Next.js Server Components

(FAQ)

FAQ

Yes — server components run in a Node.js environment and can call your database, ORM, or internal services directly using async/await. You only need an API route when the client itself needs to fetch data after the initial page load.

Add "use client" as the very first line of the file to make it a client component. Only components that actually need browser APIs, event listeners, or React hooks need this directive — keep it scoped to the smallest component possible.

Pass the data as props from the server component down to the client component — but only serialize-safe values like strings, numbers, and plain objects. Functions, class instances, and Promises cannot cross the server-client boundary as props.

Any module imported by a client component (directly or transitively) gets bundled for the browser. Use the server-only package in files that must stay server-side — it throws a build-time error if that module is ever pulled into the client graph.

You can't import a server component inside a client component, but you can pass one as children or a prop — the server component still renders on the server, and only its output HTML is sent to the client component as a slot. This is the key pattern for keeping heavy server logic out of the client bundle.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Testing
Chapter 23 · Page 161
Intermediate

Next.js Testing

Test Next.js apps with Jest, React Testing Library, and E2E testing with Playwright or Cypress.

TL;DR

  1. 01Use Jest for unit and integration tests of functions and components.
  2. 02Use React Testing Library to test components from the user perspective.
  3. 03Use Playwright or Cypress for end-to-end testing full user workflows.

Tips

  1. 01Aim for a test pyramid: many unit tests, some integration tests, and a few critical E2E tests to balance speed and confidence.
  2. 02Use Mock Service Worker (MSW) to intercept network requests in integration-style tests, since it mimics real API behavior more closely than manual <code>jest.mock</code> calls.

Warnings

  1. 01Avoid testing implementation details and focus on user behavior, since refactored code will break tests that depend on internal structure.
  2. 02E2E tests that wait on fixed timeouts instead of specific conditions or selectors become flaky, since real network and render timing varies between runs.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Testing
Chapter 23 · Page 162
Intermediate

Next.js Testing

(continued)

Setup and Jest Basics

  • Next.js doesn't ship a test runner out of the box — install Jest and configure it with the built-in Next.js preset to get started.
    npm install --save-dev jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom
  • Create jest.config.js using the built-in Next.js Jest preset.
    const nextJest = require("next/jest");
    const createJestConfig = nextJest({ dir: "./" });
    module.exports = createJestConfig({ testEnvironment: "jsdom" });
  • Create test files with .test.js or .spec.js extensions.
    // utils.test.js
    import { add } from "./utils";
    it("adds two numbers", () => {
      expect(add(2, 3)).toBe(5);
    });
  • Run tests in watch mode to re-run on file changes.
    npm test -- --watch
  • Use describe to group related tests and it for individual test cases. Jest runs tests in parallel by default.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Testing
Chapter 23 · Page 163
Intermediate

Next.js Testing

(continued)

Testing Components

  • Tests that check internal state break on every refactor — import React Testing Library to test components the way a user actually sees them.
    import { render, screen } from "@testing-library/react";
    import Button from "./Button";
    
    it("renders a button", () => {
      render(<Button>Click me</Button>);
      const btn = screen.getByRole("button", { name: /click me/i });
      expect(btn).toBeInTheDocument();
    });
  • Query elements using semantic methods like getByRole, getByText.
    screen.getByRole("button", { name: /submit/i });
    screen.getByLabelText("Email");
    screen.getByText("Welcome");
  • Test user interactions with userEvent instead of fireEvent.
    import userEvent from "@testing-library/user-event";
    const user = userEvent.setup();
    await user.click(button);
  • Avoid testing implementation details, focus on what users see and do.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Testing
Chapter 23 · Page 164
Intermediate

Next.js Testing

(continued)

Testing API Routes

  • No server needed to test an API route — create separate test files that call route handlers directly with mocked request and response objects.
    import { createMocks } from "node-mocks-http";
    import handler from "./api/hello";
    
    it("returns a greeting", async () => {
      const { req, res } = createMocks({
        method: "GET"
      });
      
      await handler(req, res);
      expect(res._getStatusCode()).toBe(200);
      expect(JSON.parse(res._getData())).toHaveProperty("message");
    });
  • Use a library like node-mocks-http to mock request and response.
  • Test different HTTP methods and status codes.
  • Mock dependencies like databases or external APIs.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Testing
Chapter 23 · Page 165
Intermediate

Next.js Testing

(continued)

Mocking and Fixtures

  • A flaky network call shouldn't fail your test suite — mock external API calls to keep tests fast and isolated.
    jest.mock("./api", () => ({
      fetchUser: jest.fn(() => Promise.resolve({ id: 1, name: "Alice" }))
    }));
  • Use fixtures to provide consistent test data.
    const mockUser = { id: 1, name: "Alice", email: "alice@example.com" };
    
    it("displays user info", () => {
      render(<Profile user={mockUser} />);
      expect(screen.getByText("Alice")).toBeInTheDocument();
    });
  • Mock the App Router navigation module for components using useRouter or usePathname.
    jest.mock("next/navigation", () => ({
      useRouter: jest.fn(() => ({ push: jest.fn(), replace: jest.fn() })),
      usePathname: jest.fn(() => "/"),
      useSearchParams: jest.fn(() => new URLSearchParams())
    }));
  • Mocking prevents external dependencies from slowing tests.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Testing
Chapter 23 · Page 166
Intermediate

Next.js Testing

(continued)

E2E Testing

  • Unit tests can't catch a broken checkout flow across pages — use Playwright or Cypress for end-to-end testing of full workflows.
    // playwright.spec.ts
    import { test, expect } from "@playwright/test";
    
    test("user can sign up", async ({ page }) => {
      await page.goto("http://localhost:3000");
      await page.fill("input[name=email]", "test@example.com");
      await page.click("button:has-text('Sign Up')");
      await expect(page).toHaveURL("/dashboard");
    });
  • E2E tests run in a real browser and test the entire application.
  • Run E2E tests against a deployed environment before releasing.
  • E2E tests are slower but catch integration issues that unit tests miss.
    npx playwright test
  • Use E2E tests for critical user paths like checkout or login.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Testing
Chapter 23 · Page 167
Intermediate

Next.js Testing

(FAQ)

FAQ

Install jest, jest-environment-jsdom, @testing-library/react, and @testing-library/jest-dom, then add a jest.config.js using Next.js's built-in preset: const nextJest = require('next/jest'); module.exports = nextJest({ dir: './' })({testEnvironment: 'jsdom'}).

Both work well, but Playwright is generally preferred for new projects due to faster execution, built-in multi-browser support, and better handling of Next.js server components. Cypress has a more mature ecosystem and better real-time debugging.

Import the handler function directly and call it with mocked req/res objects using libraries like node-mocks-http, then assert on res.status() and res.json() values without spinning up a server.

This usually means the element is conditionally rendered or loaded asynchronously—use async queries like findByText or waitFor instead of getByText so the test waits for the DOM to update after data fetching or state changes.

Mock the next/navigation module (App Router) or next/router (Pages Router) using jest.mock: jest.mock('next/navigation', () => ({ useRouter: () => ({ push: jest.fn(), pathname: '/' }) })) at the top of your test file.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Analytics and Monitoring
Chapter 24 · Page 168
Advanced

Next.js Analytics and Monitoring

Integrate analytics, error tracking, and performance monitoring into your Next.js app.

TL;DR

  1. 01Use web-vitals library to measure Core Web Vitals automatically.
  2. 02Integrate Sentry to capture and track errors in production.
  3. 03Add Google Analytics to monitor user behavior and page views.

Tips

  1. 01Use web-vitals and Sentry together for complete visibility into user experience and error rates.

Warnings

  1. 01Be mindful of privacy when tracking user data — follow GDPR and CCPA regulations when storing user information.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Analytics and Monitoring
Chapter 24 · Page 169
Advanced

Next.js Analytics and Monitoring

(continued)

Web Vitals

  • Use useReportWebVitals from next/vitals to capture Core Web Vitals in App Router.
    "use client";
    import { useReportWebVitals } from "next/vitals";
    
    export function WebVitals() {
      useReportWebVitals((metric) => {
        console.log(metric); // { name, value, id, ... }
      });
      return null;
    }
    // Place <WebVitals /> in app/layout.tsx
  • Core Web Vitals: CLS, INP (replaced FID), LCP, FCP, TTFB.
  • Essential metrics for user experience and Google SEO ranking.
  • Send vitals to a custom analytics endpoint.
    import { getCLS, getINP, getLCP } from "web-vitals";
    
    function sendToAnalytics({ name, value, id }) {
      fetch("/api/vitals", {
        method: "POST",
        body: JSON.stringify({ name, value, id })
      });
    }
    
    getCLS(sendToAnalytics);
    getINP(sendToAnalytics);
    getLCP(sendToAnalytics);
  • For Pages Router, export reportWebVitals from pages/_app.js.
    // pages/_app.js — Pages Router only
    export function reportWebVitals(metric) {
      console.log(metric); // { name, value, id, startTime, ... }
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Analytics and Monitoring
Chapter 24 · Page 170
Advanced

Next.js Analytics and Monitoring

(continued)

Error Tracking with Sentry

  • Integrate Sentry for error monitoring.
    npm install @sentry/nextjs
  • Set up Sentry in your Next.js config.
    // next.config.js
    const { withSentryConfig } = require("@sentry/nextjs");
    
    module.exports = withSentryConfig({
      // your Next.js config
    }, {
      org: "your-org",
      project: "your-project",
      authToken: process.env.SENTRY_AUTH_TOKEN
    });
  • Capture errors automatically on client and server.
    import * as Sentry from "@sentry/nextjs";
    
    Sentry.captureException(error);
  • Capture exceptions inside error boundaries manually.
    // app/error.tsx
    "use client";
    import * as Sentry from "@sentry/nextjs";
    import { useEffect } from "react";
    
    export default function Error({ error, reset }) {
      useEffect(() => {
        Sentry.captureException(error);
      }, [error]);
      return <button onClick={reset}>Try again</button>;
    }
  • Add user context to Sentry for better debugging.
    Sentry.setUser({ id: user.id, email: user.email });
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Analytics and Monitoring
Chapter 24 · Page 171
Advanced

Next.js Analytics and Monitoring

(continued)

Google Analytics

  • Add Google Analytics for user tracking.
    // app/layout.tsx
    import Script from "next/script";
    
    export default function RootLayout({ children }) {
      return (
        <html>
          <head>
            <Script
              src="https://www.googletagmanager.com/gtag/js?id=GA_ID"
              strategy="afterInteractive"
            />
            <Script id="google-analytics" strategy="afterInteractive">
              {`
                window.dataLayer = window.dataLayer || [];
                function gtag(){dataLayer.push(arguments);}
                gtag('js', new Date());
                gtag('config', 'GA_ID');
              `}
            </Script>
          </head>
          <body>{children}</body>
        </html>
      );
    }
  • Track page views and custom events.
    function trackEvent(name: string) {
      window.gtag('event', name);
    }
  • Track page views on route changes in App Router.
    "use client";
    import { usePathname } from "next/navigation";
    import { useEffect } from "react";
    
    export function Analytics() {
      const pathname = usePathname();
      useEffect(() => {
        window.gtag?.("event", "page_view", { page_path: pathname });
      }, [pathname]);
      return null;
    }
  • Track ecommerce events for purchases and conversions.
    function trackPurchase(order) {
      window.gtag("event", "purchase", {
        transaction_id: order.id,
        value: order.total,
        currency: "USD"
      });
    }
  • Use Google Tag Manager for managing multiple scripts.
    <Script
      src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXX"
      strategy="afterInteractive"
    />
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Analytics and Monitoring
Chapter 24 · Page 172
Advanced

Next.js Analytics and Monitoring

(continued)

Custom Metrics

  • Measure custom metrics with events.
    // Track API response times
    export async function POST(request: Request) {
      const start = performance.now();
      
      try {
        const result = await processRequest(request);
        const duration = performance.now() - start;
        
        // Log metric
        console.log(`Request took ${duration}ms`);
        
        return Response.json(result);
      } catch (error) {
        Sentry.captureException(error);
        return Response.json({ error: "Failed" }, { status: 500 });
      }
    }
  • Send metrics to monitoring service.
    async function recordMetric(name: string, value: number) {
      await fetch("/api/metrics", {
        method: "POST",
        body: JSON.stringify({ name, value })
      });
    }
  • Store custom metrics in a database via API route.
    // app/api/metrics/route.ts
    export async function POST(request: Request) {
      const { name, value } = await request.json();
      await db.metric.create({ data: { name, value, at: new Date() } });
      return Response.json({ ok: true });
    }
  • Track user interactions like button clicks or searches.
    function trackSearch(query: string) {
      fetch("/api/metrics", {
        method: "POST",
        body: JSON.stringify({ name: "search", value: query.length })
      });
    }
  • Use the Performance API to measure render times.
    performance.mark("component-start");
    // ... render
    performance.mark("component-end");
    performance.measure("component", "component-start", "component-end");
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Analytics and Monitoring
Chapter 24 · Page 173
Advanced

Next.js Analytics and Monitoring

(continued)

Performance Monitoring

  • Use Lighthouse CI to monitor performance.
    npm install -g @lhci/cli@*
    lhci autorun
  • Monitor bundle size with package.json scripts.
    {
      "scripts": {
        "analyze": "ANALYZE=true npm run build"
      }
    }
  • Set up performance budgets.
    // next.config.js
    const withBundleAnalyzer = require("@next/bundle-analyzer")({
      enabled: process.env.ANALYZE === "true"
    });
  • Run Lighthouse CI in a GitHub Actions workflow.
    - name: Run Lighthouse CI
      run: lhci autorun
      env:
        LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_TOKEN }}
  • Set performance score thresholds to fail CI on regressions.
    {
      "assert": {
        "assertions": {
          "categories:performance": ["error", { "minScore": 0.9 }]
        }
      }
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Analytics and Monitoring
Chapter 24 · Page 174
Advanced

Next.js Analytics and Monitoring

(FAQ)

FAQ

In the App Router, import useReportWebVitals from next/vitals and call it inside a Client Component placed in your root layout — Next.js passes each metric (CLS, INP, LCP, FCP, TTFB) to your callback. For the Pages Router, export a reportWebVitals function from pages/_app.js instead.

Install @sentry/nextjs and run npx @sentry/wizard@latest -i nextjs to auto-configure sentry.client.config.js, sentry.server.config.js, and sentry.edge.config.js. The wizard also wraps your next.config.js with withSentryConfig to enable source map uploads for readable stack traces.

Yes — create a client component that loads the gtag script via next/script with strategy='afterInteractive' and place it in your root layout. Call gtag('event', ...) for custom events, and use usePathname with a useEffect to fire page view events on route changes.

reportWebVitals captures browser-measured paint and layout metrics (CLS, LCP, etc.) that reflect perceived page speed, while Sentry performance monitoring traces request durations, API calls, and server-side spans across the full stack. Use both together to correlate slow backend operations with poor front-end user experience scores.

Use the performance.mark() and performance.measure() Web APIs in client components to instrument specific interactions, then read results with performance.getEntriesByType('measure') and send them to your own /api/metrics endpoint. For server-side timing, use Date.now() around data-fetching logic inside Server Components or Route Handlers and log to your observability backend.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 175
Advanced

Next.js Performance Optimization

Profile apps, optimize bundle size, and implement advanced caching and streaming.

TL;DR

  1. 01Use dynamic imports to split code and reduce bundles.
  2. 02Enable compression and minification in production.
  3. 03Implement streaming for faster first contentful paint.

Tips

  1. 01PPR works best for pages where the majority of content is static but a small slice (cart count, user greeting, recommendations) is dynamic. Pages that are fully dynamic benefit more from standard SSR.

Warnings

  1. 01Never use a <link rel="preconnect"> to Google Fonts alongside next/font/google — next/font downloads and self-hosts the font at build time so the Google CDN is never hit in production.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 176
Advanced

Next.js Performance Optimization

(continued)

Bundle Size Analysis

  • Check bundle size with @next/bundle-analyzer.
    npm install -D @next/bundle-analyzer
    
  • Configure next.config.js to analyze bundles.
    const withBundleAnalyzer = require("@next/bundle-analyzer")({
      enabled: process.env.ANALYZE === "true",
    });
    
    module.exports = withBundleAnalyzer({});
    
  • Run analysis to find large dependencies.
    ANALYZE=true npm run build
    
  • Look for duplicate or unused dependencies to remove.
  • Use ANALYZE=true npm run build to open an interactive bundle treemap in the browser.
  • Confirm bundle wins actually improve real load times by measuring Core Web Vitals (LCP, INP, CLS) with the web-vitals library or a Lighthouse run, not bundle size alone.
    npx lighthouse https://your-app.com --view
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 177
Advanced

Next.js Performance Optimization

(continued)

Code Splitting Strategies

  • Use dynamic imports to split code at route level.
    import dynamic from "next/dynamic";
    
    const HeavyComponent = dynamic(() => import("./HeavyComponent"));
    
    export default function Page() {
      return <HeavyComponent />;
    }
    
  • Split large components loaded conditionally.
    const Editor = dynamic(() => import("./Editor"), {
      loading: () => <p>Loading editor...</p>,
    });
    
  • Lazy load images with the Image component.
    import Image from "next/image";
    
    <Image
      src="/hero.jpg"
      alt="Hero"
      loading="lazy"
      width={1200}
      height={600}
    />
    
  • Optimize barrel imports from large libraries with optimizePackageImports nested under the top-level experimental key in next.config.js — it is still an experimental flag, so the wrapper is required for it to take effect.
    // next.config.js
    module.exports = {
      experimental: {
        optimizePackageImports: ["lodash", "date-fns"],
      },
    };
    
  • Use barrel file tree-shaking to avoid importing entire libraries.
    // Bad: imports entire lodash
    import _ from "lodash";
    // Good: imports only what you need
    import debounce from "lodash/debounce";
    
  • Measure whether your splitting actually helped by reporting Core Web Vitals (LCP, CLS, INP) from the client with useReportWebVitals from next/web-vitals, or by enabling Vercel Speed Insights for real-user production data.
    // app/_components/web-vitals.tsx
    "use client";
    import { useReportWebVitals } from "next/web-vitals";
    
    export function WebVitals() {
      useReportWebVitals((metric) => {
        console.log(metric); // send to your analytics endpoint instead
      });
      return null;
    }
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 178
Advanced

Next.js Performance Optimization

(continued)

Partial Prerendering

  • Enable Partial Prerendering (PPR) in Next.js 15 to serve a static shell instantly while streaming dynamic slots.

    // next.config.js
    module.exports = {
      experimental: { ppr: true }
    };
    
  • Wrap only the dynamic parts of a page in <Suspense> — the static shell is served from the CDN edge, dynamic content streams in.

    // app/product/[id]/page.tsx
    import { Suspense } from "react";
    import { ProductDetails } from "./ProductDetails"; // static — pre-rendered
    import { RecommendedProducts } from "./RecommendedProducts"; // dynamic
    
    export default function ProductPage() {
      return (
        <div>
          <ProductDetails />   {/* served immediately from CDN */}
          <Suspense fallback={<p>Loading recommendations...</p>}>
            <RecommendedProducts /> {/* streamed after static shell */}
          </Suspense>
        </div>
      );
    }
    
  • Mark a component as dynamic using connection() from next/server — this opts it out of static prerendering.

    import { connection } from "next/server";
    
    export async function Cart() {
      await connection(); // forces this component to be dynamic
      const cart = await fetchCart();
      return <ul>{cart.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
    }
    
  • PPR is composable — you can have multiple <Suspense> slots per page, each streaming independently.

  • PPR reduces Time To First Byte (TTFB) for pages with mixed static and dynamic content compared to full SSR.

Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 179
Advanced

Next.js Performance Optimization

(continued)

Compression and Minification

  • Enable Gzip compression for responses.
    // next.config.js
    module.exports = {
      compress: true, // enabled by default
    };
  • Use React strict mode to catch performance issues.
    // next.config.js
    module.exports = {
      reactStrictMode: true,
    };
  • Minify HTML, CSS, and JavaScript automatically.
    npm run build
  • Check production bundle size after optimization.
  • Remove console statements in production with compiler options.
    // next.config.js
    module.exports = {
      compiler: { removeConsole: { exclude: ["error"] } }
    };
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 180
Advanced

Next.js Performance Optimization

(continued)

Image and Font Optimization

  • Use the <Image> component for automatic format conversion, resizing, and lazy loading.

    import Image from "next/image";
    
    <Image
      src="/hero.jpg"
      alt="Hero banner"
      width={1200}
      height={600}
      priority          // preload above-the-fold images; omit for below-fold
      quality={80}      // default is 75; lower for smaller files
      sizes="(max-width: 768px) 100vw, 50vw"  // tell browser which size to download
    />
    
  • Set priority on hero images to preload them — only use it for images visible without scrolling.

  • Use the sizes prop so the browser downloads the smallest image that fits the layout.

    // Full-width on mobile, half-width on desktop
    <Image src="/photo.jpg" alt="Photo" fill sizes="(max-width: 640px) 100vw, 50vw" />
    
  • Load Google Fonts with zero layout shift using next/font/google.

    import { Inter } from "next/font/google";
    
    const inter = Inter({
      subsets: ["latin"],
      display: "swap",   // avoids invisible text during font load
      variable: "--font-inter"
    });
    
    export default function RootLayout({ children }) {
      return <html className={inter.variable}><body>{children}</body></html>;
    }
    
  • Self-host a local font with next/font/local to eliminate the external Google Fonts network request.

    import localFont from "next/font/local";
    const myFont = localFont({ src: "./fonts/GeistVF.woff2", display: "swap" });
    
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Performance Optimization
Chapter 25 · Page 181
Advanced

Next.js Performance Optimization

(FAQ)

FAQ

Run ANALYZE=true next build with @next/bundle-analyzer configured in next.config.js to get an interactive treemap of your client and server bundles. Look for large node_modules being included in client chunks — these are usually the easiest wins.

Use next/dynamic for components that aren't needed on initial render — modals, charts, below-the-fold sections, and anything heavy like rich text editors. Static imports are fine for everything rendered in the initial viewport, since Next.js already tree-shakes and splits by route automatically.

SSG generates pages at build time (no revalidation), ISR adds revalidate to regenerate pages in the background after a set interval, and SSR runs on every request with no built-in cache unless you add Cache-Control headers manually. For most content pages, ISR with a short revalidation window gives the best balance of freshness and performance.

Yes, Next.js enables gzip compression by default when running next start, but it's intentionally disabled when you deploy behind a reverse proxy like Nginx or a CDN — those should handle compression instead to avoid double-encoding. If you're on a platform like Vercel, Brotli compression is applied automatically at the edge.

Wrap slow data-fetching components in <Suspense> with a fallback — Next.js will stream the shell HTML immediately and flush each suspended section as it resolves. For page-level streaming, loading.tsx files in the App Router act as automatic Suspense boundaries for the entire route segment.

Useful Cheatsheetsusefulcheatsheets.com
Next.js Streaming and Progressive Rendering
Chapter 26 · Page 182
Advanced

Next.js Streaming and Progressive Rendering

Stream content progressively with Suspense to improve perceived performance and UX.

TL;DR

  1. 01Use Suspense to stream content as it's ready.
  2. 02Show loading states while slow components render.
  3. 03Stream improves perceived performance and Core Web Vitals.

Tips

  1. 01Use streaming to show partial content quickly — users perceive the page as faster even if all data isn't ready yet.

Warnings

  1. 01Don't create too many Suspense boundaries — keep them at logical boundaries to avoid confusing loading states.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Streaming and Progressive Rendering
Chapter 26 · Page 183
Advanced

Next.js Streaming and Progressive Rendering

(continued)

Suspense Boundaries

  • Use Suspense to show loading states while data loads.
    import { Suspense } from "react";
    import SlowComponent from "./SlowComponent";
    
    export default function Page() {
      return (
        <Suspense fallback={<p>Loading...</p>}>
          <SlowComponent />
        </Suspense>
      );
    }
  • Content is streamed as soon as it's ready.
  • Users see the page progressively instead of waiting.
  • Use a loading.tsx file as a route-level Suspense boundary.
    // app/dashboard/loading.tsx
    export default function Loading() {
      return <div className="skeleton">Loading dashboard...</div>;
    }
  • Suspense boundaries catch async server components automatically in App Router.
    // Any async server component inside Suspense is streamed
    async function SlowData() {
      const data = await fetchSlowAPI(); // streamed when ready
      return <ul>{data.map(d => <li key={d.id}>{d.name}</li>)}</ul>;
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Streaming and Progressive Rendering
Chapter 26 · Page 184
Advanced

Next.js Streaming and Progressive Rendering

(continued)

Nested Suspense Boundaries

  • Create multiple boundaries for granular control.
    export default function Dashboard() {
      return (
        <div>
          <Header />
          <Suspense fallback={<p>Loading posts...</p>}>
            <Posts />
          </Suspense>
          <Suspense fallback={<p>Loading sidebar...</p>}>
            <Sidebar />
          </Suspense>
        </div>
      );
    }
  • Each boundary loads independently.
  • Fast components render immediately.
  • Slow components show loading states separately.
  • Nest boundaries to control exactly which sections block each other.
    <Suspense fallback={<HeaderSkeleton />}>
      <Header />
      <Suspense fallback={<FeedSkeleton />}>
        <Feed />
      </Suspense>
    </Suspense>
  • Outer boundary shows while inner boundaries resolve independently.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Streaming and Progressive Rendering
Chapter 26 · Page 185
Advanced

Next.js Streaming and Progressive Rendering

(continued)

Server Components with Streaming

  • Server components naturally stream their data.
    // app/page.tsx
    import { Suspense } from "react";
    import Header from "./components/Header";
    import Post from "./components/Post";
    
    export default function Home() {
      return (
        <>
          <Header />
          <Suspense fallback={<p>Loading posts...</p>}>
            <Post />
          </Suspense>
        </>
      );
    }
    
    // components/Post.tsx (server component)
    async function Post() {
      const posts = await fetch("https://api/posts").then(r => r.json());
      return posts.map(post => <article key={post.id}>{post.title}</article>);
    }
  • Server components fetch data directly.
  • Data streams as soon as it's ready.
  • Parallel data fetching inside server components speeds up streaming.
    async function Page() {
      // Both fetches start at the same time
      const [user, posts] = await Promise.all([
        fetchUser(),
        fetchPosts()
      ]);
      return <Profile user={user} posts={posts} />;
    }
  • Avoid waterfalls by fetching all needed data in parallel.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Streaming and Progressive Rendering
Chapter 26 · Page 186
Advanced

Next.js Streaming and Progressive Rendering

(continued)

Error Boundaries While Streaming

  • Wrap a streamed Suspense boundary with the nearest error.tsx so a thrown component swaps its fallback for an error UI instead of crashing the page.
    // app/dashboard/error.tsx — catches errors thrown by streamed children
    "use client";
    export default function Error({ error, reset }: { error: Error; reset: () => void }) {
      return (
        <div>
          <p>Failed to load this section.</p>
          <button onClick={reset}>Retry</button>
        </div>
      );
    }
    
  • A component that throws inside a <Suspense> boundary already streamed to the client gets replaced in place by the nearest error boundary's UI.
    async function Reviews({ id }) {
      const reviews = await fetchReviews(id); // throws on network failure
      return <ul>{reviews.map(r => <li key={r.id}>{r.text}</li>)}</ul>;
    }
    // If Reviews throws after streaming starts, error.tsx output
    // replaces only the Reviews chunk -- sibling boundaries stay intact.
    
  • Only the failed boundary's fallback is replaced; sibling Suspense boundaries that already streamed in keep their resolved content.
  • A thrown error after the initial shell has flushed swaps in via an inline script, so streamed error recovery silently no-ops if JavaScript is disabled.
  • Use try/catch inside an async Server Component to return a fallback value instead of throwing, when inline UI is better than delegating to error.tsx.
    async function Reviews({ id }) {
      try {
        const reviews = await fetchReviews(id);
        return <ReviewList reviews={reviews} />;
      } catch {
        return <p>Reviews are unavailable right now.</p>; // no error boundary triggered
      }
    }
    
  • Place error.tsx at the same route segment as the Suspense boundary it should protect — a parent segment's error.tsx also catches throws from streamed children further down.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Streaming and Progressive Rendering
Chapter 26 · Page 187
Advanced

Next.js Streaming and Progressive Rendering

(continued)

Prioritizing Stream Order

  • Load critical content first, then push secondary content behind its own boundary so it streams in later.
    export default function Product({ id }) {
      return (
        <div>
          <Suspense fallback={<p>Loading product...</p>}>
            <BasicProduct id={id} />
          </Suspense>
          <Suspense fallback={<p>Loading reviews...</p>}>
            <Reviews id={id} />
          </Suspense>
          <Suspense fallback={<p>Loading recommendations...</p>}>
            <Recommendations id={id} />
          </Suspense>
        </div>
      );
    }
    
  • Render above-the-fold content synchronously, outside any Suspense boundary, so it never waits on a fallback.
    <main>
      <HeroSection /> {/* renders synchronously, no Suspense */}
      <Suspense fallback={<Skeleton />}>
        <BelowFoldContent /> {/* streams in after hero */}
      </Suspense>
    </main>
    
  • React flushes resolved boundaries in the order they finish, not the order they appear in JSX, so a slow first boundary never blocks a fast later one.
  • Race a slow data source against a timeout and fall back to cached or partial data, so one flaky dependency can't stall its entire boundary.
    async function withTimeout(promise, ms, fallback) {
      const timeout = new Promise(resolve => setTimeout(() => resolve(fallback), ms));
      return Promise.race([promise, timeout]);
    }
    
  • Deferred, lower-priority sections streaming in after the hero improve Largest Contentful Paint without delaying it.
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Streaming and Progressive Rendering
Chapter 26 · Page 188
Advanced

Next.js Streaming and Progressive Rendering

(continued)

State Transition Updates

  • Keep UI responsive during state changes.
    import { useTransition } from "react";
    
    export default function Page({ id }) {
      const router = useRouter();
      const [isPending, startTransition] = useTransition();
      
      function handleNavigate() {
        startTransition(() => {
          router.push(`/product/${id}`);
        });
      }
      
      return (
        <>
          {isPending && <p>Loading...</p>}
          <button onClick={handleNavigate}>
            View Product
          </button>
        </>
      );
    }
  • useTransition keeps UI responsive during async operations.
  • Show loading state without blocking interaction.
  • Mark expensive state updates as non-urgent with startTransition.
    const [isPending, startTransition] = useTransition();
    
    function handleFilter(value) {
      setInputValue(value); // urgent: update input immediately
      startTransition(() => {
        setFilteredList(filterItems(value)); // non-urgent: can wait
      });
    }
  • useDeferredValue defers a value update to keep UI responsive.
    const deferredQuery = useDeferredValue(query);
    // deferredQuery lags behind query, preventing expensive re-renders
    return <Results query={deferredQuery} />;
Notes
Useful Cheatsheetsusefulcheatsheets.com
Next.js Streaming and Progressive Rendering
Chapter 26 · Page 189
Advanced

Next.js Streaming and Progressive Rendering

(FAQ)

FAQ

Streaming sends the initial HTML shell immediately, then flushes chunks as server components resolve, which improves Time to First Byte (TTFB) and Largest Contentful Paint (LCP) since the browser can start rendering and hydrating before all data is fetched.

Yes — nested Suspense boundaries let inner components show their own fallback independently of outer ones, so a fast sidebar can render while a slow feed still shows a skeleton. Each boundary resolves and hydrates as soon as its own async work completes.

loading.js creates an implicit Suspense boundary around an entire route segment and applies automatically to all navigations to that route, while explicit tags give you fine-grained control over which parts of a page stream independently within a single layout.

Move the slow fetch inside its own async Server Component, then wrap that component in <Suspense fallback={}>. Next.js will stream the fallback immediately and replace it with the resolved content once the query completes, without delaying the rest of the page.

This happens when the async operation resolves before React flushes the boundary, but the fallback is still briefly painted. Wrap the component with startTransition on the client side for navigation updates, or use the new React 19 use() hook with a cached promise to avoid unnecessary fallback renders on re-renders.

Preview: Next.js Cheatsheets