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
ISR only works on deployed Vercel, not in local dev. Make sure you've deployed with git push and are testing against your production or preview URL, not localhost:5173.
This is how stale-while-revalidate works. The first request after expiration gets the stale page and triggers a background regeneration. The next request gets the fresh version. Refresh twice.
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)API routes (+server.ts) are not affected by ISR. They always run dynamically. Use Cache-Control headers for API caching (covered in lesson 4.3).
Was this helpful?