Next.js Caching Cheat Sheet

Data Fetching and Caching

fetch() Caching

// Default caching (cache: 'default')
async function getData() {
    const res = await fetch('https://api.example.com/data');
    return res.json();
}

// Force cache (cache: 'force-cache')
async function getCachedData() {
    const res = await fetch('https://api.example.com/data', {
        cache: 'force-cache'
    });
    return res.json();
}

// No cache (cache: 'no-store')
async function getFreshData() {
    const res = await fetch('https://api.example.com/data', {
        cache: 'no-store'
    });
    return res.json();
}

// Revalidate after time (next: { revalidate: 3600 })
async function getRevalidatedData() {
    const res = await fetch('https://api.example.com/data', {
        next: { revalidate: 3600 } // 1 hour
    });
    return res.json();
}

Cache Options

// Cache options for fetch
const fetchOptions = {
    // Cache behavior
    cache: 'default' | 'force-cache' | 'no-store' | 'only-cache' | 'reload',
    
    // Next.js specific options
    next: {
        revalidate: 3600,        // Revalidate every hour
        tags: ['posts', 'user'], // Cache tags for invalidation
        revalidateTag: 'posts'   // Revalidate specific tag
    }
};

// Usage examples
const data = await fetch('/api/posts', {
    cache: 'force-cache',
    next: { 
        revalidate: 60,
        tags: ['posts']
    }
});

Static Generation (SSG)

Basic Static Generation

// pages/posts/[id].js
export async function getStaticProps({ params }) {
    const post = await getPost(params.id);
    
    return {
        props: {
            post
        },
        revalidate: 3600 // ISR: revalidate every hour
    };
}

export async function getStaticPaths() {
    const posts = await getPosts();
    
    const paths = posts.map((post) => ({
        params: { id: post.id.toString() }
    }));
    
    return {
        paths,
        fallback: 'blocking' // or 'false' or 'true'
    };
}

Incremental Static Regeneration (ISR)

// ISR with revalidation
export async function getStaticProps() {
    const data = await fetch('https://api.example.com/data');
    const posts = await data.json();
    
    return {
        props: {
            posts
        },
        revalidate: 60, // Revalidate every 60 seconds
        notFound: false // Return 404 if data not found
    };
}

// ISR with on-demand revalidation
export async function getStaticProps() {
    const posts = await getPosts();
    
    return {
        props: { posts },
        revalidate: 3600, // Fallback revalidation
        tags: ['posts']   // Cache tags for manual invalidation
    };
}

Server Components Caching

App Router Caching

// app/posts/page.js
async function getPosts() {
    const res = await fetch('https://api.example.com/posts', {
        next: { revalidate: 3600 }
    });
    return res.json();
}

export default async function PostsPage() {
    const posts = await getPosts();
    
    return (
        <div>
            {posts.map(post => (
                <Post key={post.id} post={post} />
            ))}
        </div>
    );
}

Route Segment Config

// app/posts/page.js
export const revalidate = 3600; // Revalidate every hour
export const dynamic = 'force-static'; // Force static generation
export const fetchCache = 'force-cache'; // Force cache

// Or disable caching
export const dynamic = 'force-dynamic';
export const fetchCache = 'force-no-store';

Cache Tags

// Set cache tags
async function getPosts() {
    const res = await fetch('https://api.example.com/posts', {
        next: { tags: ['posts'] }
    });
    return res.json();
}

// Revalidate by tag
import { revalidateTag } from 'next/cache';

export async function POST(request) {
    const post = await createPost(request.body);
    
    // Revalidate posts cache
    revalidateTag('posts');
    
    return Response.json({ post });
}

Revalidation Strategies

On-Demand Revalidation

// app/api/revalidate/route.js
import { revalidatePath, revalidateTag } from 'next/cache';

export async function POST(request) {
    const { path, tag } = await request.json();
    
    if (path) {
        revalidatePath(path);
    }
    
    if (tag) {
        revalidateTag(tag);
    }
    
    return Response.json({ revalidated: true });
}

// Usage
await fetch('/api/revalidate', {
    method: 'POST',
    body: JSON.stringify({
        path: '/posts',
        tag: 'posts'
    })
});

Time-Based Revalidation

// Revalidate every hour
export async function getStaticProps() {
    return {
        props: { data },
        revalidate: 3600
    };
}

