---
title: @storyblok/react (Version 8.x RC)
description: @storyblok/react is Storyblok’s official SDK for React applications.
url: https://www.storyblok.com/docs/libraries/js/react-sdk/v8rc
---

# @storyblok/react (Version 8.x RC)

[@storyblok/react](https://github.com/storyblok/monoblok/tree/main/packages/react) is Storyblok’s official SDK for React applications.

> [!WARNING]
> This page documents the release candidate of version 8.x. The API can still change before the stable release. For the latest stable version, refer to the [@storyblok/react reference](/docs/libraries/js/react-sdk).

Version 8.x separates rendering from data fetching. Fetch content with [`@storyblok/api-client`](/docs/libraries/js/content-delivery-api-client), and render it with the components returned by `defineStoryblokBlocks`. To upgrade from version 7.x, follow the [migration guide](/docs/libraries/js/react-sdk/migration-from-v7).

## Requirements

-   **React** version 18 or later (version 19 for React Server Components)
-   **Node.js** LTS (version 22.x recommended)
-   **Evergreen web browser** (for example, Chrome, Firefox, Safari, or Edge)

## Installation

Add the SDK and the Storyblok API client to a project by running this command in the terminal:

```bash
npm install @storyblok/react@rc @storyblok/api-client
```

## Usage

How to configure the SDK, register components, and render stories. The SDK provides two entry points for different rendering modes:

| Export | When to use | How to use |
| --- | --- | --- |
| `@storyblok/react` | All environments: client-rendered and single-page apps, static export, and Server Components. | Use **`defineStoryblokBlocks`** to render content. Use **`StoryblokPreview`** or **`useStoryblokState`** for live editing in client-rendered apps. |
| `@storyblok/react/rsc` | Next.js App Router and other React Server Components (RSC) setups that support Server Actions (React 19). | Use **`StoryblokPreview`** for live editing. Its `renderContent` prop is a Server Action, so the content re-renders on the server with every change in the Visual Editor. |

### Configuration

Create an [`@storyblok/api-client`](/docs/libraries/js/content-delivery-api-client) instance with the space’s [access token](/docs/concepts/access-tokens), and register the React components that render each block.

src/lib/storyblok.js

```jsx
import { createApiClient } from "@storyblok/api-client";
import { defineStoryblokBlocks } from "@storyblok/react";
import Page from "@/components/Page";
import Teaser from "@/components/Teaser";

export const apiClient = createApiClient({
  accessToken: "YOUR_ACCESS_TOKEN",
  region: "eu",
});

export const { StoryblokBlock, StoryblokBlocks, StoryblokRichText } = defineStoryblokBlocks({
  components: {
    page: Page,
    teaser: Teaser,
  },
});
```

> [!WARNING]
> The `region` parameter is required for non-EU spaces. For details, refer to the [`@storyblok/api-client` reference](/docs/libraries/js/content-delivery-api-client#region).

### Components

Create a React component for each block defined in Storyblok and registered in the configuration. Each component receives a `block` prop that contains the block’s content, and an `editable` prop.

src/components/Teaser.jsx

```jsx
export default function Teaser({ block, editable }) {
  return (
    <div {...editable}>
      <h2>{block.headline}</h2>
    </div>
  );
}
```

To automatically render nested blocks, use `StoryblokBlocks`, which renders each block of an array through the registered components.

src/components/Page.jsx

```jsx
import { StoryblokBlocks } from "@/lib/storyblok";

export default function Page({ block, editable }) {
  return <main {...editable}>{block.body && <StoryblokBlocks blocks={block.body} />}</main>;
}
```

### Fetching and rendering

Use the API client to fetch a story and render its content with `StoryblokBlock`.

In a React Server Component, fetch the story directly:

src/app/page.js

```jsx
import { notFound } from "next/navigation";
import { apiClient, StoryblokBlock } from "@/lib/storyblok";

export default async function Home() {
  const { data } = await apiClient.stories.get("home", {
    query: { version: "draft" },
  });

  if (!data?.story) {
    notFound();
  }

  return <StoryblokBlock block={data.story.content} />;
}
```

In a client-rendered application, use a data-fetching library such as [SWR](https://swr.vercel.app/docs/getting-started) or [React Query](https://tanstack.com/query/latest/docs/framework/react/overview) alongside the API client. These libraries handle loading and error states, and cache the fetched stories:

src/App.jsx

```jsx
import useSWR from "swr";
import { apiClient, StoryblokBlock } from "@/lib/storyblok";

async function fetchStory(slug) {
  const { data } = await apiClient.stories.get(slug, {
    query: { version: "draft" },
  });
  if (!data) throw new Error(`Story not found: ${slug}`);
  return data.story;
}

export default function App() {
  const { data: story, error } = useSWR("home", fetchStory);

  if (error) return <div>Failed to load story.</div>;
  if (!story) return <div>Loading...</div>;

  return <StoryblokBlock block={story.content} />;
}
```

To enable live editing in the Visual Editor, wrap the rendered content in [`StoryblokPreview`](#storyblokpreview).

## API

`@storyblok/react` exports the following components, hooks, and helpers.

### `defineStoryblokBlocks`

`defineStoryblokBlocks()` maps Storyblok blocks to React components and returns the `StoryblokBlock`, `StoryblokBlocks`, and `StoryblokRichText` components, which share the same component map.

```js
import { defineStoryblokBlocks } from "@storyblok/react";

export const { StoryblokBlock, StoryblokBlocks, StoryblokRichText } = defineStoryblokBlocks(OPTIONS);
```

`defineStoryblokBlocks()` accepts the following options:

| Key | Description | Type |
| --- | --- | --- |
| `components` | An object that maps the technical names of Storyblok blocks to React components or to a component configuration. Each component receives a `block` prop containing the content of the block, and an `editable` prop. Required. | `Record<string, StoryblokBlockEntry>` |
| `fallback` | A component to render when a block has no registered component. The fallback receives the same `block` and `editable` props. | React component |
| `suspenseFallback` | The default element to show while an asynchronous or lazy component loads, for example, `<Skeleton />`. | React element |

If a block has no registered component and the options don’t include a `fallback`, `StoryblokBlock` renders nothing and logs a `No component registered for "<block-name>"` warning.

#### Component configuration

Instead of a component, an entry in `components` can be a configuration object that controls the Suspense behavior of that component. This configuration is especially useful for asynchronous Server Components that fetch their own data.

```jsx
export const { StoryblokBlock, StoryblokBlocks, StoryblokRichText } = defineStoryblokBlocks({
  components: {
    hero: {
      component: Hero,
      fallback: <HeroSkeleton />,
      suspense: true,
    },
    article: Article,
  },
  suspenseFallback: <Skeleton />,
});
```

| Key | Description | Type |
| --- | --- | --- |
| `component` | The React component that renders the block. Required. | React component |
| `fallback` | The element to show while this component loads. Overrides `suspenseFallback`. | React element |
| `suspense` | Whether to wrap the component in a Suspense boundary. `StoryblokBlock` wraps components created with `React.lazy` automatically. | `boolean` |

### `StoryblokBlock`

`StoryblokBlock` is a React component that dynamically renders a single block from Storyblok. `defineStoryblokBlocks` returns it.

`StoryblokBlock` accepts a `block` prop, which should be a block from the Storyblok API. `StoryblokBlock` passes any other props directly to the block component.

```jsx
<StoryblokBlock block={block} />
```

### `StoryblokBlocks`

`StoryblokBlocks` is a React component that renders an array of blocks, such as the content of a blocks field. `defineStoryblokBlocks` returns it.

`StoryblokBlocks` accepts a `blocks` prop and renders each block with `StoryblokBlock`, using the `_uid` of the block as the `key`. `StoryblokBlocks` passes any other props to every block component.

```jsx
<StoryblokBlocks blocks={block.nested_blocks} />
```

### `StoryblokPreview`

`StoryblokPreview` is a React component that enables live editing in the Visual Editor. The component receives the fetched story and a `renderContent` function, and calls `renderContent` again with the updated story on every change in the Visual Editor.

Outside the Visual Editor, `StoryblokPreview` doesn’t load the Storyblok Bridge and renders the content once.

`StoryblokPreview` provides two entry points depending on the application architecture: one for client-rendered applications and one for server-rendered applications that use React Server Components.

#### Client-rendered applications

For client-rendered applications, import `StoryblokPreview` from `@storyblok/react`:

```jsx
import { StoryblokPreview } from "@storyblok/react";
import { StoryblokBlock } from "@/lib/storyblok";

<StoryblokPreview story={story} renderContent={(live) => <StoryblokBlock block={live.content} />} />;
```

The `renderContent` function receives the live story and returns the React content to render.

#### Server-rendered applications

For applications that use React Server Components, import `StoryblokPreview` from `@storyblok/react/rsc`. In this entry point, `StoryblokPreview` is an asynchronous Server Component, and `renderContent` must be a Server Action. This entry point requires React 19.

Define the Server Action in a separate file:

src/lib/actions.js

```jsx
"use server";

import { StoryblokBlock } from "@/lib/storyblok";

export async function renderContent(story) {
  return <StoryblokBlock block={story.content} />;
}
```

Then pass the Server Action to `StoryblokPreview`:

src/app/page.js

```jsx
import { notFound } from "next/navigation";
import { StoryblokPreview } from "@storyblok/react/rsc";
import { renderContent } from "@/lib/actions";
import { apiClient } from "@/lib/storyblok";

export default async function Home() {
  const { data } = await apiClient.stories.get("home", {
    query: { version: "draft" },
  });

  if (!data?.story) {
    notFound();
  }

  return <StoryblokPreview story={data.story} renderContent={renderContent} />;
}
```

`StoryblokPreview` renders the initial content on the server. In the Visual Editor, the component calls the Server Action on every change and streams the result in, keeping the current content on screen until the update arrives. If the Server Action fails, `StoryblokPreview` logs the error and keeps the initially rendered content visible.

#### Props

| Key | Description | Type |
| --- | --- | --- |
| `story` | The story fetched from the Storyblok API. Required. | `Story` |
| `renderContent` | A function that receives the live story and returns the content to render. In `@storyblok/react/rsc`, it must be an asynchronous Server Action. Required. | `function` |
| `bridgeOptions` | Options for the Storyblok Bridge, such as `resolveRelations`. `StoryblokPreview` reads them once, when it mounts. Learn more in the [@storyblok/preview-bridge reference](/docs/libraries/js/preview-bridge). | `object` |
| `debounceMs` | Milliseconds to wait after the last change in the Visual Editor before updating the content. In `@storyblok/react/rsc`, the default is `200`; otherwise, updates are immediate. | `number` |

When fetching a story with the `resolve_relations` parameter, also pass the same relations as `bridgeOptions.resolveRelations`, so the relations stay resolved after a live update. Enable `inlineRelations` in the [API client](/docs/libraries/js/content-delivery-api-client) to inline the resolved stories into the content.

### `useStoryblokState`

`useStoryblokState()` accepts a story from the Storyblok API and makes the story reactive for live editing. The hook returns the latest version of the story on every change in the Visual Editor. `StoryblokPreview` from `@storyblok/react` uses this hook internally.

Use `StoryblokPreview` to wrap the rendered content of a story. Use `useStoryblokState()` when other parts of the component also depend on the live story, for example, to update the page title while editing.

```jsx
import { useStoryblokState } from "@storyblok/react";
import { StoryblokBlock } from "@/lib/storyblok";

export default function Home({ story: STORY_OBJECT }) {
  const story = useStoryblokState(STORY_OBJECT, OPTIONS);

  return <StoryblokBlock block={story.content} />;
}
```

`OPTIONS` accepts the same `bridgeOptions` and `debounceMs` options as [`StoryblokPreview`](#props).

### `useStoryblokEditorEvent`

`useStoryblokEditorEvent()` subscribes to changes in the Visual Editor and calls a function with the updated story, without keeping the story in state. Use the hook to react to changes in a custom way, for example, to update data held by another state manager.

```jsx
import { useStoryblokEditorEvent } from "@storyblok/react";

useStoryblokEditorEvent((story) => {
  // ...
}, OPTIONS);
```

`OPTIONS` accepts the same `bridgeOptions` and `debounceMs` options as [`StoryblokPreview`](#props).

### `storyblokEditable`

Every component rendered through `StoryblokBlock` receives an `editable` prop. Spread `editable` onto the root element of the component to make the component editable in the Visual Editor.

```jsx
export default function Feature({ block, editable }) {
  return <section {...editable}>{block.title}</section>;
}
```

For components that aren’t rendered through `StoryblokBlock`, use `storyblokEditable()` instead. `storyblokEditable()` accepts a block from the Storyblok API and returns an object containing the same HTML attributes.

```jsx
import { storyblokEditable } from "@storyblok/react";

export default function Feature({ block }) {
  return <section {...storyblokEditable(block)}>{block.title}</section>;
}
```

### `StoryblokRichText`

`StoryblokRichText` renders a rich text field from a Storyblok story. Use the `StoryblokRichText` component returned by `defineStoryblokBlocks`, so that blocks embedded in the rich text field render through the registered components. The component works in both Server Components and Client Components.

```jsx
import { StoryblokRichText } from "@/lib/storyblok";

<StoryblokRichText document={block.richtext_field} />;
```

The component accepts the following optional props:

-   `components` sets custom React components for supported rich text nodes and marks.
-   `optimizeImage` sets [image optimization](/docs/api/image-service) options for image nodes.
-   `data` shares custom data with all custom rich text components. Each component receives the data as `context.data`.

To render rich text without the component map, use the standalone [`createRichTextRenderer`](#createrichtextrenderer) function. The function renders embedded blocks only when its `components` option includes a custom component for the `blok` node, the rich text node that holds embedded blocks.

When using TypeScript, custom components are fully type-safe through the `StoryblokReactRichTextProps<T>` utility type. For example, `StoryblokReactRichTextProps<"link">` provides the attributes and `children` available for the `link` mark, while `StoryblokReactRichTextProps<"heading">` provides access to heading-specific attributes such as `level`.

Find the supported rich text nodes and marks, and the customization options, in the [@storyblok/richtext reference](/docs/libraries/js/rich-text).

#### Custom components

To register custom components, use the `components` prop. The key must match a supported rich text node or mark name.

```tsx
import type { StoryblokReactRichTextComponentMap } from "@storyblok/react";

const components: StoryblokReactRichTextComponentMap = {
  heading: CustomHeading,
  link: CustomLink,
  table: CustomTable,
  bold: ({ children }) => <b className="font-bold text-black">{children}</b>,
};
```

Then pass the components to `StoryblokRichText`:

```tsx
<StoryblokRichText document={block.richtext_field} components={components} />
```

> [!WARNING]
> The rendered output is recomputed whenever `components` or `data` change by reference. Define `components` and `data` outside the component, or wrap them in `useMemo`, instead of passing inline objects.

#### Example: custom link component (mark)

Marks receive their rendered child content through the `children` prop.

```tsx
import type { StoryblokReactRichTextProps } from "@storyblok/react";
import Link from "next/link";

export function CustomLink({ children, attrs }: StoryblokReactRichTextProps<"link">) {
  return (
    <Link href={attrs?.href ?? ""} target={attrs?.target ?? "_self"}>
      {children}
    </Link>
  );
}
```

#### Example: custom heading component (node)

Nodes can use the `children` prop to render child content.

```tsx
import type { StoryblokReactRichTextProps } from "@storyblok/react";

export function CustomHeading({ children, attrs }: StoryblokReactRichTextProps<"heading">) {
  const Tag = `h${attrs?.level ?? 1}` as keyof JSX.IntrinsicElements;

  return <Tag className="custom-heading">{children}</Tag>;
}
```

#### Example: custom table component (advanced node)

For advanced use cases, nodes can access `content` and `context` to recursively render child nodes using `StoryblokRichText`.

To render semantic table nodes, use the `splitTableRows` utility from `@storyblok/richtext` and create `<thead>` and `<tbody>` elements.

```tsx
import type { StoryblokReactRichTextProps } from "@storyblok/react";
import { StoryblokRichText } from "@/lib/storyblok";
import { splitTableRows } from "@storyblok/richtext";

export function CustomTable({ content, context }: StoryblokReactRichTextProps<"table">) {
  const { headerRows, bodyRows } = splitTableRows(content);

  return (
    <table className="custom-table">
      {headerRows.length > 0 && (
        <thead>
          <StoryblokRichText document={headerRows} {...context} />
        </thead>
      )}

      <tbody>
        <StoryblokRichText document={bodyRows} {...context} />
      </tbody>
    </table>
  );
}
```

Spreading `context` passes the custom components, image options, and data of the parent on to the nested rows, including the renderer for embedded blocks.

### `createRichTextRenderer`

`createRichTextRenderer()` returns a function that renders a rich text document to React elements. Use `createRichTextRenderer()` to render rich text outside of JSX.

```jsx
import { createRichTextRenderer } from "@storyblok/react";

const render = createRichTextRenderer({ components, optimizeImage: true });

const content = render(block.richtext_field);
```

`createRichTextRenderer()` accepts the same `components`, `optimizeImage`, and `data` options as `StoryblokRichText`. To render embedded blocks, provide a custom component for the `blok` rich text node.

### TypeScript

`@storyblok/react` exports types for stories and blocks. Use `StoryblokBlockComponentProps` to type the props of a block component:

```tsx
import type { StoryblokBlockData, StoryblokBlockComponentProps } from "@storyblok/react";

type PageProps = StoryblokBlockComponentProps<{ body: StoryblokBlockData[] }>;

export default function Page({ block, editable }: PageProps) {
  // ...
}
```

To pass extra props through `StoryblokBlock` and `StoryblokBlocks` in TypeScript, declare the extra props as a type argument of `defineStoryblokBlocks`, for example, `defineStoryblokBlocks<{ locale: string }>(OPTIONS)`. Then pass the same props as the second type argument of `StoryblokBlockComponentProps`, for example, `StoryblokBlockComponentProps<{ title: string }, { locale: string }>`.

To narrow `story.content` to the blocks of a project, use the `withTypes()` method of the API client. Learn more in the [`@storyblok/api-client` reference](/docs/libraries/js/content-delivery-api-client#typescript).

## Further resources

[Migrate to @storyblok/react 8.x](/docs/libraries/js/react-sdk/migration-from-v7)Learn how to upgrade an application from version 7.x in the migration guide.

[@storyblok/api-client Package Reference](/docs/libraries/js/content-delivery-api-client)Learn how to fetch content from the Content Delivery API in the @storyblok/api-client reference.

[@storyblok/richtext Package Reference](/docs/libraries/js/rich-text)Find the supported rich text nodes, marks, and customization options in the @storyblok/richtext reference.

[@storyblok/schema Package Reference](/docs/libraries/js/schema)Learn how to type the blocks of a project in the @storyblok/schema reference.

[Visual Editor](/docs/concepts/visual-editor)Learn how live editing works in the Visual Editor concept.

[Repository Playground](https://github.com/storyblok/monoblok/tree/main/packages/react/playground)Find additional examples in the repository playground.

[React Guide](/docs/quickstarts/react)Learn how to integrate Storyblok with React in the React guide.

[Next.js Guide](/docs/quickstarts/nextjs)Learn how to integrate Storyblok with Next.js in the Next.js guide.

[Space Blueprint: React](https://github.com/storyblok/blueprint-core-react)Kickstart a new React project with the core space blueprint.

[Space Blueprint: Next.js](https://github.com/storyblok/blueprint-core-nextjs)Kickstart a new Next.js project with the core space blueprint.

## Previous versions

[@storyblok/react (Version 7.x)](/docs/libraries/js/react-sdk)

[@storyblok/react (Version 6.x)](/docs/libraries/js/react-sdk/v6)

[@storyblok/react (Version 5.x)](/docs/libraries/js/react-sdk/v5)
