# Content rendering

Learn how to fetch and render content for your slots.

This guide provides practical examples of how to use the Vue SDK to fetch and render a [Slot](/explanation/slot) in your application.

## 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 [`useContent('home-hero@2')`](api/composables/use-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, you can use either the [`useContent`](api/composables/use-content) composable or the [`<Slot>`](api/components/slot-content) component, depending on whether you prefer an imperative or declarative approach.

Below is an example of how to fetch and render content using both methods:

**Composable — JavaScript**

```vue
<script setup>
import {useContent} from '@croct/plug-vue';

const {data} = useContent('home-hero');
</script>

<template>
  <div v-if="data">
    <strong>{{ data.title }}</strong>
    <p>{{ data.subtitle }}</p>
    <a :href="data.button.link">{{ data.button.label }}</a>
  </div>
</template>
```

**Composable — TypeScript**

```vue
<script setup lang="ts">
import {useContent} from '@croct/plug-vue';

const {data} = useContent('home-hero');
</script>

<template>
  <div v-if="data">
    <strong>{{ data.title }}</strong>
    <p>{{ data.subtitle }}</p>
    <a :href="data.button.link">{{ data.button.label }}</a>
  </div>
</template>
```

**Component — JavaScript**

```vue
<script setup>
import {Slot} from '@croct/plug-vue';
</script>

<template>
  <Slot id="home-hero" v-slot="{ content }">
    <div>
      <strong>{{ content.title }}</strong>
      <p>{{ content.subtitle }}</p>
      <a :href="content.button.link">{{ content.button.label }}</a>
    </div>
  </Slot>
</template>
```

**Component — TypeScript**

```vue
<script setup lang="ts">
import {Slot} from '@croct/plug-vue';
</script>

<template>
  <Slot id="home-hero" v-slot="{ content }">
    <div>
      <strong>{{ content.title }}</strong>
      <p>{{ content.subtitle }}</p>
      <a :href="content.button.link">{{ content.button.label }}</a>
    </div>
  </Slot>
</template>
```

If you are using a server-side rendering framework, you should also provide an initial content for pre-rendering, which will then be personalized on the client.

