Revalidation Strategy Planner
Analyze Next.js application routes and recommend optimal caching and revalidation strategies for performance and data freshness.
Overview
To optimize Next.js caching strategies:
-
Analyze route characteristics (data freshness requirements, update frequency)
-
Determine appropriate rendering strategy (SSG, ISR, SSR, streaming)
-
Configure revalidation intervals for ISR routes
-
Implement cache tags for on-demand revalidation
-
Set up streaming for progressive page loading
Rendering Strategies
Static Site Generation (SSG)
To use SSG for rarely changing content:
// app/about/page.tsx export default async function AboutPage() { // Generated at build time, no revalidation return <div>About Us</div>; }
Best for:
-
Marketing pages
-
Documentation
-
Static content that rarely changes
Incremental Static Regeneration (ISR)
To use ISR for periodically updated content:
// app/entities/[id]/page.tsx export const revalidate = 3600; // Revalidate every hour
export default async function EntityPage({ params }: { params: { id: string } }) { const entity = await fetchEntity(params.id); return <EntityDetail entity={entity} />; }
Best for:
-
Entity detail pages
-
Blog posts
-
Product listings
-
Content with predictable update patterns
Server-Side Rendering (SSR)
To use SSR for real-time data:
// app/dashboard/page.tsx export const dynamic = 'force-dynamic';
export default async function Dashboard() { const data = await fetchUserData(); return <DashboardView data={data} />; }
Best for:
-
User dashboards
-
Personalized content
-
Real-time data displays
-
Authentication-dependent pages
Streaming
To use streaming for progressive loading:
// app/timeline/page.tsx import { Suspense } from 'react';
export default function TimelinePage() { return ( <div> <TimelineHeader /> <Suspense fallback={<TimelineLoader />}> <TimelineEvents /> </Suspense> </div> ); }
Best for:
-
Pages with slow data fetching
-
Complex pages with multiple data sources
-
Improving perceived performance
Consult references/rendering-strategies.md for detailed strategy comparison.
Revalidation Configuration
Time-Based Revalidation
To set revalidation intervals:
// Revalidate every 60 seconds export const revalidate = 60;
// Revalidate every hour export const revalidate = 3600;
// Revalidate every day export const revalidate = 86400;
On-Demand Revalidation
To implement on-demand cache invalidation:
// app/api/revalidate/route.ts import { revalidatePath, revalidateTag } from 'next/cache'; import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) { const { path, tag } = await request.json();
if (path) { revalidatePath(path); }
if (tag) { revalidateTag(tag); }
return Response.json({ revalidated: true, now: Date.now() }); }
Use from Server Actions:
'use server';
import { revalidatePath } from 'next/cache';
export async function updateEntity(id: string, data: EntityData) {
await saveEntity(id, data);
revalidatePath(/entities/${id});
revalidatePath('/entities');
}
Cache Tags
To implement cache tag-based revalidation:
// app/entities/[id]/page.tsx
export default async function EntityPage({ params }: { params: { id: string } }) {
const entity = await fetch(/api/entities/${params.id}, {
next: {
tags: [entity-${params.id}, 'entities'],
},
});
return <EntityDetail entity={entity} />; }
Revalidate by tag:
import { revalidateTag } from 'next/cache';
// Revalidate all pages with 'entities' tag revalidateTag('entities');
// Revalidate specific entity
revalidateTag(entity-${entityId});
Reference assets/cache-tag-patterns.ts for cache tagging patterns.
Route Analysis
Use scripts/analyze_routes.py to analyze application routes and recommend strategies:
python scripts/analyze_routes.py ./app
Output includes:
-
Route path
-
Recommended rendering strategy
-
Suggested revalidation interval
-
Appropriate cache tags
-
Reasoning for recommendations
Analysis Criteria
Consider these factors:
Data Freshness Requirements
-
Real-time: SSR or very short revalidation (1-60s)
-
Near real-time: ISR with short interval (60-300s)
-
Periodic updates: ISR with medium interval (300-3600s)
-
Rarely changes: SSG or long interval (3600s+)
Update Frequency
-
Continuous: SSR
-
Multiple times per hour: ISR (60-300s)
-
Hourly: ISR (3600s)
-
Daily: ISR (86400s)
-
Weekly+: SSG
Personalization
-
User-specific: SSR
-
Role-based: SSR or ISR with user context
-
Public: SSG or ISR
Data Source Performance
-
Fast (<100ms): Any strategy
-
Medium (100-500ms): Consider streaming
-
Slow (>500ms): Use streaming or aggressive caching
Consult references/decision-matrix.md for the complete decision matrix.
Implementation Patterns
Entity Detail Pages
To optimize entity pages:
// app/entities/[id]/page.tsx export const revalidate = 1800; // 30 minutes
export async function generateStaticParams() { const entities = await fetchAllEntityIds(); return entities.map((id) => ({ id: id.toString() })); }
export default async function EntityPage({ params }: { params: { id: string } }) {
const entity = await fetchEntity(params.id, {
next: { tags: [entity-${params.id}, 'entities'] },
});
return <EntityDetail entity={entity} />; }
List Pages
To optimize listing pages:
// app/entities/page.tsx export const revalidate = 300; // 5 minutes
export default async function EntitiesPage({ searchParams, }: { searchParams: { page?: string }; }) { const page = parseInt(searchParams.page || '1'); const entities = await fetchEntities(page, { next: { tags: ['entities'] }, });
return <EntityList entities={entities} />; }
Timeline Pages
To optimize timeline with streaming:
// app/timeline/page.tsx import { Suspense } from 'react';
export default function TimelinePage() { return ( <div> <Suspense fallback={<TimelineHeaderSkeleton />}> <TimelineHeader /> </Suspense> <Suspense fallback={<EventsSkeleton />}> <TimelineEvents /> </Suspense> </div> ); }
async function TimelineEvents() { const events = await fetchTimelineEvents({ next: { tags: ['timeline'], revalidate: 600 }, }); return <EventsList events={events} />; }
Dashboard Pages
To implement personalized dashboard:
// app/dashboard/page.tsx export const dynamic = 'force-dynamic';
export default async function DashboardPage() { const session = await getSession(); const data = await fetchUserDashboard(session.userId);
return ( <div> <Suspense fallback={<StatsSkeleton />}> <DashboardStats userId={session.userId} /> </Suspense> <Suspense fallback={<ActivitySkeleton />}> <RecentActivity userId={session.userId} /> </Suspense> </div> ); }
Cache Invalidation Strategies
Granular Invalidation
To invalidate specific resources:
// After entity update
revalidateTag(entity-${entityId});
// After relationship change
revalidateTag(entity-${sourceId});
revalidateTag(entity-${targetId});
revalidateTag('relationships');
Cascade Invalidation
To invalidate related resources:
async function updateEntity(id: string, data: EntityData) { await saveEntity(id, data);
// Invalidate entity page
revalidateTag(entity-${id});
// Invalidate list pages revalidateTag('entities');
// Invalidate related pages
const relationships = await getEntityRelationships(id);
for (const rel of relationships) {
revalidateTag(entity-${rel.targetId});
}
}
Batch Invalidation
To invalidate multiple resources efficiently:
async function bulkUpdateEntities(updates: EntityUpdate[]) { await saveBulkUpdates(updates);
// Collect unique tags
const tags = new Set<string>(['entities']);
for (const update of updates) {
tags.add(entity-${update.id});
}
// Revalidate all at once for (const tag of tags) { revalidateTag(tag); } }
Performance Optimization
Stale-While-Revalidate
To implement SWR pattern:
export const revalidate = 60; // Revalidate every minute export const dynamic = 'force-static'; // Serve stale while revalidating
Parallel Data Fetching
To fetch data in parallel:
export default async function EntityPage({ params }: { params: { id: string } }) { const [entity, relationships, timeline] = await Promise.all([ fetchEntity(params.id), fetchRelationships(params.id), fetchTimeline(params.id), ]);
return <EntityDetailView entity={entity} relationships={relationships} timeline={timeline} />; }
Selective Streaming
To stream only slow components:
export default function EntityPage({ params }: { params: { id: string } }) { return ( <div> <EntityHeader id={params.id} /> {/* Fast, no streaming /} <Suspense fallback={<RelationshipsSkeleton />}> <EntityRelationships id={params.id} /> {/ Slow, stream it */} </Suspense> </div> ); }
Monitoring and Testing
To monitor cache performance:
-
Cache Hit Rates: Track ISR cache hits vs. regenerations
-
Revalidation Frequency: Monitor how often pages regenerate
-
Response Times: Measure time to first byte (TTFB)
-
Stale Serving: Track stale-while-revalidate occurrences
Use Next.js analytics or custom logging:
// middleware.ts export function middleware(request: NextRequest) { const start = Date.now();
return NextResponse.next({
headers: {
'x-response-time': ${Date.now() - start}ms,
},
});
}
Best Practices
-
Start Conservative: Begin with shorter revalidation intervals, increase gradually
-
Use Cache Tags: Prefer tag-based invalidation over path-based
-
Monitor Performance: Track cache hit rates and response times
-
Plan Invalidation: Design invalidation strategy with data mutations
-
Test Edge Cases: Verify behavior with stale data and revalidation
-
Document Decisions: Record why specific intervals were chosen
-
Consider Users: Balance freshness with performance
Troubleshooting
Common issues:
-
Stale Data Persisting: Check cache tag implementation and invalidation logic
-
Excessive Regeneration: Increase revalidation interval or fix trigger-happy invalidation
-
Slow Page Loads: Add streaming for slow components
-
Cache Not Working: Verify fetch options and dynamic/static configuration
-
Development vs Production: Remember ISR only works in production builds