Rendering Strategies
FlatWP supports three rendering strategies for your WordPress content: Static Site Generation (SSG), Incremental Static Regeneration (ISR), and Server-Side Rendering (SSR). Understanding when to use each strategy is crucial for optimal performance.
Strategy Overview
| Strategy | When to Use | Performance | Freshness | Build Time |
|---|---|---|---|---|
| Static | Content rarely changes | Fastest | Stale until rebuild | Longest |
| ISR | Balance speed and freshness | Fast | Configurable | Medium |
| SSR | Real-time data required | Slower | Always fresh | None |
Static Site Generation (SSG)
Overview
Static pages are generated once at build time and served from CDN. This is the fastest option but requires rebuilding to update content.
Configuration
{
strategy: 'static',
revalidate: false,
generateStaticParams: true,
}
When to Use
Use static generation for:
- Marketing pages (
/about,/contact,/services) - Documentation that changes infrequently
- Landing pages optimized for speed
- Legal pages (
/privacy,/terms) - Portfolio projects with stable content
Example: Static Pages
export default defineConfig({
rendering: {
pages: {
strategy: 'static',
revalidate: false,
generateStaticParams: true,
},
},
});
import { getContentISR } from '@/lib/config';
// No revalidation - fully static
export const revalidate = getContentISR('pages').revalidate;
export async function generateStaticParams() {
const pages = await getAllPages();
return pages.map((page) => ({ slug: page.slug }));
}
export default async function Page({ params }: { params: { slug: string } }) {
const page = await getPageBySlug(params.slug);
return <PageContent page={page} />;
}
Pros & Cons
Advantages:
- Fastest possible delivery
- Lowest server load
- Best for SEO (pre-rendered HTML)
- Predictable costs
Disadvantages:
- Requires full rebuild for updates
- Not suitable for frequently changing content
- Build time increases with content volume
Incremental Static Regeneration (ISR)
Overview
ISR combines static generation with automatic or on-demand revalidation. Pages are generated statically but can be refreshed without rebuilding the entire site.
Configuration
{
strategy: 'isr',
revalidate: number | boolean,
generateStaticParams: true,
}
Revalidation Options
On-Demand Revalidation (Recommended)
Revalidate only when WordPress content changes via webhook.
{
strategy: 'isr',
revalidate: false, // On-demand only
generateStaticParams: true,
}
How it works:
- WordPress content is saved
- FlatWP plugin sends webhook to Next.js
- Next.js revalidates specific paths
- Next request gets fresh content
Setup:
import { revalidatePath } from 'next/cache';
import { wordpress } from '@/lib/config';
export async function POST(request: Request) {
const { secret, paths } = await request.json();
// Verify webhook secret
if (secret !== wordpress.revalidateSecret) {
return Response.json({ error: 'Invalid secret' }, { status: 401 });
}
// Revalidate all requested paths
for (const path of paths) {
await revalidatePath(path);
}
return Response.json({ revalidated: true, paths });
}
Time-Based Revalidation
Automatically revalidate pages after a time interval.
{
strategy: 'isr',
revalidate: 300, // 5 minutes
generateStaticParams: true,
}
Common intervals:
60- 1 minute (homepage, high-traffic pages)300- 5 minutes (archives, category pages)900- 15 minutes (less critical content)3600- 1 hour (rarely updated content)
Hybrid Approach
Combine time-based and on-demand revalidation:
{
strategy: 'isr',
revalidate: 300, // Fallback: revalidate every 5 minutes
// PLUS on-demand via webhook
}
When to Use
Use ISR for:
- Blog posts (on-demand revalidation)
- Archive pages (time-based, 5 minutes)
- Homepage (time-based, 1 minute)
- Product pages (on-demand or time-based)
- News articles (time-based, short interval)
Example: Blog Posts with On-Demand ISR
export default defineConfig({
rendering: {
posts: {
strategy: 'isr',
revalidate: false, // WordPress webhook triggers revalidation
generateStaticParams: true,
},
},
});
import { getContentISR } from '@/lib/config';
// On-demand revalidation
export const revalidate = getContentISR('posts').revalidate;
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug);
return <PostContent post={post} />;
}
Example: Archives with Time-Based ISR
export default defineConfig({
rendering: {
archives: {
strategy: 'isr',
revalidate: 300, // Revalidate every 5 minutes
generateStaticParams: true,
},
},
});
import { getContentISR } from '@/lib/config';
// Time-based revalidation
export const revalidate = getContentISR('archives').revalidate;
export default async function BlogArchive() {
const posts = await getAllPosts({ limit: 10 });
return <ArchiveLayout posts={posts} />;
}
Pros & Cons
Advantages:
- Fast like static, fresh like SSR
- On-demand updates without rebuilds
- Lower server load than SSR
- SEO-friendly (pre-rendered HTML)
Disadvantages:
- First visitor after revalidation sees stale content
- Requires webhook setup for on-demand
- More complex than pure static
Server-Side Rendering (SSR)
Overview
Pages are rendered on every request. Use this only when real-time data is absolutely necessary.
Configuration
{
strategy: 'ssr',
generateStaticParams: false,
}
When to Use
Use SSR sparingly for:
- User dashboards (personalized content)
- Real-time data (stock prices, live scores)
- A/B testing requiring server logic
- Geo-targeted content based on request location
SSR is significantly slower than static or ISR. Use it only when absolutely necessary.
Example: Real-Time Dashboard
// Force SSR - no caching
export const dynamic = 'force-dynamic';
export default async function Dashboard() {
// Fetched on every request
const userData = await getCurrentUserData();
const liveStats = await getLiveStatistics();
return <DashboardContent user={userData} stats={liveStats} />;
}
Pros & Cons
Advantages:
- Always shows latest data
- No build or revalidation needed
- Personalized per request
Disadvantages:
- Slowest rendering strategy
- Highest server load
- Higher costs
- Not ideal for SEO (TTFB)
Choosing the Right Strategy
Decision Tree
Is content user-specific or real-time?
├─ Yes → SSR
└─ No → Does content change frequently?
├─ No (rarely/never) → Static
└─ Yes → ISR
├─ Controlled updates (WordPress saves) → ISR (on-demand)
└─ Unpredictable updates → ISR (time-based)
Recommendations by Content Type
Blog Posts
posts: {
strategy: 'isr',
revalidate: false, // On-demand via WordPress webhook
generateStaticParams: true,
}
Reasoning: Blog posts change only when edited in WordPress. On-demand revalidation provides instant updates without constant polling.
Static Pages
pages: {
strategy: 'static',
revalidate: false,
generateStaticParams: true,
}
Reasoning: Pages like About, Contact, Services rarely change. Fully static is fastest.
Archive Pages
archives: {
strategy: 'isr',
revalidate: 300, // 5 minutes
generateStaticParams: true,
}
Reasoning: Archives change when new posts are published. Time-based ISR ensures freshness without individual revalidation.
Homepage
homepage: {
strategy: 'isr',
revalidate: 60, // 1 minute
generateStaticParams: false,
}
Reasoning: Homepage should feel fresh. Short revalidation interval keeps it updated.
WooCommerce Products
custom: {
product: {
strategy: 'isr',
revalidate: false, // On-demand when product updated
generateStaticParams: true,
},
}
Reasoning: Products change when stock, price, or details update. On-demand keeps inventory accurate.
Events
custom: {
event: {
strategy: 'isr',
revalidate: 60, // 1 minute for time-sensitive content
generateStaticParams: true,
},
}
Reasoning: Events are time-sensitive. Short revalidation ensures accurate status.
Advanced Patterns
Hybrid Content Strategy
Different parts of a page can use different strategies:
import { getContentISR } from '@/lib/config';
// Homepage: ISR with 1-minute revalidation
export const revalidate = getContentISR('homepage').revalidate;
export default async function HomePage() {
// Static component (cached)
const heroData = await getHeroContent();
// Dynamic component (not cached)
const latestPosts = await getLatestPosts({ limit: 3 });
return (
<>
<Hero data={heroData} />
<LatestPosts posts={latestPosts} />
</>
);
}
Conditional Revalidation
Revalidate based on content characteristics:
import { getContentISR } from '@/lib/config';
// Base revalidation from config
export const revalidate = getContentISR('posts').revalidate;
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug);
// Could implement custom logic:
// - Shorter revalidation for popular posts
// - Longer for old posts
// - Different for different categories
return { title: post.title };
}
Fallback Generation
Generate popular pages at build time, render others on-demand:
export async function generateStaticParams() {
// Only generate top 100 posts at build time
const popularPosts = await getPopularPosts({ limit: 100 });
return popularPosts.map((post) => ({ slug: post.slug }));
}
// Other posts generated on first request (ISR)
export const dynamicParams = true;
Performance Optimization
Build Time Optimization
For sites with thousands of pages:
posts: {
strategy: 'isr',
revalidate: false,
generateStaticParams: true, // But limit in generateStaticParams()
}
export async function generateStaticParams() {
if (process.env.NODE_ENV === 'development') {
// Only 10 posts in dev
const posts = await getAllPosts({ limit: 10 });
return posts.map((post) => ({ slug: post.slug }));
}
// All posts in production
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
// Generate others on-demand
export const dynamicParams = true;
Cache Warming
Pre-warm cache for critical pages:
const criticalPaths = [
'/',
'/blog',
'/about',
'/contact',
];
for (const path of criticalPaths) {
await fetch(`https://your-site.com${path}`);
}
Stale-While-Revalidate Pattern
ISR implements stale-while-revalidate automatically:
- Visitor requests page
- Serve cached version immediately (fast!)
- Regenerate in background (if stale)
- Next visitor gets fresh version
Monitoring and Debugging
Check ISR Status
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug);
if (process.env.NODE_ENV === 'development') {
console.log('Rendering post:', post.slug, 'at', new Date().toISOString());
}
return { title: post.title };
}
Monitor Revalidation
export async function POST(request: Request) {
const { paths } = await request.json();
console.log('Revalidating paths:', paths, 'at', new Date().toISOString());
for (const path of paths) {
await revalidatePath(path);
}
return Response.json({ revalidated: true, paths, timestamp: Date.now() });
}
Response Headers
ISR responses include cache headers:
Cache-Control: s-maxage=300, stale-while-revalidate
X-Vercel-Cache: HIT (or MISS, STALE)
Best Practices
- Start with ISR: Use ISR as default, optimize to static or SSR as needed
- On-Demand for Content: Use on-demand revalidation for WordPress content
- Time-Based for Aggregates: Use time-based for archives, categories, searches
- Static for Stability: Use static for rarely-changing pages
- Avoid SSR: Only use SSR when absolutely necessary
- Monitor Performance: Track cache hit rates and page load times
- Test Revalidation: Verify webhook revalidation works correctly
- Consider Build Time: Balance generateStaticParams with deployment speed
Troubleshooting
ISR Not Updating
Problem: Pages not revalidating after WordPress updates.
Solutions:
- Check webhook is configured correctly in WordPress
- Verify
REVALIDATION_SECRETmatches between WordPress and Next.js - Test revalidation endpoint manually:
POST /api/revalidate - Check Next.js logs for revalidation events
Build Timeouts
Problem: Build fails due to too many pages.
Solutions:
- Limit
generateStaticParams()to critical pages - Set
dynamicParams = truefor on-demand generation - Use incremental builds if available
- Consider build time limits in your deployment platform
Stale Content
Problem: Content appears stale even with ISR.
Solutions:
- Check
revalidatevalue isn't too high - Verify time-based revalidation is working
- Implement on-demand revalidation via webhook
- Clear CDN cache if applicable
See Also
- Configuration Reference - Full rendering config options
- Environment Variables - Required env vars
- Runtime Usage - Using rendering config in code