> **Example: How can I pre-render initial content on the server?**
>
> Pass the [`initial`](api/composables/use-content#options-initial-prop) option for pre-rendering on the server:
>
> **Composable — JavaScript**
>
> ```diff
> <script setup>
> import {useContent} from '@croct/plug-vue';
>
> const {data} = useContent('home-hero', {
> +  initial: {
> +    title: 'Welcome to Croct!',
> +    subtitle: 'The easiest way to personalize your application.',
> +    button: {
> +      label: 'Get started',
> +      link: '/signup',
> +    }
> +  },
> });
> </script>
>
> <template>
>   <div v-if="data">
>     <strong>{{ data.title }}</strong>
>     <p>{{ data.subtitle }}</p>
>     <a :href="data.button.link">{{ data.button.label }}</a>
>   </div>
> </template>
> ```
>
> **Composable — TypeScript**
>
> ```diff
> <script setup lang="ts">
> import {useContent} from '@croct/plug-vue';
>
> const {data} = useContent('home-hero', {
> +  initial: {
> +    title: 'Welcome to Croct!',
> +    subtitle: 'The easiest way to personalize your application.',
> +    button: {
> +      label: 'Get started',
> +      link: '/signup',
> +    }
> +  },
> });
> </script>
>
> <template>
>   <div v-if="data">
>     <strong>{{ data.title }}</strong>
>     <p>{{ data.subtitle }}</p>
>     <a :href="data.button.link">{{ data.button.label }}</a>
>   </div>
> </template>
> ```
>
> **Component — JavaScript**
>
> ```diff
> <script setup>
> import {Slot} from '@croct/plug-vue';
>
> +const initial = {
> +  title: 'Welcome to Croct!',
> +  subtitle: 'The easiest way to personalize your application.',
> +  button: {
> +    label: 'Get started',
> +    link: '/signup',
> +  }
> +};
> </script>
>
> <template>
>   <Slot id="home-hero" :initial="initial" v-slot="{ content }">
>     <div>
>       <strong>{{ content.title }}</strong>
>       <p>{{ content.subtitle }}</p>
>       <a :href="content.button.link">{{ content.button.label }}</a>
>     </div>
>   </Slot>
> </template>
> ```
>
> **Component — TypeScript**
>
> ```diff
> <script setup lang="ts">
> import {Slot} from '@croct/plug-vue';
>
> +const initial = {
> +  title: 'Welcome to Croct!',
> +  subtitle: 'The easiest way to personalize your application.',
> +  button: {
> +    label: 'Get started',
> +    link: '/signup',
> +  }
> +};
> </script>
>
> <template>
>   <Slot id="home-hero" :initial="initial" v-slot="{ content }">
>     <div>
>       <strong>{{ content.title }}</strong>
>       <p>{{ content.subtitle }}</p>
>       <a :href="content.button.link">{{ content.button.label }}</a>
>     </div>
>   </Slot>
> </template>
> ```

Since the content is fetched asynchronously, the component needs to handle loading and error states. You can do this using the composable's reactive properties or the component's named slots.

> **Using named slots**
>
> Use the `#loading` and `#error` named slots on the `<Slot>` component:
>
> **JavaScript**
>
> ```vue
> <script setup>
> import {Slot} from '@croct/plug-vue';
> </script>
>
> <template>
>   <Slot id="home-hero">
>     <template #default="{ content }">
>       <div>
>         <strong>{{ content.title }}</strong>
>         <p>{{ content.subtitle }}</p>
>         <a :href="content.button.link">{{ content.button.label }}</a>
>       </div>
>     </template>
>
>     <template #loading>
>       <div>Dynamic content...</div>
>     </template>
>
>     <template #error="{ error }">
>       <div>Something went wrong: {{ error.message }}</div>
>     </template>
>   </Slot>
> </template>
> ```
>
> **TypeScript**
>
> ```vue
> <script setup lang="ts">
> import {Slot} from '@croct/plug-vue';
> </script>
>
> <template>
>   <Slot id="home-hero">
>     <template #default="{ content }">
>       <div>
>         <strong>{{ content.title }}</strong>
>         <p>{{ content.subtitle }}</p>
>         <a :href="content.button.link">{{ content.button.label }}</a>
>       </div>
>     </template>
>
>     <template #loading>
>       <div>Dynamic content...</div>
>     </template>
>
>     <template #error="{ error }">
>       <div>Something went wrong: {{ error.message }}</div>
>     </template>
>   </Slot>
> </template>
> ```

> **Using composable state**
>
> Use the `isLoading` and `error` refs returned by the composable:
>
> **JavaScript**
>
> ```vue
> <script setup>
> import {useContent} from '@croct/plug-vue';
>
> const {data, isLoading, error} = useContent('home-hero');
> </script>
>
> <template>
>   <div v-if="isLoading">Dynamic content...</div>
>   <div v-else-if="error">Something went wrong: {{ error.message }}</div>
>   <div v-else-if="data">
>     <strong>{{ data.title }}</strong>
>     <p>{{ data.subtitle }}</p>
>     <a :href="data.button.link">{{ data.button.label }}</a>
>   </div>
> </template>
> ```
>
> **TypeScript**
>
> ```vue
> <script setup lang="ts">
> import {useContent} from '@croct/plug-vue';
>
> const {data, isLoading, error} = useContent('home-hero');
> </script>
>
> <template>
>   <div v-if="isLoading">Dynamic content...</div>
>   <div v-else-if="error">Something went wrong: {{ error.message }}</div>
>   <div v-else-if="data">
>     <strong>{{ data.title }}</strong>
>     <p>{{ data.subtitle }}</p>
>     <a :href="data.button.link">{{ data.button.label }}</a>
>   </div>
> </template>
> ```

For more information about the available options, refer to the documentation of the [`useContent`](api/composables/use-content) composable and [`<Slot>`](api/components/slot-content) component.

## 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:

```ts
import type {SlotContent} from '@croct/plug-vue';

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

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

> **Auto-provided**
>
> You can skip this step if you added the slot using the Croct CLI, since the fallback content is already included based on the slot's default content. See the [fallback hierarchy](/reference/cli/fallback-content#fallback-hierarchy) for how the SDK resolves content.

You should always provide a [fallback content](/explanation/content/fallback-content) to make your application resilient to unexpected errors, downtime, and network failures.

All you have to do is specify the content you want to use as a fallback:

**Composable — JavaScript**

```vue
<script setup>
import {useContent} from '@croct/plug-vue';

const {data} = useContent('home-hero', {
  fallback: {
    title: 'Welcome to Croct!',
    subtitle: 'The easiest way to personalize your application.',
    button: {
      label: 'Get started',
      link: '/signup',
    }
  },
});
</script>

<template>
  <div v-if="data">
    <strong>{{ data.title }}</strong>
    <p>{{ data.subtitle }}</p>
    <a :href="data.button.link">{{ data.button.label }}</a>
  </div>
</template>
```

**Composable — TypeScript**

```vue
<script setup lang="ts">
import {useContent} from '@croct/plug-vue';

const {data} = useContent('home-hero', {
  fallback: {
    title: 'Welcome to Croct!',
    subtitle: 'The easiest way to personalize your application.',
    button: {
      label: 'Get started',
      link: '/signup',
    }
  },
});
</script>

<template>
  <div v-if="data">
    <strong>{{ data.title }}</strong>
    <p>{{ data.subtitle }}</p>
    <a :href="data.button.link">{{ data.button.label }}</a>
  </div>
</template>
```

**Component — JavaScript**

```vue
<script setup>
import {Slot} from '@croct/plug-vue';

const fallback = {
  title: 'Welcome to Croct!',
  subtitle: 'The easiest way to personalize your application.',
  button: {
    label: 'Get started',
    link: '/signup',
  }
};
</script>

<template>
  <Slot id="home-hero" :fallback="fallback" v-slot="{ content }">
    <div>
      <strong>{{ content.title }}</strong>
      <p>{{ content.subtitle }}</p>
      <a :href="content.button.link">{{ content.button.label }}</a>
    </div>
  </Slot>
</template>
```

**Component — TypeScript**

```vue
<script setup lang="ts">
import {Slot} from '@croct/plug-vue';

const fallback = {
  title: 'Welcome to Croct!',
  subtitle: 'The easiest way to personalize your application.',
  button: {
    label: 'Get started',
    link: '/signup',
  }
};
</script>

<template>
  <Slot id="home-hero" :fallback="fallback" v-slot="{ content }">
    <div>
      <strong>{{ content.title }}</strong>
      <p>{{ content.subtitle }}</p>
      <a :href="content.button.link">{{ content.button.label }}</a>
    </div>
  </Slot>
</template>
```

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.

**Composable — JavaScript**

```vue
<script setup>
import {useContent} from '@croct/plug-vue';

const {data} = useContent('home-hero@2');
</script>

<template>
  <div v-if="data">
    <strong>{{ data.title }}</strong>
    <p>{{ data.subtitle }}</p>
    <a :href="data.button.link">{{ data.button.label }}</a>
  </div>
</template>
```

**Composable — TypeScript**

```vue
<script setup lang="ts">
import {useContent} from '@croct/plug-vue';

const {data} = useContent('home-hero@2');
</script>

<template>
  <div v-if="data">
    <strong>{{ data.title }}</strong>
    <p>{{ data.subtitle }}</p>
    <a :href="data.button.link">{{ data.button.label }}</a>
  </div>
</template>
```

**Component — JavaScript**

```vue
<script setup>
import {Slot} from '@croct/plug-vue';
</script>

<template>
  <Slot id="home-hero@2" v-slot="{ content }">
    <div>
      <strong>{{ content.title }}</strong>
      <p>{{ content.subtitle }}</p>
      <a :href="content.button.link">{{ content.button.label }}</a>
    </div>
  </Slot>
</template>
```

**Component — TypeScript**

```vue
<script setup lang="ts">
import {Slot} from '@croct/plug-vue';
</script>

<template>
  <Slot id="home-hero@2" v-slot="{ content }">
    <div>
      <strong>{{ content.title }}</strong>
      <p>{{ content.subtitle }}</p>
      <a :href="content.button.link">{{ content.button.label }}</a>
    </div>
  </Slot>
</template>
```

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

## Localization

To support multiple locales, you can use the [`preferredLocale`](api/composables/use-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.

> **Good to know**
>
> You can set a [default locale](api/functions/create-croct#defaultpreferredlocale-prop) for your application during SDK initialization, eliminating the need to specify it with each content fetch.

By default, if you do not specify a locale, or if the content is not available in the preferred locale, the content is returned in the [default locale of your workspace](https://app.croct.com/redirect/organizations/-organization-/workspaces/-workspace-/settings).

Here is an example of how to fetch content in a specific locale:

**Composable — JavaScript**

```vue
<script setup>
import {useContent} from '@croct/plug-vue';

const {data} = useContent('home-hero', {
  preferredLocale: 'en-ca',
});
</script>

<template>
  <div v-if="data">
    <strong>{{ data.title }}</strong>
    <p>{{ data.subtitle }}</p>
    <a :href="data.button.link">{{ data.button.label }}</a>
  </div>
</template>
```

**Composable — TypeScript**

```vue
<script setup lang="ts">
import {useContent} from '@croct/plug-vue';

const {data} = useContent('home-hero', {
  preferredLocale: 'en-ca',
});
</script>

<template>
  <div v-if="data">
    <strong>{{ data.title }}</strong>
    <p>{{ data.subtitle }}</p>
    <a :href="data.button.link">{{ data.button.label }}</a>
  </div>
</template>
```

**Component — JavaScript**

```vue
<script setup>
import {Slot} from '@croct/plug-vue';
</script>

<template>
  <Slot id="home-hero" preferred-locale="en-ca" v-slot="{ content }">
    <div>
      <strong>{{ content.title }}</strong>
      <p>{{ content.subtitle }}</p>
      <a :href="content.button.link">{{ content.button.label }}</a>
    </div>
  </Slot>
</template>
```

**Component — TypeScript**

```vue
<script setup lang="ts">
import {Slot} from '@croct/plug-vue';
</script>

<template>
  <Slot id="home-hero" preferred-locale="en-ca" v-slot="{ content }">
    <div>
      <strong>{{ content.title }}</strong>
      <p>{{ content.subtitle }}</p>
      <a :href="content.button.link">{{ content.button.label }}</a>
    </div>
  </Slot>
</template>
```

For more information, refer to the [`preferredLocale`](api/composables/use-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/composables/use-content#options-attributes-prop) option:

**Composable — JavaScript**

```vue
<script setup>
import {useContent} from '@croct/plug-vue';

const {data} = useContent('upgrade-banner', {
  attributes: {plan: 'premium'},
});
</script>

<template>
  <div v-if="data">
    <strong>{{ data.title }}</strong>
    <p>{{ data.subtitle }}</p>
    <a :href="data.button.url">{{ data.button.label }}</a>
  </div>
</template>
```

**Composable — TypeScript**

```vue
<script setup lang="ts">
import {useContent} from '@croct/plug-vue';

const {data} = useContent('upgrade-banner', {
  attributes: {plan: 'premium'},
});
</script>

<template>
  <div v-if="data">
    <strong>{{ data.title }}</strong>
    <p>{{ data.subtitle }}</p>
    <a :href="data.button.url">{{ data.button.label }}</a>
  </div>
</template>
```

**Component — JavaScript**

```vue
<script setup>
import {Slot} from '@croct/plug-vue';
</script>

<template>
  <Slot id="upgrade-banner" :attributes="{plan: 'premium'}" v-slot="{ content }">
    <div>
      <strong>{{ content.title }}</strong>
      <p>{{ content.subtitle }}</p>
      <a :href="content.button.url">{{ content.button.label }}</a>
    </div>
  </Slot>
</template>
```

**Component — TypeScript**

```vue
<script setup lang="ts">
import {Slot} from '@croct/plug-vue';
</script>

<template>
  <Slot id="upgrade-banner" :attributes="{plan: 'premium'}" v-slot="{ content }">
    <div>
      <strong>{{ content.title }}</strong>
      <p>{{ content.subtitle }}</p>
      <a :href="content.button.url">{{ content.button.label }}</a>
    </div>
  </Slot>
</template>
```

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/composables/use-content#options-attributes-prop) documentation.

## Explore

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