---
title: How to optimize Next.js and Sitecore Content SDK apps
description: Learn how to choose a rendering strategy, configure proxy middleware, and optimize costs for Next.js and Sitecore Content SDK apps on Vercel.
url: "https://vercel.com/kb/guide/how-to-optimize-next.js-sitecore-content-sdk"
published: 2026-09-30
last_updated: 2026-09-30
authors: Pat Beecher
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Sitecore Content SDK apps on Vercel can reduce latency and compute usage by serving cached content and limiting work on each request. The right configuration depends on how often content changes, which pages need personalization, and which Sitecore features your application uses.

This guide covers three areas: choosing a rendering strategy, configuring the proxy chain, and reducing resource usage. Start with rendering and caching, then optimize the requests and data transfers.

## Prerequisites

Before applying these recommendations, make sure you have:

- A SitecoreAI tenant, formerly XM Cloud, and a Content SDK application using `@sitecore-content-sdk/nextjs`. For a Sitecore JSS application, follow the [JSS optimization guide](https://vercel.com/kb/guide/how-to-optimize-next.js-sitecore-jss).
  
- A Vercel project hosting the application, with access to [Observability](https://vercel.com/docs/observability) and the Usage page in the dashboard.
  
- Working knowledge of [Next.js App Router](https://nextjs.org/docs/app), including Server and Client Components, route segments, and caching.
  
- Permission to configure Experience Edge webhooks and Vercel environment variables if you plan to invalidate caches after publishing.
  

The template comparisons below cover Content SDK’s Next.js 16 templates. Sitecore’s built-in App Router on-demand static revalidation (OSR) requires Content SDK 2.2 or later. Check your installed versions and generated configuration before applying template-specific changes.

Before making changes, record your application’s data transfer, function usage, external API requests, and cache hit rates. Use representative pages and a consistent traffic period to compare the results.

## Choose a rendering strategy

Choose how each page type renders based on its freshness and request-time requirements. A content page updated after publishing has different needs from a page that displays account-specific information.

| Strategy                                                 | How it works                                                                                                                                                   | When to use                                                                    |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Static rendering, including static site generation (SSG) | Prerenders a page and reuses its output across requests. Pages generated at build time are updated through a new deployment unless revalidation is configured. | Content that can remain unchanged between deployments.                         |
| Incremental Static Regeneration (ISR)                    | Serves cached page output and regenerates it after time-based or on-demand invalidation, without rebuilding the entire site.                                   | Published content that should remain cached between updates.                   |
| Partial Prerendering (PPR)                               | Combines a prerendered shell with dynamic sections that stream at request time. In Next.js 16, enable it through Cache Components.                             | Pages that combine reusable content with sections requiring request-time data. |
| Dynamic rendering, or server-side rendering (SSR)        | Renders the page at request time. Individual data requests may still be cached.                                                                                | Pages whose output depends on fresh or visitor-specific information.           |

These concepts can work together. ISR describes how cached output is refreshed; PPR describes how prerendered and dynamic content are combined. A PPR page can contain cached content that is revalidated after publishing.

React Server Components are also separate from this choice. A Server Component can participate in static or dynamic rendering. Moving code to a Server Component does not, by itself, make the page static.

### Choose a Content SDK template

Once you know which rendering and caching behavior you need, choose a Content SDK template:

- **Pages Router (**`nextjs`**)** uses the Pages Router structure familiar to developers working with JSS applications.
  
- **App Router (**`nextjs-app-router`**)** uses React Server Components and lets you configure caching and revalidation.
  
- **App Router with Cache Components (**`nextjs-app-router-cache-components`**)** enables PPR and includes tagged cache helpers and a revalidation endpoint.
  

Use the table below to choose the right starting point:

| Name                             | When to use                                                                                                                           |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Pages Router                     | Maintaining a Pages Router application or migrating a JSS application while retaining that router.                                    |
| App Router                       | Building with App Router while configuring your own caching and revalidation strategy.                                                |
| App Router with Cache Components | Combining cached content with dynamic sections, or using Sitecore’s built-in publish-driven revalidation after configuring a webhook. |

### What changes with App Router

If you’re moving from Pages Router, these changes affect routing, data fetching, and caching:

- **Routing:** `src/pages/[[...path]].tsx` becomes `src/app/[site]/[locale]/[[...path]]/page.tsx`. Site and locale become explicit route parameters.
  
- **Data fetching:** An async Server Component calling `client.getPage()` replaces `getStaticProps` or `getServerSideProps`. `generateStaticParams` replaces the route-enumeration role of `getStaticPaths`.
  
- **Build-time generation:** With the SSG option and static-path generation enabled, `getAppRouterStaticParams()` enumerates routes across `.sitecore/sites.json` and the locales in `src/i18n/routing.ts`. More combinations mean more build work. Set `GENERATE_STATIC_PATHS=false` or `generateStaticPaths: false` in `sitecore.config.ts` to defer supported static routes to their first request, trading shorter builds for slower initial responses.
  
- **Caching:** The Pages template’s SSG option includes `revalidate: 5`, enabling **ISR**. The standard App Router template does not include an equivalent interval; configure revalidation explicitly to refresh cached pages between deployments.
  
- **Internationalization:** `next-localization` in `_app.tsx` becomes `next-intl`, configured through `src/i18n/routing.ts` and `src/i18n/request.ts`.
  
- **Proxy middleware:** The Pages Router and App Router templates for Next.js 16 use `src/proxy.ts`. App Router adds `LocaleProxy` and `AppRouterMultisiteProxy`, which are required by its default route structure.
  
- **Cache Components:** The separate Cache Components template enables PPR and tagged cache helpers. Its caching configuration differs from the standard App Router template. Follow [Sitecore’s Cache Components guide](https://doc.sitecore.com/sai/en/developers/content-sdk/20/enable-cache-components.html) for implementation details.
  

### Refresh cached content with revalidation

Choose how cached content should update between deployments:

- **Time-based ISR:** The first request after the revalidation interval receives the cached page and triggers background regeneration. Choose an interval that meets your content freshness requirements.
  
- **On-demand revalidation:** A publish webhook invalidates relevant paths or cache tags. When fresh content becomes visible depends on the revalidation API and cache profile; invalidation does not necessarily regenerate the page immediately.
  

For the standard App Router template, configure caching and revalidation explicitly using the [Next.js ISR guide](https://nextjs.org/docs/app/guides/incremental-static-regeneration). For the Cache Components template, follow [Sitecore’s OSR guide](https://doc.sitecore.com/sai/en/developers/content-sdk/20/on-demand-static-revalidation-for-app-router-apps.html) to connect the included handler to a publish webhook. Keep preview and editing requests dynamic.

### Stream dynamic sections with useful fallbacks

For pages with request-time content, use Suspense boundaries with meaningful loading fallbacks so slower sections do not delay the rest of the page. Streaming alone does not make a page static or enable PPR.

With Cache Components enabled, follow [Sitecore’s guidance](https://doc.sitecore.com/sai/en/developers/content-sdk/20/enable-cache-components.html) for combining cached reads and dynamic sections. Dynamic sections still require request-time compute.

## Configure the proxy chain

After configuring page rendering and caching, review the work performed by the proxy on each matched request. Content SDK replaces JSS’s `MiddlewarePlugin` composition with `defineProxy(...proxies).exec(req)`, configured in `src/proxy.ts` in the Next.js 16 templates.

The default App Router chain is:

```plaintext
preview → botTracking → locale → multisite → redirects → personalize
```

Each matched request passes through this chain. Focus on removing unnecessary lookups while preserving the routing and editing behavior your application needs.

| Proxy                     | Purpose                                                               | Optimization guidance                                              |
| ------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `PreviewProxy`            | Authorizes preview and editing requests.                              | Retain it when using Sitecore editing or preview.                  |
| `BotTrackingProxy`        | Detects bots and sends analytics events.                              | Remove it if the application does not need Sitecore bot analytics. |
| `LocaleProxy`             | Resolves locale information and rewrites the route.                   | Required by the default `[locale]` route structure.                |
| `AppRouterMultisiteProxy` | Resolves the site from local configuration and rewrites the route.    | Required by the default `[site]` route structure.                  |
| `RedirectsProxy`          | Retrieves and applies Sitecore redirect rules.                        | Disable it, move rules elsewhere, or cache shared lookups.         |
| `PersonalizeProxy`        | Looks up personalization metadata and selects applicable experiences. | Disable it when unused or limit it to eligible routes.             |

Removing locale or multisite processing without changing the corresponding route structure can cause 404s. Even a single-site application may need these proxies. To learn more, see [Sitecore’s routing requirements](https://doc.sitecore.com/sai/en/developers/content-sdk/20/disable-multisite-and-locale-proxies.html).

### Disable unused features and scope individual proxies

Redirect and personalization lookups can delay a response when they require a Sitecore request. Personalization first looks up page metadata to determine whether personalization is configured; personalized pages can then require additional decision requests.

Disable each feature you do not use in `sitecore.config.ts`. This example disables both redirects and personalization:

```typescript
import { defineConfig } from '@sitecore-content-sdk/nextjs/config';

export default defineConfig({
  // Keep the rest of your existing configuration.
  redirects: { enabled: false },
  personalize: { enabled: false },
});
```

If a feature applies only to some routes, use that proxy’s `skip` predicate to bypass the others.

Restrict the global `config.matcher` only for requests that do not need any proxy processing. Excluding a content route from the matcher also skips locale, multisite, and preview handling—not just the expensive lookup.

### Choose where redirects should run

Move redirect processing out of the application proxy when the rules can be handled by Vercel’s routing layer.

| Redirect requirements                                                | Where to configure them                                                                                                   | Considerations                                                                                                       |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Known at build time, including wildcard, regex, or header conditions | `redirects()` in `next.config`                                                                                            | Avoids a Sitecore lookup per request. Changes require a deployment.                                                  |
| Simple path-to-path or URL redirects maintained in Sitecore          | [Bulk Redirects](https://vercel.com/docs/routing/redirects/bulk-redirects/getting-started), synchronized after publishing | Supports updates through the CLI or API. Verify matching behavior and publish or promote staged changes as required. |
| Sitecore rules requiring application-level matching                  | `RedirectsProxy`, with shared lookups cached where appropriate                                                            | Preserve supported Sitecore matching behavior. Custom conditions may require additional implementation.              |
| A redirect map maintained outside Sitecore                           | [Vercel Global Config](https://vercel.com/docs/global-config), read from the proxy                                        | Avoids Sitecore lookups, but the proxy still executes.                                                               |

Before moving redirects, test status codes, locale handling, query strings, and destination URLs. Check current product limits rather than treating all redirect mechanisms as interchangeable.

### Cache shared Sitecore lookups

When redirects or personalization must remain in the proxy, consider [Runtime Cache](https://vercel.com/docs/caching/runtime-cache) for shared lookup data. It provides a regional cache with time-to-live (TTL) and tag-based invalidation.

A cache hit avoids repeating the Sitecore lookup. A miss, expired entry, or eviction requires another fetch. Each region has its own cache, so a request in another region may also miss.

For personalization metadata, supply a custom `personalizeService` to `PersonalizeProxy`. Inside its `getPersonalizeInfo()` implementation:

1. Build a cache key containing the site, language, and path.
   
2. Read the entry with `getCache().get(key)`.
   
3. On a miss, fetch the metadata from the underlying Sitecore service, store it with a TTL and invalidation tag, and return it.
   
4. On a hit, return the cached metadata.
   

On a cache miss, call a separate Sitecore service instance or `super.getPersonalizeInfo()`. Calling the wrapper’s own method would cause recursion.

Cache shared metadata, not visitor-specific personalization decisions. For redirects, use a separate key for each site’s redirect map. Account for any underlying SDK cache so that a refreshed outer cache does not immediately store stale data again.

A publish webhook can call a protected API route that awaits `cache.expireTag(tag)`. For an Experience Edge webhook, configure the shared secret in the webhook’s custom headers and compare it with an environment variable in the receiving application.

Runtime Cache lookups and writes incur usage charges. Its invalidation is also separate from Next.js page revalidation: expiring entries written with `getCache()` does not refresh ISR pages. To learn more, see the [Runtime Cache API reference](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package).

## Optimize usage and costs

After configuring rendering and the proxy chain, use Vercel’s dashboard to identify the largest remaining sources of usage. Prioritize changes based on measured traffic, not assumptions about which resource should cost the most.

### Reduce data transfer

[Fast Data Transfer](https://vercel.com/docs/manage-cdn-usage#fast-data-transfer) measures the data transferred between Vercel’s CDN and visitors. HTML, JavaScript, CSS, images, fonts, React Server Component (RSC) responses, and downloadable files all contribute to data transfer when delivered over the network.

Fast Origin Transfer measures a separate part of delivery, including transfers involving functions and other Vercel origin services. Serving cached output can reduce origin work while still transferring bytes to visitors. Charges depend on your plan and usage.

For large PDFs, videos, and ZIP files, consider linking directly to Sitecore’s media delivery service or another suitable media host. Confirm the browser fetches the file directly rather than through a Vercel rewrite or route handler. Compare the other host’s delivery costs and performance before moving traffic.

### Reduce layout data and client payloads

Experience Edge supplies layout data for the requested route. Reduce unnecessary data at its source:

- Remove unused renderings and datasource references.
  
- Avoid large fields on datasources when those fields are not needed.
  
- Separate datasources where doing so lets pages retrieve less content.
  
- Request only required fields in component-level GraphQL queries.
  

In App Router, server-fetched data is not automatically sent wholesale to the browser. Transfer depends on the rendered output and serialized data, including props passed to Client Components. Keep Sitecore data in Server Components where possible and pass minimal props across client boundaries. See [Server and Client Components](https://nextjs.org/docs/app/getting-started/server-and-client-components).

For Pages Router, inspect `/_next/data` responses for oversized page-props JSON. Prefetching and client caching affect whether navigation needs another request.

### Limit unnecessary prefetching

In production, Next.js can prefetch routes when links to them enter the viewport. Large navigation menus and product listings can therefore transfer content for pages the visitor never opens.

Consider `prefetch={false}` for links visitors are unlikely to follow, or prefetch on hover when the visitor shows intent. Disabling prefetching can increase navigation latency, so compare both transfer and responsiveness. Static and dynamic routes also have different prefetch behavior. See the [prefetching guide](https://nextjs.org/docs/app/guides/prefetching).

### Optimize images and fonts

Use appropriately sized images and configure responsive `sizes` values so visitors do not download unnecessarily large variants. Review image dimensions, formats, and cache behavior using the [Image Optimization cost guide](https://vercel.com/docs/image-optimization/managing-image-optimization-costs).

When Vercel optimizes a remote image through `/_next/image`, its delivery still contributes to Vercel data transfer. Hosting the original elsewhere does not bypass that delivery path.

For fonts, consider `next/font/google` or `next/font/local` to self-host required font files and remove external font stylesheet requests. Load only necessary weights, styles, and character sets. Self-hosted fonts still contribute to transfer; compare file sizes and loading performance, including whether a variable font reduces total downloads.

### Reduce compute and external API requests

Use Observability to inspect function activity and outgoing requests to Sitecore. Repeated layout, redirect, or personalization lookups can reveal missing caches, overly broad proxy processing, or routes rendering dynamically when cached output would suffice.

Remove unnecessary requests and measure cache hit rates after changes. For applications using Observability Plus, also review event usage and the applicable pricing. Reducing repeated external calls can reduce both application work and recorded events.

For the remaining request-time rendering and API work, review [Fluid Compute](https://vercel.com/docs/fluid-compute) and your function configuration. Compare function duration and compute usage before and after changes.

## Validate your changes

Compare the updated application with your baseline under similar traffic conditions:

- Confirm which routes use static rendering, ISR, PPR, or dynamic rendering.
  
- Test initial requests and repeat visits to distinguish cache misses from hits.
  
- Publish content and verify that affected pages, shared content, and dictionaries refresh.
  
- Check redirects, locale routing, personalization, preview, and editing.
  
- Compare transfer, function activity, external requests, and cache hit rates.
  
- Check page loading and navigation performance after changing images, fonts, or prefetching.
  

Keep changes that improve the measurements without breaking content freshness or Sitecore functionality.

## Next steps

- Set up publish-driven content updates with [Sitecore’s on-demand revalidation guide](https://doc.sitecore.com/sai/en/developers/content-sdk/20/on-demand-static-revalidation-for-app-router-apps.html).
  
- Explore PPR and explicit caching with [Sitecore’s Cache Components guide](https://doc.sitecore.com/sai/en/developers/content-sdk/20/enable-cache-components.html).
  
- Learn when and how cached pages refresh in the [Next.js ISR guide](https://nextjs.org/docs/app/guides/incremental-static-regeneration).
  
- Move suitable redirect rules out of your proxy with [Vercel Bulk Redirects](https://vercel.com/docs/routing/redirects/bulk-redirects/getting-started).
  
- Find more ways to reduce image usage in [Vercel’s Image Optimization cost guide](https://vercel.com/docs/image-optimization/managing-image-optimization-costs).