Prerendering

Learn how to serve dynamic content on prerendered and statically generated pages.

If you prerender your Next.js site for performance, you might worry that you can no longer run A/B tests, tailor content to each visitor, or update it from your CMS. You can. This guide explains why prerendering and dynamic content seem to be at odds, and shows the two ways to combine them so you can pick the right one for each page.

Static vs. dynamic content

Next.js renders pages statically at build time by default. That is great for performance, but it also means the HTML is the same for everyone the moment it is generated.

Dynamic content, on the other hand, is decided at request time, whether that is an A/B test variant, content tailored to the visitor, or the latest content from your CMS. A fully prerendered page is frozen by the time the visitor sees it, so on its own it cannot adapt on the fly.

The good news is that you do not have to choose between the two for your whole site. Next.js lets you render each route statically or dynamically, and the SDK ships both server-side functions and client-side hooks. So you keep prerendering everywhere it makes sense and change the approach only for the pages that need dynamic content.

Choosing an approach

There are two ways to serve dynamic content on a page without giving up prerendering across the rest of your site.

Server-side renderingClient-side rendering
Final content on first load
No content flicker
Visible to crawlers
Served as a static file
Runs without a server

For most cases we recommend rendering these pages on the server. The experience is smoother, there is no content flicker, and the content is visible to search engines. Reach for client-side rendering when a page must remain a fully static file, for example when it is served from a CDN with no server at all.

Render on the server

To serve dynamic content on the server, fetch it with fetchContent from @croct/plug-next/server. Because it reads the incoming request, Next.js renders the route dynamically, so the right content is decided per request and is already in the first HTML response.

app/page.jsx
123456789101112131415
import {fetchContent} from '@croct/plug-next/server';
// Optional: make dynamic rendering explicit.export const dynamic = 'force-dynamic';
export default async function HomePage() {  const {content} = await fetchContent('home-hero');
  return (    <section>      <strong>{content.title}</strong>      <p>{content.subtitle}</p>    </section>  );}

The rest of your site stays prerendered, so only the pages that need it are rendered on the server.

Render on the client

When a page must stay static, keep it prerendered and load the content in the browser. The page ships static, and the content updates right after it appears.

Use the client hooks and components from @croct/plug-next inside a 'use client' component, with your app wrapped in the <CroctProvider> added during integration.

These hooks fetch in the browser and need an initial value, or a <Suspense> boundary, to render during prerendering. The initial content is shown to visitors and crawlers until the content loads.

components/HomeHero.jsx
12345678910111213141516171819
'use client';
import {useContent} from '@croct/plug-next';
export function HomeHero() {  const {content} = useContent('home-hero', {    initial: {      title: 'Welcome to Croct!',      subtitle: 'The easiest way to personalize your application.',    },  });
  return (    <section>      <strong>{content.title}</strong>      <p>{content.subtitle}</p>    </section>  );}