# How to reduce ISR Writes by shrinking cached output

**Author:** Jonas Herrmannsdörfer, Mark Knichel

---

A route can revalidate at the right time and still use many ISR Write units when its changed output is large. Find the routes that write the most data, then reduce their HTML, React Server Component payload, or Pages Router page data.

This guide focuses on output size. If you haven't confirmed which routes, tags, or cache reasons drive the increase, start with [How To Reduce ISR Revalidation Costs](https://vercel.com/kb/guide/how-to-reduce-isr-revalidation-costs).

## Understand the write-unit model

Vercel durably stores ISR output in the ISR cache. [ISR Reads and Writes are measured in 8 KB units](https://vercel.com/docs/incremental-static-regeneration/limits-and-pricing#written-data). Larger changed output needs more units each time Vercel stores it.

A revalidation doesn't automatically create billed ISR Writes. When regeneration produces the same output as the previous version, Vercel doesn't incur another ISR Write. When the stored output changes, its size determines the write units. Values such as the current date, timestamps, random numbers, and generated IDs can change the rendered output on every regeneration. This can happen even when the route's source data has not changed. See [Optimizing ISR Reads and Writes](https://vercel.com/docs/incremental-static-regeneration/limits-and-pricing#optimizing-isr-reads-and-writes) for common causes of unexpected writes.

Broad invalidation still matters. It can make many entries stale, which can lead to more later regenerations, ISR Reads, Function invocations, and changed outputs written as users visit those entries.

## Before you start

This guide assumes you've found routes where relatively few changed regenerations produce many written bytes or write units. If you still need to distinguish between revalidation frequency, invalidation scope, and output size, start with [How To Reduce ISR Revalidation Costs](https://vercel.com/kb/guide/how-to-reduce-isr-revalidation-costs).

Use each affected route's current output as the baseline. Don't apply an arbitrary size threshold.

## Remove values that change every regeneration

A timestamp, random value, or volatile identifier can make output differ even when its source data is unchanged. Vercel's [ISR usage and pricing documentation](https://vercel.com/docs/incremental-static-regeneration/limits-and-pricing#optimizing-isr-reads-and-writes) calls out `new Date()` and `Math.random()` as common causes of unexpected writes.

Check cached rendering code for `new Date()`, [`Date.now`](http://Date.now)`()`, `Math.random()`, and generated UUIDs. The [agent prompts](#investigate-an-app-router-project-with-an-agent) below include repository searches for these patterns.

Move those values out of cached output unless they are part of the page content. For example, render a relative timestamp from stable source data instead of storing the regeneration time in the page.

## Reduce App Router output

The App Router sends an RSC payload that includes the rendered Server Component result, references to Client Component JavaScript, and props passed from Server Components to Client Components. This payload contributes to the output generated for an ISR route. The [Next.js Server and Client Components documentation](https://nextjs.org/docs/app/getting-started/server-and-client-components) explains the rendering model.

Large payloads often come from:

- Passing full content, product, or GraphQL responses into Client Components
  
- Placing `'use client'` too high in the component tree
  
- Rendering many Client Components in the initial view
  
- Storing route-specific data in a global provider
  
- Passing data through a provider when one small component needs it
  

Keep large records on the server. Pass only the fields needed for interaction.

`import { ProductActions } from './product-actions' export default async function ProductPage({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params const product = await getProduct(slug) return ( <main> <h1>{product.name}</h1> <p>{product.description}</p> {/* Only these fields cross the Server-to-Client Component boundary. */} <ProductActions id={product.id} name={product.name} price={product.price} /> </main> ) }`

Avoid passing the full record when the Client Component uses only a few fields:

`// This serializes every property that crosses the client boundary. <ProductActions product={product} />`

Keep React Context providers close to the components that use them. A Context provider in the root layout can add route-specific data to every cached page. Split stable global state from page state, and keep non-interactive data in Server Components.

For a complete RSC investigation workflow, see [How to Optimize RSC Payload Size](https://vercel.com/kb/guide/how-to-optimize-rsc-payload-size). That guide covers client boundaries, component trees, large props, and local `.rsc` output in more detail.

## Trim Pages Router page data

In the Pages Router, [`getStaticProps`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props) returns props that Next.js serializes for the page. The browser can read them in `__NEXT_DATA__`. Returning a full content or GraphQL response increases cached output even when the UI uses only part of it.

Common causes include:

- Full records with internal metadata
  
- GraphQL responses with unused nested fields
  
- Product objects with inventory, variants, and recommendations that the page doesn't render
  
- Large arrays used only after a client interaction
  
- Provider values built from the complete `pageProps` object
  

Select only the fields the page needs before returning props:

`import type { GetStaticProps, InferGetStaticPropsType } from 'next' type ProductPageProps = { product: { id: string name: string description: string price: number } } export const getStaticProps = (async ({ params }) => { const slug = String(params?.slug) const product = await getProductFromDatabase(slug) return { props: { product: { id: product.id, name: product.name, description: product.description, price: product.price, }, }, } }) satisfies GetStaticProps<ProductPageProps> export default function ProductPage({ product, }: InferGetStaticPropsType<typeof getStaticProps>) { return ( <main> <h1>{product.name}</h1> <p>{product.description}</p> <p>{product.price}</p> </main> ) }`

Query fewer fields at the source where possible. Trimming after a broad request reduces page output, but a focused database or GraphQL selection also reduces origin work and transfer.

Next.js reports a [large page data warning](https://nextjs.org/docs/messages/large-page-data) when serialized page props exceed its warning threshold. Treat the warning as an investigation signal, then compare the affected route with its own measured baseline.

## Reduce repeated HTML and inline assets

HTML can grow through repeated card markup, large hidden sections, inline SVGs, and other assets embedded in every page.

Check for:

- Large SVGs repeated in cards, navigation, or footers
  
- Full content rendered both visibly and in hidden UI
  
- Long lists rendered on the first response when pagination would work
  
- Repeated structured data or serialized state
  

When an SVG doesn't need direct DOM styling or animation, serve it as a static file instead of repeating its markup in every regenerated page. Next.js serves files from the [`public`](https://nextjs.org/docs/app/api-reference/file-conventions/public-folder) [directory](https://nextjs.org/docs/app/api-reference/file-conventions/public-folder), which lets the browser cache the asset separately.

Don't move every SVG out of the page by default. Keep it inline when direct styling, animation, or accessibility behavior requires access to its elements. Measure the route after each change.

## Set up your coding agent

Before you run either prompt, [install the Vercel Plugin](https://vercel.com/plugin) if your coding agent supports it. The plugin gives Claude Code, Cursor, and Codex Vercel-specific skills and current product context.

Your agent still needs read access to the repository and access to the Vercel CLI.

## Investigate an App Router project with an agent

Use this prompt:

``Investigate large cached output for the affected ISR routes in this Next.js App Router project. Start read-only. Use the Vercel CLI to confirm the current team and linked project. Run `vercel metrics schema` before querying metrics. Use `vercel metrics` to rank ISR routes by written bytes and write units, then compare their revalidation volume. Correlate the result with deployments and source-data changes. Return the commands used and the measured baseline for each affected route. Search the repository for client boundaries, providers, inline output, and unstable values. Include searches equivalent to: - `rg "['\\\"]use client['\\\"]|createContext|<.*Provider" app src` - `rg "<svg|dangerouslySetInnerHTML|JSON\\.stringify" app src` - `rg "new Date\\(|Date\\.now\\(|Math\\.random\\(|randomUUID\\(" app src` Inspect RSC payload contributors, large objects passed from Server Components to Client Components, client boundaries placed high in the tree, global providers, repeated inline SVG or HTML, long initial lists, duplicated serialized state, and non-deterministic values such as timestamps or random IDs. For each finding, show the affected route and evidence by file and line. Explain which fields or markup can stay on the server, be selected earlier, move below a client boundary, or load separately. Rank changes by likely output reduction. Do not invent a byte threshold and do not edit files.`` ## Investigate a Pages Router project with an agent Use this prompt: ``Investigate large cached output for the affected ISR routes in this Next.js Pages Router project. Start read-only. Use the Vercel CLI to confirm the current team and linked project. Run `vercel metrics schema` before querying metrics. Use `vercel metrics` to rank ISR routes by written bytes and write units, then compare their revalidation volume. Correlate the result with deployments and source-data changes. Return the commands used and the measured baseline for each affected route. Search the repository for getStaticProps, page-level providers, inline output, and unstable values. Include searches equivalent to: - `rg "getStaticProps|pageProps|createContext|<.*Provider" pages src` - `rg "<svg|dangerouslySetInnerHTML|JSON\\.stringify" pages src` - `rg "new Date\\(|Date\\.now\\(|Math\\.random\\(|randomUUID\\(" pages src` Inspect getStaticProps return values, __NEXT_DATA__, full content or GraphQL responses, unused nested fields, large arrays, providers built from pageProps, repeated inline SVG or HTML, duplicated serialized state, and non-deterministic values such as timestamps or random IDs. For each finding, show the affected route and evidence by file and line. Recommend smaller source queries and the exact fields the page needs. Rank changes by likely output reduction. Do not invent a byte threshold and do not edit files.`` ## Measure before and after Use the same production route and representative source data for both measurements. 1. Record the route's written bytes, write units, and revalidation volume before the change.     2. Confirm frequency and invalidation scope are already appropriate.     3. Remove non-deterministic values that don't belong in cached output.     4. For App Router routes, compare RSC output and server-to-client props.     5. For Pages Router routes, compare serialized props and `__NEXT_DATA__`.     6. Compare HTML and repeated inline assets.     7. Deploy the change and measure the same route over a comparable window.     8. Check that page behavior, SEO content, and accessibility haven't changed.     9. Confirm written bytes and write units decreased without increasing regeneration frequency.     If the affected routes also regenerate more often than their data changes, follow [How to move from time-based to on-demand revalidation](https://vercel.com/kb/guide/how-to-move-to-on-demand-revalidation).

---

[View full KB sitemap](/kb/sitemap.md)
