StarterApp Docs
Performance

Caching Public Data

Accelerating API responses through edge distribution

Pro Tip

Edge caching serves content from CDN locations near users. Use securePublicJson() with maxAge to enable caching while maintaining security headers.

Edge Caching Strategy

Edge caching distributes content globally through CDN nodes. Users receive responses from nearby locations within milliseconds. Origin servers handle fewer requests, reducing load and costs.

Security-First Caching Helpers

The framework provides @workspace/security helpers that enforce correct caching behavior:

import { securePublicJson } from '@workspace/security';

export async function GET() {
  const products = await fetchProducts();

  return securePublicJson(products, { maxAge: 300 });
}

securePublicJson adds:

  • Cache-Control: public, max-age=300
  • Vary: Accept-Encoding
  • Security headers (CSP, X-Content-Type-Options)
  • Proper content type
import { secureErrorJson, secureUserJson } from '@workspace/security';

export async function GET(req: NextRequest) {
  const user = await getCurrentSession();
  if (!user) {
    return secureErrorJson(
      { message: 'Authentication required', code: 'UNAUTHORIZED' },
      { status: 401, userScoped: true }
    );
  }
  const userData = await fetchUserProfile(user.id);

  return secureUserJson(userData);
}

secureUserJson prevents caching:

  • Cache-Control: private, no-cache, no-store, must-revalidate
  • Vary: Cookie for session-based responses
  • Security headers
  • Never caches user-specific data
import { secureErrorJson } from '@workspace/security';

export async function GET() {
  try {
    const data = await riskyOperation();
    return securePublicJson(data, { maxAge: 60 });
  } catch (error) {
    return secureErrorJson(
      { message: 'Operation failed', code: 'SERVER_ERROR' },
      { status: 500 }
    );
  }
}

secureErrorJson prevents error caching:

  • No cache headers on error responses
  • Consistent error shape
  • Security headers

Never cache user-specific data

Using securePublicJson for user data exposes private information to other users. Always use secureUserJson for authenticated responses.

Real Implementation Example

The CSP report API demonstrates correct no-cache behavior:

import { secureJson } from "@workspace/security";
import type { NextRequest } from "next/server";

export const revalidate = 0;
export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";
export const runtime = "nodejs";

export async function POST(request: NextRequest) {
  const violation = await request.json().catch(() => null);

  if (violation && process.env.NODE_ENV !== "production") {
    console.warn("[dashboard][csp-report] violation", violation);
  }

  return secureJson({ ok: true }, { cache: "private-no-store" });
}

This route:

Disables caching entirely

Static rendering is disabled and secureJson emits Cache-Control: private, no-store.

Strips framework headers

secureJson removes X-Powered-By/Server headers while applying X-Content-Type-Options.

Choosing Cache Durations

Different content types require different maxAge values:

DurationUse CaseExamples
60sReal-time dataStock prices, live scores, availability status
300s (5min)Standard contentProduct listings, blog posts, search results
3600s (1hr)Stable contentDocumentation, marketing pages, legal terms
86400s (24hr)Static assetsLogos, icons, generated images (with versioned URLs)

Start conservative

Begin with shorter durations (60-300s). Increase gradually as you verify data freshness requirements. Shortening cache duration is easier than dealing with stale data issues.

Identifying Cacheable Content

Public data that all users see identically benefits from edge caching:

User-specific data must never cache publicly

These require secureUserJson:

  • User profiles and preferences
  • Shopping carts and wishlists
  • Private messages or notifications
  • Account settings and billing
  • Personalized recommendations

Stale-While-Revalidate Pattern

Advanced caching strategies serve cached content while updating in the background:

export async function GET() {
  const products = await fetchProducts();

  return securePublicJson(products, {
    maxAge: 300,        // Fresh for 5 minutes
    swr: 3600          // Serve stale for 1 hour while revalidating
  });
}

This pattern:

  1. Serves cached content immediately (even if stale)
  2. Triggers background revalidation
  3. Updates cache with fresh data
  4. Next request receives updated content

Users experience instant responses while data stays reasonably fresh.

Page-Level Caching with Revalidation

Next.js provides page-level caching through route segment config:

export const revalidate = 600; // Regenerate static HTML every 10 minutes

export default async function HomePage() {
  return <HeroSection />;
}

This configuration:

  • Generates static HTML at build time
  • Serves from edge with instant response times
  • Regenerates every 10 minutes to keep content fresh
  • Eliminates server requests for cached pages

The marketing site uses this pattern extensively. Static pages serve instantly while periodic regeneration ensures content freshness.

Combine with API caching

Page-level caching works best when underlying API routes also cache appropriately. This creates multiple layers of caching efficiency.

Monitoring Cache Effectiveness

Track cache performance through CDN analytics and response headers:

Check Cache-Control headers

Verify responses include correct caching directives:

curl -I https://your-app.com/api/products

Cache-Control: public, max-age=300
Vary: Accept-Encoding

Monitor cache hit rates

CDN providers report hit/miss ratios. Target 80-95% hit rates for public content.

Measure origin load reduction

Compare origin requests before and after implementing caching. Expect 80-90% reduction.

Common Caching Mistakes

Avoid these anti-patterns

These patterns compromise security or performance:

Next Steps