This repository was archived by the owner on Jun 19, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 36
fix: 1373 storyblokrichtext blok resolver not working #1388
Open
alvarosabu
wants to merge
8
commits into
main
Choose a base branch
from
bugfix/1373-storyblokrichtext-blok-resolver-not-working
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
9e666ba
chore(playground): enhance react richtext playground
alvarosabu edf1966
feat(rsc): added server richtext component
alvarosabu f07b110
fix(styles): add missing semicolon in CSS rules
alvarosabu 4ef3c61
Merge branch 'main' into bugfix/1373-storyblokrichtext-blok-resolver-…
alvarosabu 44adbb1
Merge branch 'main' into bugfix/1373-storyblokrichtext-blok-resolver-…
alvarosabu f747adf
Merge branch 'main' into bugfix/1373-storyblokrichtext-blok-resolver-…
alvarosabu bb89056
docs: update to async function and integrate fetchData for Storyblok …
alvarosabu 66c948c
Merge branch 'main' into bugfix/1373-storyblokrichtext-blok-resolver-…
alvarosabu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -524,9 +524,7 @@ sbBridge.on(['input', 'published', 'change'], (event) => { | |
|
|
||
| ## Rendering Rich Text | ||
|
|
||
| > [!WARNING] | ||
| > We have identified issues with richtext and Types on React 19 and Next.js 15. As a temporary measure, we advise you to continue using React 18 and Next.js 14 until we have fully resolved the issues. | ||
|
|
||
| ### Client-side Rich Text Rendering | ||
| You can render rich text fields by using the `StoryblokRichText` component: | ||
|
|
||
| ```ts | ||
|
|
@@ -569,7 +567,61 @@ function App() { | |
| } | ||
| ``` | ||
|
|
||
| For a comprehensive list of options you can provide to the `useStoryblokRichText`, please consult the [Full options](https://github.com/storyblok/richtext?tab=readme-ov-file#options) documentation. | ||
| ### Server-side Rich Text Rendering | ||
|
|
||
| > [!INFO] | ||
| > Recommended for Next.js 15 and React 19. | ||
|
|
||
| You can render rich text fields by using the `StoryblokServerRichText` component: | ||
|
|
||
| ```ts | ||
| import { StoryblokServerRichText, StoryblokStory } from '@storyblok/react/rsc'; | ||
| import { StoryblokClient, ISbStoriesParams } from '@storyblok/react'; | ||
| import { getStoryblokApi } from '@/lib/storyblok'; // Remember to import from the local file | ||
|
|
||
| async function App() { | ||
| const { data } = await fetchData(); | ||
|
|
||
| if (!data?.story?.content) { | ||
| return <div>Loading...</div>; | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
| <StoryblokServerRichText doc={data.story.content.richText} /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export async function fetchData() { | ||
| let sbParams: ISbStoriesParams = { version: 'draft' }; | ||
|
|
||
| const storyblokApi: StoryblokClient = getStoryblokApi(); | ||
| return storyblokApi.get(`cdn/stories/home`, sbParams); | ||
| } | ||
| ``` | ||
|
|
||
| Or you can have more control by using the `useStoryblokServerRichText` hook: | ||
|
|
||
| ```ts | ||
| import { useStoryblokServerRichText, convertAttributesInElement } from '@storyblok/react'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are them not exported from the |
||
| import Codeblock from './Codeblock'; | ||
|
|
||
| function App() { | ||
| const { render } = useStoryblokServerRichText({ | ||
| // options like resolvers | ||
| }); | ||
|
|
||
| const html = render(doc); | ||
| const formattedHtml = convertAttributesInElement(html as React.ReactElement); // JSX | ||
|
|
||
| return ( | ||
| <div ref={ref}> | ||
| {formattedHtml} | ||
| </div> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ### Overriding the default resolvers | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,3 +38,5 @@ yarn-error.log* | |
| # typescript | ||
| *.tsbuildinfo | ||
| next-env.d.ts | ||
|
|
||
| certificates | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| /** @type {import('tailwindcss').Config} */ | ||
|
|
||
| const config = { | ||
| plugins: { | ||
| '@tailwindcss/postcss': {}, | ||
| }, | ||
| }; | ||
| export default config; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,111 +1,40 @@ | ||
| import type { | ||
| ISbStoriesParams, | ||
| StoryblokClient, | ||
| StoryblokRichTextNode, | ||
| } from '@storyblok/react/rsc'; | ||
| import { MarkTypes, StoryblokRichText, StoryblokStory, | ||
| } from '@storyblok/react/rsc'; | ||
| import { getStoryblokApi } from '@/lib/storyblok'; | ||
| import type { ISbStoriesParams, StoryblokClient } from '@storyblok/react/rsc'; | ||
| import { StoryblokStory } from '@storyblok/react/rsc'; | ||
| import { getStoryblokApi } from '@/lib/storyblok'; // Remember to import from the local file | ||
| import Link from 'next/link'; | ||
| import type { ReactElement } from 'react'; | ||
|
|
||
| export default async function Home() { | ||
| const { data } = await fetchData(); | ||
|
|
||
| const doc = { | ||
| type: 'doc', | ||
| content: [ | ||
| { | ||
| type: 'paragraph', | ||
| content: [ | ||
| { | ||
| type: 'text', | ||
| text: 'This is a test of the StoryblokRichText component.', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| type: 'paragraph', | ||
| content: [ | ||
| { | ||
| text: 'Internal Link', | ||
| type: 'text', | ||
| marks: [ | ||
| { | ||
| type: 'link', | ||
| attrs: { | ||
| href: '/', | ||
| uuid: '8489bed8-d86f-4fde-965c-e3d748e12147', | ||
| anchor: null, | ||
| target: '_self', | ||
| linktype: 'story', | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| type: 'paragraph', | ||
| content: [ | ||
| { | ||
| text: 'External link', | ||
| type: 'text', | ||
| marks: [ | ||
| { | ||
| type: 'link', | ||
| attrs: { | ||
| href: 'https://alvarosaburido.dev', | ||
| uuid: null, | ||
| anchor: null, | ||
| target: '_blank', | ||
| linktype: 'url', | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }; | ||
| const resolvers = { | ||
| // custom resolvers | ||
| [MarkTypes.LINK]: (node: StoryblokRichTextNode<ReactElement>) => { | ||
| return node.attrs?.linktype === 'story' | ||
| ? ( | ||
| <Link | ||
| href={node.attrs?.href} | ||
| target={node.attrs?.target} | ||
| > | ||
| {node.text} | ||
| </Link> | ||
| ) | ||
| : ( | ||
| <a | ||
| href={node.attrs?.href} | ||
| target={node.attrs?.target} | ||
| > | ||
| {node.text} | ||
| </a> | ||
| ); | ||
| }, | ||
| }; | ||
|
|
||
| return ( | ||
| <div> | ||
| <h1> | ||
| Story: | ||
| {data.story.id} | ||
| </h1> | ||
| <StoryblokStory story={data.story} /> | ||
| <StoryblokRichText doc={doc} resolvers={resolvers} /> | ||
| </div> | ||
| <main className="container mx-auto px-4 py-8"> | ||
| <div className="max-w-4xl mx-auto"> | ||
| <h1 className="text-4xl font-bold mb-8 dark:text-white"> | ||
| Storyblok Next.js 15 Example | ||
| </h1> | ||
|
|
||
| <nav className="space-y-4"> | ||
| <Link | ||
| href="/richtext" | ||
| className="block p-4 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors" | ||
| > | ||
| Go to Rich Text Example | ||
| </Link> | ||
| </nav> | ||
|
|
||
| {data.story && ( | ||
| <div> | ||
| <StoryblokStory story={data.story} /> | ||
| </div> | ||
| )} | ||
| </div> | ||
| </main> | ||
| ); | ||
| } | ||
|
|
||
| export async function fetchData() { | ||
| const sbParams: ISbStoriesParams = { version: 'draft' }; | ||
|
|
||
| const storyblokApi: StoryblokClient = getStoryblokApi(); | ||
| return storyblokApi.get(`cdn/stories/home`, sbParams); | ||
| return storyblokApi.get(`cdn/stories/react`, sbParams); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import type { ISbStoriesParams, StoryblokClient } from '@storyblok/react/rsc'; | ||
| import { getStoryblokApi } from '@/lib/storyblok'; | ||
| import { StoryblokServerRichText } from '@storyblok/react/rsc'; | ||
|
|
||
| export default async function RichtextPage() { | ||
| const { data } = await fetchData(); | ||
|
|
||
| if (!data.story?.content) { | ||
| return ( | ||
| <div className="min-h-screen flex items-center justify-center"> | ||
| <div className="animate-pulse text-lg text-gray-600 dark:text-gray-400"> | ||
| Loading content... | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="container mx-auto px-4 py-8 prose prose-lg dark:prose-invert max-w-4xl"> | ||
| <h1 className="text-3xl font-bold mb-8">Rich Text Example</h1> | ||
| {data.story.content.richText | ||
| ? ( | ||
| <StoryblokServerRichText doc={data.story.content.richText} /> | ||
| ) | ||
| : ( | ||
| <p className="text-gray-600 dark:text-gray-400">No content available</p> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export async function fetchData() { | ||
| const sbParams: ISbStoriesParams = { version: 'draft' }; | ||
|
|
||
| const storyblokApi: StoryblokClient = getStoryblokApi(); | ||
| return storyblokApi.get(`cdn/stories/react/test-richtext`, sbParams); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| 'use client'; | ||
|
|
||
| import React, { type FC, useState } from 'react'; | ||
| import type { SbBlokData } from '@storyblok/react'; | ||
|
|
||
| interface EmojiRandomizerProps { | ||
| blok: SbBlokData & { | ||
| label?: string; | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * A component that displays a label and a random emoji that changes on click | ||
| */ | ||
| const EmojiRandomizer: FC<EmojiRandomizerProps> = ({ blok }) => { | ||
| // List of fun emojis to randomly choose from | ||
| const emojis = ['😊', '🎉', '🚀', '✨', '🌈', '🎨', '🎸', '🎮', '🍕', '🌺']; | ||
|
|
||
| // State to track current emoji | ||
| const [currentEmoji, setCurrentEmoji] = useState(() => | ||
| emojis[Math.floor(Math.random() * emojis.length)], | ||
| ); | ||
|
|
||
| /** | ||
| * Generates a new random emoji different from the current one | ||
| */ | ||
| const randomizeEmoji = () => { | ||
| let newEmoji; | ||
| do { | ||
| newEmoji = emojis[Math.floor(Math.random() * emojis.length)]; | ||
| } while (newEmoji === currentEmoji); | ||
|
|
||
| setCurrentEmoji(newEmoji); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex flex-col items-center gap-6 p-4 bg-gray-100 dark:bg-gray-900 rounded-lg"> | ||
| <div className="text-6xl"> | ||
| {currentEmoji} | ||
| </div> | ||
| <button | ||
| onClick={randomizeEmoji} | ||
| className="px-6 py-3 rounded-lg bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white font-small transition-colors duration-200 dark:bg-blue-600 dark:hover:bg-blue-700 dark:active:bg-blue-800" | ||
| > | ||
| {blok.label || 'Randomize Emoji'} | ||
| </button> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default EmojiRandomizer; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On this example we are using the
rscmodule which is meant for Next.js App Router, but the code does not reflect the changes in the DX (they are documented in the README).So either we change the example detailing that this is for App Router and reuse the same code, or we document that this is a non-strict example that only details how to use the
StoryblokServerRichTextcomponent but everything else needs to be adapted to the user's context.