---
title: "ISR"
description: "Configure Incremental Static Regeneration (ISR) to serve cached pages while revalidating content in the background."
canonical_url: "https://vercel.com/academy/svelte-on-vercel/isr"
md_url: "https://vercel.com/academy/svelte-on-vercel/isr.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-04-11T14:52:23.147Z"
content_type: "lesson"
course: "svelte-on-vercel"
course_title: "Svelte on Vercel"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# ISR

# 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:**

   ```typescript title="src/routes/+page.server.ts" {1-5}
   export const config = {
     isr: {
       expiration: 300 // Revalidate every 5 minutes
     }
   };
   ```

2. **Deploy and visit the page:**
   ```bash
   $ 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

```bash
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

```typescript title="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

\*\*Warning: 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`.

\*\*Warning: 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:

```typescript
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:

```typescript
// 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)
```

\*\*Note: 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).


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
