Skip to content
Docs

How to reduce ISR Writes by shrinking cached output

Reduce ISR Write units by finding large changed routes and removing unnecessary data from their cached output.

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.

Copy link to headingUnderstand the write-unit model

Vercel durably stores ISR output in the ISR cache. ISR Reads and Writes are measured in 8 KB units. 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 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.

Copy link to headingBefore 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.

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

Copy link to headingRemove 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 calls out new Date() and Math.random() as common causes of unexpected writes.

Check cached rendering code for new Date(), Date.now(), Math.random(), and generated UUIDs. The agent prompts 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.

Copy link to headingReduce 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 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. That guide covers client boundaries, component trees, large props, and local .rsc output in more detail.

Copy link to headingTrim Pages Router page data

In the Pages Router, getStaticProps 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 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.

Copy link to headingReduce 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 directory, 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.

Copy link to headingSet up your coding agent

Before you run either prompt, install the Vercel 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.

Copy link to headingInvestigate 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.

Copy link to headingInvestigate 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.

Copy link to headingMeasure 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.

More Next.js guides