# Content rendering

Learn how to fetch and render content for your slots.

This guide provides practical examples of how to use the Next.js SDK to fetch and render a [Slot](/explanation/slot) on the server side.

## Basic usage

Start by adding the slot using the CLI:

**Command to add a slot to your project**

```sh
croct@latest add slot --example
```

The CLI will prompt you to select the slot you want to add.

Once selected, it takes care of everything — from generating the TypeScript types to adding a working example to your project. You can use this example as a reference for your own implementation.

> **Good to know: How do types and fallback content work?**
>
> The CLI [generates type definitions](/reference/cli/type-generation) so that calls like [`fetchContent('home-hero@2')`](api/functions/fetch-content) return the correct content type with full autocomplete. It also [downloads default content](/reference/cli/fallback-content) that the SDK uses as automatic fallback when dynamic content is unavailable.

### How it works

Let's say you have a slot called `home-hero` for the hero section of your homepage.

To fetch content on the server side, you can use the [`fetchContent`](api/functions/fetch-content) function. This function receives the ID of the slot you want to fetch and returns a promise that resolves to the content.

You can retrieve the content for this slot in a component as follows:

**Basic example**

**App router — JavaScript**

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

export async function HomeHero() {
  const {content} = await fetchContent('home-hero');

  return (
    <div>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
      <a href={content.button.link}>{content.button.label}</a>
    </div>
  );
}
```

**App router — TypeScript**

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

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

  return (
    <div>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
      <a href={content.button.link}>{content.button.label}</a>
    </div>
  );
}
```

**Page router — JavaScript**

**pages/index.jsx**

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

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

export default function HomePage({content}) {
  return (
    <div>
      <HomeHero {...content} />
    </div>
  );
}
```

**components/HomeHero.jsx**

```js
export function HomeHero(props) {
  return (
    <div>
      <strong>{props.title}</strong>
      <p>{props.subtitle}</p>
      <a href={props.button.link}>{props.button.label}</a>
    </div>
  );
}
```

**Page router — TypeScript**

**pages/index.tsx**

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

type HomePageProps = {
  content: HomeHeroProps
}

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

export default function HomePage({content}: HomePageProps): ReactElement {
  return (
    <div>
      <HomeHero {...content} />
    </div>
  );
}
```

**components/HomeHero.tsx**

```tsx
import type {ReactElement} from 'react';

export type HomeHeroProps = {
  title: string;
  subtitle: string;
  button: {
    label: string;
    link: string;
  };
};

export function HomeHero(props: HomeHeroProps): ReactElement {
  return (
    <div>
      <strong>{props.title}</strong>
      <p>{props.subtitle}</p>
      <a href={props.button.link}>{props.button.label}</a>
    </div>
  );
}
```

For more information about the available options, refer to the documentation of the [`fetchContent`](api/functions/fetch-content) function.

