Back to Rules
Next.js97% popularity

Next.js Caching Strategies

Master Next.js caching with fetch, revalidation, and Route Handler caching for optimal performance.

Vercel EngineeringUpdated Mar 28, 2024

Overview

Next.js provides a multi-layered caching system that dramatically improves performance by reducing database queries and API calls. Understanding how these layers interact enables building applications that are both fast and fresh, balancing user experience with data accuracy. The fetch API caching extends the Web standard with Next.js-specific options. cache: 'force-cache' (default) caches responses indefinitely, suitable for static reference data. cache: 'no-store' bypasses all caching for dynamic content. The next.revalidate option sets a time-based revalidation period, automatically refreshing cached data after the specified seconds. Route Handler caching uses the same caching semantics as fetch. When a Route Handler is called from a Server Component, its response is cached according to the fetch options used. Dynamic functions like cookies() and headers() automatically opt routes out of caching, ensuring proper behavior without explicit cache: 'no-store'. Full route cache stores rendered HTML and React Server Component payload for entire routes. This cache is invalidated by route changes and revalidation. Static routes benefit most, rendering once and serving from cache for subsequent requests. Dynamic routes with uncached data render on each request but can still benefit from component-level caching. Data cache sits between raw database queries and the route cache. Each unique fetch request is cached separately, and multiple components fetching the same data share cached results. The unstable_cache function extends this behavior to non-fetch data sources, enabling caching for direct database calls with specified revalidation periods.

Code Example

.nextjsrules
import { unstable_cache } from 'next/cache';
import { revalidatePath, revalidateTag } from 'next/cache';
import { cookies, headers } from 'next/headers';

// Cached database query with revalidation
const getCachedProjects = unstable_cache(
  async (limit: number) => {
    return db.project.findMany({
      orderBy: { views: 'desc' },
      take: limit,
      include: { owner: true },
    });
  },
  ['projects'], // Cache key parts
  {
    revalidate: 3600, // Revalidate every hour
    tags: ['projects'], // For tag-based revalidation
  }
);

// Revalidate on mutation
async function updateProject(projectId: string, data: UpdateProjectDTO) {
  await db.project.update({
    where: { id: projectId },
    data,
  });

  // Revalidate specific paths
  revalidatePath('/dashboard');
  revalidatePath('/projects/' + projectId);

  // Or revalidate by tag
  revalidateTag('projects');
}

// Dynamic data with cookies
async function getUserData() {
  const cookieStore = cookies();
  const token = cookieStore.get('auth_token');

  if (!token) {
    return null;
  }

  // This fetch won't be cached because it uses cookies
  const response = await fetch('/api/user/profile', {
    headers: {
      Authorization: 'Bearer ' + token.value,
    },
    // Opt out of caching for authenticated data
    cache: 'no-store',
  });

  return response.json();
}

// API Route with caching
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const category = searchParams.get('category');

  // Check cache headers
  const cacheControl = request.headers.get('cache-control');

  if (cacheControl?.includes('no-cache')) {
    // Force fresh data
    const data = await fetchFromDatabase(category);
    return Response.json(data);
  }

  // Cache for 5 minutes
  const data = await fetchFromDatabase(category);

  return Response.json(data, {
    headers: {
      'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600',
    },
  });
}

// Incremental Static Regeneration at page level
export const revalidate = 60; // Revalidate every 60 seconds

export default async function Page() {
  const data = await fetchData();
  return <MainContent data={data} />;
}

// On-demand revalidation API
export async function POST(request: Request) {
  const { path, tag } = await request.json();

  if (path) {
    revalidatePath(path);
  }

  if (tag) {
    revalidateTag(tag);
  }

  return Response.json({ revalidated: true, now: Date.now() });
}

More Next.js Rules

NEXTJS
98%

Next.js App Router Server Components First

Prioritize React Server Components for data fetching and static content. Client components only when interactivity is required.

server-componentsapp-routerperformance
'use server';

import { db } from '@/lib/database';
import { cache } from 'react';

export const getProjects = cache(async () => {
  return db.project...
Jan 15, 2024by Vercel Engineering
View Rule
NEXTJS
93%

Next.js Image Optimization Best Practices

Use the Image component with proper sizing, formats, and loading strategies for maximum performance.

nextjsimage-optimizationperformance
import Image from 'next/image';
import Link from 'next/link';

export function ProjectCard({ project }: { project: Project }) {
  return (
    <Link h...
Feb 28, 2024by Vercel
View Rule
NEXTJS
94%

Next.js Middleware Authentication

Implement authentication at the edge with Next.js Middleware for protected routes.

nextjsmiddlewareauthentication
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { jwtVerify } from 'jose';

const JWT_SECRET = new ...
Mar 8, 2024by Next.js Team
View Rule