Vercel Logo

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

  1. Export a config object with isr.expiration from +page.server.ts
  2. Deploy to Vercel
  3. 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:

  1. Uncomment the ISR config in src/routes/+page.server.ts
  2. Set the expiration to 300 seconds (5 minutes)
  3. 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 fetchedAt timestamp in the page data tells you when the data was actually fetched vs when you're seeing the cached version
  • Check the x-vercel-cache response header to see if the page was served from cache

Try It

  1. Enable the ISR config:

    src/routes/+page.server.ts
    export const config = {
      isr: {
        expiration: 300 // Revalidate every 5 minutes
      }
    };
  2. Deploy and visit the page:

    $ git add -A && git commit -m "feat(isr): enable 5-minute caching" && git push
  3. Check response headers (first visit):

    x-vercel-cache: MISS
    

    The page was generated fresh.

  4. Refresh the page:

    x-vercel-cache: HIT
    

    Served from cache. Notice the "Last updated" timestamp stays the same.

  5. Wait 5 minutes and refresh:

    x-vercel-cache: STALE
    

    You got the stale cached version instantly. Vercel is regenerating the page in the background. The next request will show HIT with a new timestamp.

Commit

git add -A
git commit -m "feat(isr): enable 5-minute caching on conditions dashboard"
git push

Done-When

  • config.isr.expiration is set to 300 in +page.server.ts
  • Deployed page shows x-vercel-cache: HIT on subsequent requests
  • "Last updated" timestamp stays the same between cached requests
  • After expiration, page revalidates in the background

Solution

src/routes/+page.server.ts
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

x-vercel-cache always shows MISS

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.

Page still shows old data after expiration

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)
ISR only applies to page server load

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).