# 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](https://nextjs.org/docs/app/getting-started/caching), 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.

> **Good to know: What about fully static sites?**
>
> This guide assumes you deploy with a Next.js server, where each route can render statically or dynamically. If you use a fully static export (`output: 'export'`) instead, there is no server at runtime. The SDK can run entirely on the client side, so your content still works, as shown in [Render on the client](#client).

## 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 rendering | Client-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 \[#server]

To serve dynamic content on the server, fetch it with [`fetchContent`](api/functions/fetch-content) 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 router — JavaScript**

```jsx
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>
  );
}
```

**App router — TypeScript**

```tsx
import type {ReactElement} from 'react';
import {fetchContent} from '@croct/plug-next/server';

// Optional: make dynamic rendering explicit.
export const dynamic = 'force-dynamic';

export default async function HomePage(): Promise<ReactElement> {
  const {content} = await fetchContent('home-hero');

  return (
    <section>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
    </section>
  );
}
```

**Page router — JavaScript**

```jsx
import {fetchContent} from '@croct/plug-next/server';

export const getServerSideProps = async context => ({
  props: await fetchContent('home-hero', {route: context}),
});

export default function HomePage({content}) {
  return (
    <section>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
    </section>
  );
}
```

**Page router — TypeScript**

```tsx
import type {ReactElement} from 'react';
import type {GetServerSideProps} from 'next';
import {fetchContent} from '@croct/plug-next/server';

export const getServerSideProps: GetServerSideProps = async context => ({
  props: await fetchContent('home-hero', {route: context}),
});

export default function HomePage({content}): ReactElement {
  return (
    <section>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
    </section>
  );
}
```

> **Good to know: Stream the dynamic content**
>
> With the App Router, you can wrap the component that calls `fetchContent` in a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary. Next.js then sends the rest of the page right away and [streams](https://nextjs.org/docs/app/guides/streaming#granular-streaming-with-suspense) the content in as soon as it is ready, so a dynamic section never holds up the rest of the page.

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

## Render on the client \[#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.

> **Good to know: When is client-side rendering a good fit?**
>
> Client-side rendering works well whenever a brief loading state is acceptable. Below-the-fold sections are a common example, such as a recommendations block, a "related articles" list, or a promo banner further down the page, where a skeleton placeholder while the content loads does not hurt the experience.

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](integration).

These hooks fetch in the browser and need an [`initial`](api/hooks/use-content#options-initial-prop) value, or a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary, to render during prerendering. The initial content is shown to visitors and crawlers until the content loads.

**JavaScript**

```jsx
'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>
  );
}
```

**TypeScript**

```tsx
'use client';

import type {ReactElement} from 'react';
import {useContent} from '@croct/plug-next';

export function HomeHero(): ReactElement {
  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>
  );
}
```

> **Avoid build-time fetching**
>
> Do not call `fetchContent` on a route you want to keep static. Because it reads the request, it opts the route into dynamic rendering. To serve dynamic content on a static route, use the client hooks and components from `@croct/plug-next` instead.

## Explore

- [Content rendering](content-rendering): Learn how to fetch and render content for your slots.
- [Static and dynamic rendering](https://nextjs.org/docs/app/getting-started/caching): See how Next.js decides whether a route is static or dynamic.
