Skip to main content

Next.js CSR Preset

Preset file: presets/nextjs-csr.toml

Works with Next.js pages that rely on client-side data fetching without getServerSideProps or getStaticProps.

When PRISM is Needed

Next.js supports multiple rendering strategies. PRISM is most useful for pages that fetch data client-side:

Rendering StrategyPRISM Needed?
getServerSideProps (SSR)No -- HTML is already rendered server-side
getStaticProps + getStaticPaths (SSG)No -- HTML is pre-built at build time
getStaticProps with revalidate (ISR)No -- HTML is pre-built and revalidated
Client-side fetching (useEffect, useSWR, React Query)Yes -- page shell is empty until JS runs
Hybrid (SSR shell + client-side enrichment)Maybe -- depends on what content is client-rendered
Static export (next export)Yes -- no server, all rendering is client-side

Key Configuration

[render]
wait_for = "networkidle"
timeout_secs = 15

[render.postprocess]
enabled = true
strip_scripts = true
strip_noscript = true
strip_comments = true
strip_event_handlers = true
strip_hydration_attrs = true # Removes data-reactroot
resolve_lazy_images = true

[render.content_validation]
enabled = true
min_text_length = 100
require_title = true
min_html_bytes = 1024

NEXT_DATA Handling

Next.js embeds a <script id="__NEXT_DATA__"> tag containing the page props as JSON. When strip_scripts = true, this script is removed along with all other scripts.

This is correct for bot-only mode -- bots do not need the hydration data. The rendered HTML already contains all the content.

If you need to preserve __NEXT_DATA__ (for example, in render-all mode where the page needs to hydrate), disable script stripping:

[render.postprocess]
strip_scripts = false

Hydration Attributes

Next.js uses data-reactroot on the root element (inherited from React). With strip_hydration_attrs = true, this is removed from the rendered output.

Route Exclusions

The preset excludes Next.js build artifacts and API routes:

[routes]
exclude = [
"/api/**",
"/_next/**",
"**/*.js", "**/*.css", "**/*.json", "**/*.xml",
"**/*.png", "**/*.jpg", "**/*.gif", "**/*.svg", "**/*.ico",
"**/*.woff", "**/*.woff2", "**/*.ttf", "**/*.wasm", "**/*.map",
"/manifest.json", "/sw.js", "/robots.txt", "/sitemap.xml",
]

The /_next/** exclusion is important -- it covers all Next.js static assets, chunks, and webpack hot module replacement endpoints.

SPA Status Codes

For Next.js apps with client-side 404 handling, use the status meta tag:

import Head from 'next/head';

function Custom404() {
return (
<>
<Head>
<title>Page Not Found</title>
<meta name="render:status_code" content="404" />
</Head>
<h1>404 - Page Not Found</h1>
</>
);
}

Enable in your PRISM config:

[render]
status_from_meta = true

Partial Rendering Use Case

For hybrid Next.js apps where some routes use SSR and others use client-side fetching, you can scope PRISM to only the CSR routes:

[routes]
include = [
"/dashboard/**",
"/search/**",
"/user/**",
]
exclude = [
"/api/**",
"/_next/**",
# ... asset patterns
]

This way, SSR routes are proxied directly (they already have rendered HTML) while CSR routes go through PRISM's rendering pipeline.

Full Preset

presets/nextjs-csr.toml
[render]
wait_for = "networkidle"
timeout_secs = 15

[render.postprocess]
enabled = true
strip_scripts = true
strip_noscript = true
strip_comments = true
strip_event_handlers = true
strip_hydration_attrs = true
resolve_lazy_images = true

[render.content_validation]
enabled = true
min_text_length = 100
require_title = true
min_html_bytes = 1024

[routes]
include = ["/**"]
exclude = [
"/api/**",
"/_next/**",
"**/*.js", "**/*.css", "**/*.json", "**/*.xml",
"**/*.png", "**/*.jpg", "**/*.gif", "**/*.svg", "**/*.ico",
"**/*.woff", "**/*.woff2", "**/*.ttf", "**/*.wasm", "**/*.map",
"/manifest.json", "/sw.js", "/robots.txt", "/sitemap.xml",
]