Incremental Static Regeneration
The ski-alerts dashboard fetches live weather data for 5 resorts on every page load. That's 5 API calls per visitor. With 1,000 visitors per hour, you're making 5,000 weather API calls for data that changes maybe once every few minutes. ISR serves a cached version instantly and refreshes the data in the background.
Outcome
Enable ISR on the conditions dashboard so it serves cached pages and revalidates every 5 minutes.
Fast Track
- Export a
configobject withisr.expirationfrom+page.server.ts - Deploy to Vercel
- Verify the page loads instantly from cache with background revalidation
How ISR Works
First request:
User → Vercel Edge → Run load() → Fetch weather → Render page → Cache result → Return to user
Next request (within 5 minutes):
User → Vercel Edge → Return cached page instantly (0ms)
Request after expiration:
User → Vercel Edge → Return stale cached page instantly
→ Background: Run load() → Fetch weather → Update cache
The key insight: users always get a fast response. The revalidation happens in the background, so nobody waits for the weather API.
Hands-on exercise 4.1
Let's enable ISR on the conditions dashboard:
Requirements:
- Uncomment the ISR config in
src/routes/+page.server.ts - Set the expiration to 300 seconds (5 minutes)
- Deploy to Vercel and verify caching behavior
Implementation hints:
- The config is already in the starter file as a comment, so uncomment it
- ISR only works on Vercel (not in local dev), so you need to deploy to test it
- The
fetchedAttimestamp in the page data tells you when the data was actually fetched vs when you're seeing the cached version - Check the
x-vercel-cacheresponse header to see if the page was served from cache
Try It
-
Enable the ISR config:
src/routes/+page.server.tsexport const config = { isr: { expiration: 300 // Revalidate every 5 minutes } }; -
Deploy and visit the page:
$ git add -A && git commit -m "feat(isr): enable 5-minute caching" && git push -
Check response headers (first visit):
x-vercel-cache: MISSThe page was generated fresh.
-
Refresh the page:
x-vercel-cache: HITServed from cache. Notice the "Last updated" timestamp stays the same.
-
Wait 5 minutes and refresh:
x-vercel-cache: STALEYou got the stale cached version instantly. Vercel is regenerating the page in the background. The next request will show
HITwith a new timestamp.
Commit
git add -A
git commit -m "feat(isr): enable 5-minute caching on conditions dashboard"
git pushDone-When
config.isr.expirationis set to 300 in+page.server.ts- Deployed page shows
x-vercel-cache: HITon subsequent requests - "Last updated" timestamp stays the same between cached requests
- After expiration, page revalidates in the background
Solution
import { resorts } from '$lib/data/resorts';
import { fetchAllConditions } from '$lib/services/weather';
import type { PageServerLoad } from './$types';
export const config = {
isr: {
expiration: 300 // Revalidate every 5 minutes
}
};
export const load: PageServerLoad = async () => {
const conditions = await fetchAllConditions(resorts);
return {
conditions,
fetchedAt: new Date().toISOString()
};
};That's a two-line change from the starter: uncomment the config object. The expiration: 300 means:
- For 5 minutes after generation, serve the cached version
- After 5 minutes, serve the stale version but regenerate in the background
- The next request after regeneration gets the fresh version
Troubleshooting
Advanced: ISR Options
Bypass token to force regeneration on demand:
export const config = {
isr: {
expiration: 300,
bypassToken: 'my-secret-token'
}
};Then hit https://your-app.vercel.app/?__prerender_bypass=my-secret-token to force a fresh render. Useful for content updates that can't wait for expiration.
Per-route ISR for different expiration on different pages:
// Dashboard: refresh every 5 minutes (weather changes slowly)
// src/routes/+page.server.ts
export const config = { isr: { expiration: 300 } };
// Alerts page: no ISR (user-specific data, no caching)
// src/routes/alerts/+page.server.ts
// (no config needed -defaults to dynamic rendering)Was this helpful?