> **Example: How to render content on the client side?**
>
> For client-side rendering, you can follow the [React examples](/reference/sdk/react/content-rendering#basic-usage) replacing the `@croct/plug-react` imports with `@croct/plug-next`:
>
> **Hook — JavaScript**
>
> ```diff
> -import {useContent} from '@croct/plug-react';
> +import {useContent} from '@croct/plug-next';
>
> export function HomeHero() {
>   const {content} = useContent('home-hero');
>
>   return (
>     <div>
>       <strong>{content.title}</strong>
>       <p>{content.subtitle}</p>
>       <a href={content.button.link}>{content.button.label}</a>
>     </div>
>   );
> }
> ```
>
> **Hook — TypeScript**
>
> ```diff
> import type {ReactElement} from 'react';
> -import {useContent} from '@croct/plug-react';
> +import {useContent} from '@croct/plug-next';
>
> export function HomeHero(): ReactElement {
>   const {content} = useContent('home-hero');
>
>   return (
>     <div>
>       <strong>{content.title}</strong>
>       <p>{content.subtitle}</p>
>       <a href={content.button.link}>{content.button.label}</a>
>     </div>
>   );
> }
> ```
>
> **Component — JavaScript**
>
> ```diff
> -import {Slot} from '@croct/plug-react';
> +import {Slot} from '@croct/plug-next';
>
> export function HomeHero() {
>   return (
>     <Slot id="home-hero">
>       {({content}) => (
>         <div>
>           <strong>{content.title}</strong>
>           <p>{content.subtitle}</p>
>           <a href={content.button.link}>{content.button.label}</a>
>         </div>
>       )}
>     </Slot>
>   );
> }
> ```
>
> **Component — TypeScript**
>
> ```diff
> import type {ReactElement} from 'react';
> -import {Slot} from '@croct/plug-react';
> +import {Slot} from '@croct/plug-next';
>
> export function HomeHero(): ReactElement {
>   return (
>     <Slot id="home-hero">
>       {({content}) => (
>         <div>
>           <strong>{content.title}</strong>
>           <p>{content.subtitle}</p>
>           <a href={content.button.link}>{content.button.label}</a>
>         </div>
>       )}
>     </Slot>
>   );
> }
> ```

## Typing

If you are using TypeScript, you can use the [`SlotContent`](api/types/slot-content) and [`ComponentContent`](api/types/component-content) types to type variables and props based on slot or component schemas:

```tsx
import type {SlotContent} from '@croct/plug-next';

type HeroProps = SlotContent<'home-hero@1'>;

export function HomeHero(props: HeroProps) {
  return <h1>{props.title}</h1>;
}
```

These types are automatically available when you add slots or components using the CLI. See [Type generation](/reference/cli/type-generation) for details.

## Fault tolerance

If you added the slot using the CLI, your application already has automatic [fallback content](/explanation/content/fallback-content). The CLI downloads default content that the SDK uses when a content request fails. You can optionally provide an explicit fallback to override it. See the [fallback hierarchy](/reference/cli/fallback-content#fallback-hierarchy) for how the SDK resolves content.

If you want to specify a custom fallback, pass it as an option:

**App router — JavaScript**

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

export async function HomeHero() {
  const {content} = await fetchContent('home-hero', {
    fallback: {
      title: 'Welcome to Croct!',
      subtitle: 'The easiest way to personalize your application.',
      button: {
        label: 'Get started',
        link: '/signup',
      }
    },
  });

  return (
    <div>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
      <a href={content.button.link}>{content.button.label}</a>
    </div>
  );
}
```

**App router — TypeScript**

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

export async function HomeHero(): Promise<ReactElement> {
  const {content} = await fetchContent('home-hero', {
    fallback: {
      title: 'Welcome to Croct!',
      subtitle: 'The easiest way to personalize your application.',
      button: {
        label: 'Get started',
        link: '/signup',
      }
    },
  });

  return (
    <div>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
      <a href={content.button.link}>{content.button.label}</a>
    </div>
  );
}
```

**Page router — JavaScript**

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

export const getServerSideProps = async context => ({
  props: await fetchContent('home-hero', {
      route: context,
      fallback: {
        title: 'Welcome to Croct!',
        subtitle: 'The easiest way to personalize your application.',
        button: {
          label: 'Get started',
          link: '/signup',
        }
      },
    })
});

export default function HomePage({content}) {
  return (
    <div>
      <HomeHero {...content} />
    </div>
  );
}
```

**Page router — TypeScript**

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

type HomePageProps = {
  content: HomeHeroProps
}

export const getServerSideProps: GetServerSideProps<HomePageProps> = async context => ({
  props: await fetchContent('home-hero', {
      route: context,
      fallback: {
        title: 'Welcome to Croct!',
        subtitle: 'The easiest way to personalize your application.',
        button: {
          label: 'Get started',
          link: '/signup',
        }
      },
    })
});

export default function HomePage({content}: HomePageProps): ReactElement {
  return (
    <div>
      <HomeHero {...content} />
    </div>
  );
}
```

The SDK takes care of the rest, ensuring that your application will always have content to render, even if the fetch fails.

## Version control

You can lock a specific slot version to keep the content structure aligned with your application's expectations. This gives your team the freedom to evolve the structure over time without the risk of breaking things.

You can specify the version of the slot by passing a versioned ID in the form `<id>@<version>`. For example, passing `home-hero@2` will fetch the content for the `home-hero` slot in version 2. Not specifying a version number is the same as passing `home-hero@latest`, which will load the latest content.

**App router — JavaScript**

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

export async function HomeHero() {
  const {content} = await fetchContent('home-hero@2');

  return (
    <div>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
      <a href={content.button.link}>{content.button.label}</a>
    </div>
  );
}
```

**App router — TypeScript**

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

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

  return (
    <div>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
      <a href={content.button.link}>{content.button.label}</a>
    </div>
  );
}
```

**Page router — JavaScript**

```js
import {fetchContent} from '@croct/plug-next/server';
import {HomeHero} from '../components/HomeHero';

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

export default function HomePage({content}) {
  return (
    <div>
      <HomeHero {...content} />
    </div>
  );
}
```

**Page router — TypeScript**

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

type HomePageProps = {
  content: HomeHeroProps
}

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

export default function HomePage({content}: HomePageProps): ReactElement {
  return (
    <div>
      <HomeHero {...content} />
    </div>
  );
}
```

For more information, see [Slot versioning](/explanation/slot#versioning).

## Localization

To support multiple locales, you can use the [`preferredLocale`](api/functions/fetch-content#options-preferredlocale-prop) option to specify the locale of the content you want to retrieve. This is usually the locale of the user's browser or account.

If you are using [Internationalized Routing](https://nextjs.org/docs/advanced-features/i18n-routing), the SDK will automatically use the locale from the router. If you are not using internalization, or if there is no content available in that locale, the SDK will fall back to the [default locale of your workspace](https://app.croct.com/redirect/organizations/-organization-/workspaces/-workspace-/settings).

You can always specify a different locale as needed:

**App router — JavaScript**

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

export async function HomeHero() {
  const {content} = await fetchContent('home-hero', {
    preferredLocale: 'en-ca',
  });

  return (
    <div>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
      <a href={content.button.url}>{content.button.label}</a>
    </div>
  );
}
```

**App router — TypeScript**

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

export async function HomeHero(): Promise<ReactElement> {
  const {content} = await fetchContent('home-hero', {
    preferredLocale: 'en-ca',
  });

  return (
    <div>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
      <a href={content.button.url}>{content.button.label}</a>
    </div>
  );
}
```

**Page router — JavaScript**

```js
import {fetchContent} from '@croct/plug-next/server';
import {HomeHero} from '../components/HomeHero';

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

export default function HomePage({content}) {
  return (
    <div>
      <HomeHero {...content} />
    </div>
  );
}
```

**Page router — TypeScript**

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

type HomePageProps = {
  content: HomeHeroProps
}

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

export default function HomePage({content}: HomePageProps): ReactElement {
  return (
    <div>
      <HomeHero {...content} />
    </div>
  );
}
```

For more information, refer to the [`preferredLocale`](api/functions/fetch-content#options-preferredlocale-prop) documentation.

## Context variables

Sometimes you need to provide additional information to personalize or segment your users.

For example, if you are working on a SaaS application, you may want to personalize the content based on the subscription plan, quota usage, features, or any other application-specific information. You can achieve this by passing any relevant information to the [`attributes`](api/functions/fetch-content#options-context-attributes-prop) option:

**Context example**

**App router — JavaScript**

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

export async function UpgradeBanner() {
  const {content} = await fetchContent('upgrade-banner', {
    context: {attributes: {plan: 'premium'}},
  });

  return (
    <div>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
      <a href={content.button.url}>{content.button.label}</a>
    </div>
  );
}
```

**App router — TypeScript**

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

export async function UpgradeBanner(): Promise<ReactElement> {
  const {content} = await fetchContent('upgrade-banner', {
    context: {attributes: {plan: 'premium'}},
  });

  return (
    <div>
      <strong>{content.title}</strong>
      <p>{content.subtitle}</p>
      <a href={content.button.url}>{content.button.label}</a>
    </div>
  );
}
```

**Page router — JavaScript**

**pages/subscription.jsx**

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

export const getServerSideProps = async context => ({
  props: await fetchContent('upgrade-banner', {
      context: {attributes: {plan: 'premium'}},
      route: context
    })
});

export default function SubscriptionPage({content}) {
  return (
    <div>
      <UpgradeBanner {...content} />
    </div>
  );
}
```

**components/UpgradeBanner.jsx**

```jsx
export function UpgradeBanner(props) {
  return (
    <div>
      <strong>{props.title}</strong>
      <p>{props.subtitle}</p>
      <a href={props.button.url}>{props.button.label}</a>
    </div>
  );
}
```

**Page router — TypeScript**

**pages/subscription.tsx**

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

type SubscriptionPageProps = {
  content: UpgradeBannerProps
}

export const getServerSideProps: GetServerSideProps<SubscriptionPageProps> = async context => ({
  props: await fetchContent('upgrade-banner', {
      context: {attributes: {plan: 'premium'}},
      route: context
    })
});

export default function SubscriptionPage({content}: SubscriptionPageProps): ReactElement {
  return (
    <div>
      <UpgradeBanner {...content} />
    </div>
  );
}
```

**components/UpgradeBanner.tsx**

```tsx
import type {ReactElement} from 'react';

export type UpgradeBannerProps = {
  title: string;
  subtitle: string;
  button: {
    label: string;
    url: string;
  };
};

export function UpgradeBanner(props: UpgradeBannerProps): ReactElement {
  return (
    <div>
      <strong>{props.title}</strong>
      <p>{props.subtitle}</p>
      <a href={props.button.url}>{props.button.label}</a>
    </div>
  );
}
```

These values are then accessible as custom attributes in the [context variable](/reference/cql/context#evaluation):

```cql
context's plan is "premium"
```

Keep in mind that the context has some constraints on the number of attributes and levels of nesting. For more information, please refer to the [`attributes`](api/functions/fetch-content#options-context-attributes-prop) documentation.

## Explore

- [Slots](/explanation/slot): Learn how slots help you organize and personalize your content.
- [Fetch function](api/functions/fetch-content): Explore the function documentation and available options.