// Revalidate every 5 minutes
export const revalidate = 300;

// No revalidation (static)
export const revalidate = false;

Conditional Revalidation

export async function getStaticProps() {
    const data = await fetchData();
    
    // Revalidate based on data freshness
    const revalidateTime = data.isStale ? 60 : 3600;
    
    return {
        props: { data },
        revalidate: revalidateTime
    };
}

Cache Invalidation

Manual Cache Invalidation

// Invalidate specific paths
import { revalidatePath } from 'next/cache';

export async function POST(request) {
    // Invalidate specific page
    revalidatePath('/posts');
    
    // Invalidate with layout
    revalidatePath('/posts', 'layout');
    
    // Invalidate page only
    revalidatePath('/posts', 'page');
}

// Invalidate cache tags
import { revalidateTag } from 'next/cache';

export async function POST(request) {
    // Invalidate specific tag
    revalidateTag('posts');
    
    // Invalidate multiple tags
    revalidateTag('posts');
    revalidateTag('users');
}

Webhook-Based Invalidation

// app/api/webhook/route.js
import { revalidateTag } from 'next/cache';

export async function POST(request) {
    const body = await request.json();
    
    // Invalidate based on webhook event
    if (body.event === 'post.created') {
        revalidateTag('posts');
    }
    
    if (body.event === 'user.updated') {
        revalidateTag('users');
    }
    
    return Response.json({ success: true });
}

Performance Optimization

Cache Headers

// Set cache headers
export async function GET(request) {
    const data = await getData();
    
    return Response.json(data, {
        headers: {
            'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400'
        }
    });
}

// Cache control options
const cacheHeaders = {
    'Cache-Control': 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400'
};

Optimistic Updates

'use client';

import { useOptimistic } from 'react';

export default function PostForm() {
    const [optimisticPosts, addOptimisticPost] = useOptimistic(
        posts,
        (state, newPost) => [...state, newPost]
    );
    
    async function handleSubmit(formData) {
        const newPost = { title: formData.get('title'), pending: true };
        addOptimisticPost(newPost);
        
        await createPost(formData);
    }
    
    return (
        <form action={handleSubmit}>
            {/* Form fields */}
        </form>
    );
}

Streaming and Suspense

// app/posts/page.js
import { Suspense } from 'react';

export default function PostsPage() {
    return (
        <div>
            <h1>Posts</h1>
            <Suspense fallback={<PostsSkeleton />}>
                <PostsList />
            </Suspense>
        </div>
    );
}

async function PostsList() {
    const posts = await getPosts(); // Cached fetch
    
    return (
        <ul>
            {posts.map(post => (
                <li key={post.id}>{post.title}</li>
            ))}
        </ul>
    );
}

Cache Configuration

Next.js Config

// next.config.js
/* @type {import('next').NextConfig} */
const nextConfig = {
    // Experimental cache options
    experimental: {
        // Enable new cache system
        serverComponentsExternalPackages: ['@prisma/client'],
        
        // Optimize bundle caching
        optimizeCss: true,
        optimizePackageImports: ['lodash', 'react-icons']
    },
    
    // Image optimization caching
    images: {
        minimumCacheTTL: 60,
        formats: ['image/webp', 'image/avif']
    }
};

module.exports = nextConfig;

Environment Variables

// .env.local
# Cache configuration
NEXT_CACHE_MAX_AGE=3600
NEXT_CACHE_TAGS=posts,users,products

# Revalidation
NEXT_REVALIDATE_TIME=3600
NEXT_REVALIDATE_SECRET=your-secret-key

Monitoring and Debugging

Cache Status

// Check cache status
export async function GET(request) {
    const { searchParams } = new URL(request.url);
    const debug = searchParams.get('debug');
    
    if (debug === 'cache') {
        return Response.json({
            cache: 'hit',
            timestamp: new Date().toISOString(),
            revalidated: false
        });
    }
    
    const data = await getData();
    return Response.json(data);
}

Cache Headers Debug

// Debug cache headers
export async function GET(request) {
    const data = await getData();
    
    return Response.json(data, {
        headers: {
            'X-Cache-Status': 'HIT',
            'X-Cache-Timestamp': new Date().toISOString(),
            'X-Cache-TTL': '3600'
        }
    });
}