david kimani
Work with me

Your ISR route rules might be doing nothing

I moved a Nuxt site to a plain Node server and every route rule silently stopped working. Nothing warned me, nothing failed, and the site got slower forever.

The rules that were comments

I moved a Nuxt site from a managed platform to a plain Node server last week. Same build, same route rules, same everything. It worked immediately, which is the part that should have worried me.

The config had this:

ts
routeRules: {
  '/': { isr: 60 },
  '/blog/**': { isr: 30 },
}

Incremental static regeneration: serve a cached render, refresh it in the background. On the old host that is exactly what happened.

On the new one, none of it happened. Not one of those rules did anything, and nothing anywhere said so.

isr belongs to the platform, not to Nitro

isr is not implemented by Nitro. It is read by Nitro’s platform presets. The Vercel preset translates it into Vercel’s revalidation config, the Netlify preset into Netlify’s, the Cloudflare preset into Cloudflare’s. Those presets are the entire implementation.

The node-server preset has no handler for it. It does not warn. It does not fail the build. The key is simply never read, so the rule is a comment with a colon in it.

The site kept working. It re-rendered every page and re-fetched the CMS on every single request, and served correct HTML while doing it. That is the failure I find most expensive: not broken, just quietly doing the slow thing forever.

swr is the one that works anywhere

swr is Nitro’s own stale-while-revalidate, implemented in its generic cache layer, so it works on every preset:

ts
routeRules: {
  '/': { swr: 60 },
  '/blog/**': { swr: 30 },
}

Nitro normalises swr: 60 into cache: { swr: true, maxAge: 60 }. A request inside the window is served from cache. The first request after it gets the stale copy while a fresh render happens behind it.

The numbers did not change. Only the key did.

Two things that came with it

The cache has to live somewhere. Nitro’s default is memory, which sounds fine until you remember that Passenger idles an app out after a few minutes of no traffic and spawns a fresh process on the next request. On a quiet site a memory cache is empty almost every time somebody arrives, which is the exact traffic pattern a new site has. It writes to disk now.

The cache is also keyed by route, not by build. Deploy without clearing it and you serve the last release’s HTML as though it were current, for as long as the max age says. The deploy step removes the cache directory before restarting.

How to check yours

Grep your config for isr, then check which preset you actually build with. If those two do not agree, your caching is decorative.

bash
grep -rn "isr:" nuxt.config.ts

I added a test that fails if isr ever comes back to that file. One line, against a mistake that costs nothing to make and gives no signal at all.

Share
XLinkedIn

Enjoyed this post? Subscribe for more. No noise, unsubscribe anytime.

Related posts