-
-
Notifications
You must be signed in to change notification settings - Fork 9.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature Request]: Support React Server Components (RSC) #21540
Comments
We'll be investigating this at some point after 7.0 is released. Thanks for filing the issue! cc @valentinpalkovic |
Following! I've used https://www.npmjs.com/package/@storybook/server in the past with PHP projects, it'd be cool to see a similar concept of mixing some server-side work with the existing client-side approach. It's quite hard to find information on RSC at the moment other than looking at the React and Next 13 |
Next.js now has a stable release of their usage of RSC via the Next.js app router https://nextjs.org/blog/next-13-4 |
We did a proof of concept last year with https://www.npmjs.com/package/@storybook/server. We'll need to dedicate some time to dusting that off and figuring a path forward now that 7.0 is released! |
@shilman would that POC work with v7 and is the source available somewhere? The lack of server component support is the main blocker for us to transition to the app directory. I tried to poke around the source for |
@pm0u It's not public -- it's very hacky and I didn't want to confuse anybody or send mixed signals. I'd like to do a proper POC soon and share it out to the community for feedback. However, you should know that this is all really early work. @valentinpalkovic is our local NextJS expert and hopefully he can give you some advice on best practices for the app directory. |
Hitting the same roadblocks here... Server actions are also an issue. They're still experimental and will probably change some, but it might be a good idea to keep them in mind |
+1 |
I found a quick workaround: export a not async component that receives the fetched data. And make your async component return it passing the fetched data in properties. At the Stories file, you mock the props. // Async Component
export const Component = ({ agent }: { agent: AgentModel }) => {
if (!agent) return <></>;
return (
<>
<Header {...agent} />
<Chat agent={agent.agent} />
</>
);
};
const Page = async ({ params }: Model) => {
const agent = await getAgent(params?.agent);
return <Component agent={agent} />;
};
export default Page; // Stories
import { Component } from "./page";
import { PageComponentMock } from "./model";
const meta: Meta<typeof Component> = {
component: Component,
};
export default meta;
type Story = StoryObj<typeof Component>;
export const Default: Story = {
args: PageComponentMock
}; |
I have two projects affected with this. I found this workaround based on we don't want to adapt or apply any workaround to our main UIKit elements:
The problem with this workaround is basically, as you can see, it's not possible update the Header component (our async server componente) from the stories. It's there any estimation about when or who this is goint to be achieve? Really thanks for the Storybook team for this work |
@icastillejogomez we don't have an ETA, since we don't know the right approach yet. i have a semi-working prototype that renders using NextJS server, but which doesn't really fit with Storybook's typical approach to rendering components on the client. There is also some interesting work going on in In the meantime, if anybody wants to help this along, please upvote vercel/next.js#50479 |
Another workaround: When crafting a story for a component that involves a server component (e.g., How do you feel about this approach? // app/page.tsx
interface MyClientComponentProps {
title: string;
}
function MyClientComponent({ title }: MyClientComponentProps) {
return <>{title}</>;
}
// Server component
async function MyServerComponent() {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = (await res.json()) as { title: string };
return <MyClientComponent title={data.title} />;
}
type Props = {
myComponent?: React.ReactNode;
} & Partial<React.ComponentProps<typeof MyClientComponent>>;
export function Page({
title,
// Using dependency injection for Storybook
myComponent = title ? <MyClientComponent title={title} /> : null,
}: Props) {
return <>{myComponent}</>;
}
export default function PageForServerSide() {
return <Page component={<MyServerComponent />} />;
} // app/page.stories.tsx
// Story of the component containing the server component
import { Page } from "./page";
export default {
title: "Page",
component: Page,
};
export const Default = {
args: {
title: "Hello Storybook!",
},
}; |
Storybook should be able to map from internal builders like |
Same ... but I am just finding out about the lack of support and I am already committed to RSC 😢 +1 for this RSC support feature |
+1 for RSC support feature, we want next components in storybook :) |
+1 please!!! orz |
Instead of commenting "+1," please use the thumbs-up (👍🏼) reaction on the issue. This way, we can avoid sending unnecessary notifications to everyone who is subscribed to this issue. |
Hello! Any news on whether or not Storybook will support RSC? If so, are there any progress updates? Thanks for all the great work. |
We have a working prototype and plan to release experimental support in the coming months. Please keep an eye out here for updates soon! @sethburtonhall |
This decorator workaround worked for me This is so you can render async components. It also (I don't know how) works with nested async components as well import { Decorator } from '@storybook/react';
import { Suspense } from 'react';
export const WithSuspense: Decorator = (Story) => {
return (
<Suspense>
<Story />
</Suspense>
);
}; |
Whoa, that's really cool! There was a bit more I had to do as I'm using import type { Meta, StoryObj } from '@storybook/react'
import { Suspense } from 'react'
import { MyServerComponent } from './MyServerComponent'
const meta = {
title: 'RSC',
component: MyServerComponent,
argTypes: {},
tags: ['autodocs'],
decorators: [
Story => (
<Suspense>
<Story />
</Suspense>
),
],
} satisfies Meta<typeof MyServerComponent>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
exampleProp1: '123456789',
exampleProp2: '987654321',
},
parameters: {
nextjs: {
appDirectory: true,
},
},
} Edit: You can also set the const preview: Preview = {
parameters: {
nextjs: {
appDirectory: true,
},
...
},
...
}
export default Preview |
d
Does this method allow for mocking fetch requests made in server components? |
I haven't tried it yet, you might be able to use msw though. |
Amazing @JamesManningR @z0n !! I just tried this out on a sample project and it's working great. Going to play with MSW today and post a sample repo. NOTE: For anybody playing with it, this technique only works with the webpack builder & not with the Vite builder (which was the first sample project I tried). If we end up pushing this solution, will try to get to the bottom of that. We've also got a very different solution that I'll also be sharing soon. |
@JamesManningR, this is incredible work! I’m with @shilman in the core SB team, and as a way of saying thanks we’d love to send you some Storybook swag! I reached out to you on LinkedIn! |
MSW worked for me |
While using |
Try this: vercel/next.js#50068 (comment) |
We already have those settings, so no, it doesn't work :-/ |
Isn't this just rendering as an async Client Component, not a React Server Component? |
I don't understand how all of these pieces fit together. But my understanding is that Storybook just serves your stuff and mocks everything in the browser. So that means that RSCs have to live clientside when running Storybook, right? We need to get all the APIs running. That clearly involves supporting However, that last bit is pretty spicy. It would be nice to have built-in Storybook support for module mocking. And module mocking is hard to support across multiple build systems. |
I just noticed the same issue with a component which includes cookies from |
|
probably a dumb question, but do you already have Of course, the nextjs preset probably needs work to handle running in fake-server mode. |
For anyone looking on how to solve the next headers/cookies and looking for a quick fix: specifically for webpack Just want to reply with a workaround, and this one is a very workaround-y workaround. Using this section of the docs I've marked any new files with * their location doesn't matter // .storybook/withHeadersMock.ts *
import { Decorator } from '@storybook/react';
let allHeaders = new Map();
export const headers = () => {
return allHeaders;
};
export const withNextHeadersMock: Decorator = (story, { parameters }) => {
if (parameters?.nextHeaders) {
allHeaders = new Map(Object.entries(parameters.nextHeaders));
}
return story();
}; // .storybook/main.ts
const config: StorybookConfig = {
// rest of your config...
webpackFinal: async (config) => {
if (!config.resolve) {
config.resolve = {};
}
if (!config.resolve.alias) {
config.resolve.alias = {};
}
config.resolve.alias['next/headers'] = path.resolve(__dirname, './withNextHeadersMock.ts');
return config;
},
} Then just add the headers you need to the story/preview export const MyStory: Story = {
decorators: [
withNextHeadersMock
]
parameters: {
nextHeaders: {
'my-header': 'foo'
}
}
}; This will at least give your stories the ability to run, albeit very mocked so only use this to tide over until a better solution is suggested / implemented @shilman / @joevaugh4n Would you like me to put this into another issue so people have a temporary workaround ready? |
@JamesManningR new issue would be fantastic!! ❤️ |
thank you @JamesManningR! |
also, one for everyone here: Storybook for React Server Components 🎊 |
Created here if anyone wants to follow along |
@shilman I have a question: I created these components as RSC in Next.js. However, if it's an Async Component (in my case, the Indicator component), then it renders three times in Storybook. Is this because Storybook does not yet support RSC? I am using Next.js 14, storybook 7.4.2 async function Indicator() {
console.log('render child');
return <div>child</div>;
}
export default function Parent() {
console.log('render parent');
return (
<div>
<section>
<h1>parent</h1>
</section>
<Suspense fallback={<div>Loading...</div>}>
<Indicator />
</Suspense>
</div>
);
} Also, if the |
If the story here is for the
This one seems very surprising. If you turn |
@david-morris @JamesManningR For a full working demo with Prisma+Http+Cookies see this demo! We recommend people using the absolute import convention with package.json imports like this, as this allows you to do module mocking in a standard based way: // package.json
{
"imports": {
"#app/actions": {
"storybook": "./app/actions.mock.ts",
"default": "./app/actions.ts"
},
"#lib/session": {
"storybook": "./lib/session.mock.ts",
"default": "./lib/session.ts"
},
"#lib/db": {
"storybook": "./lib/db.mock.ts",
"default": "./lib/db.ts"
},
"#*": [
"./*",
"./*.ts",
"./*.tsx"
]
}
} This is also what the Next tech lead wants to move to: You don't have to make module mocks for your cookies anymore, as we mock that out for you! Full example: import { Meta, StoryObj } from '@storybook/react'
import { cookies } from '@storybook/nextjs/headers.mock'
import { http } from 'msw'
import { expect, userEvent, waitFor, within } from '@storybook/test'
import Page from './page'
import { db, initializeDB } from '#lib/db.mock'
import { createUserCookie, userCookieKey } from '#lib/session'
import { PageDecorator } from '#.storybook/decorators'
import { login } from '#app/actions.mock'
import * as auth from '#app/auth/route'
const meta = {
component: Page,
decorators: [PageDecorator],
async beforeEach() {
await db.note.create({
data: {
title: 'Module mocking in Storybook?',
body: "Yup, that's a thing now! 🎉",
createdBy: 'storybookjs',
},
})
await db.note.create({
data: {
title: 'RSC support as well??',
body: 'RSC is pretty cool, even cooler that Storybook supports it!',
createdBy: 'storybookjs',
},
})
},
parameters: {
layout: 'fullscreen',
nextjs: {
navigation: {
pathname: '/note/[id]',
query: { id: '1' },
},
},
},
args: { params: { id: '1' } },
} satisfies Meta<typeof Page>
export default meta
type Story = StoryObj<typeof meta>
export const LoggedIn: Story = {
async beforeEach() {
cookies().set(userCookieKey, await createUserCookie('storybookjs'))
},
}
export const NotLoggedIn: Story = {}
export const LoginShouldGetOAuthTokenAndSetCookie: Story = {
parameters: {
msw: {
// Mock out OAUTH
handlers: [
http.post(
'https://github.com/login/oauth/access_token',
async ({ request }) => {
let json = (await request.json()) as any
return Response.json({ access_token: json.code })
},
),
http.get('https://api.github.com/user', async ({ request }) =>
Response.json({
login: request.headers.get('Authorization')?.replace('token ', ''),
}),
),
],
},
},
beforeEach() {
// Point the login implementation to the endpoint github would have redirected too.
login.mockImplementation(async () => {
return await auth.GET(new Request('/auth?code=storybookjs'))
})
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await expect(cookies().get(userCookieKey)?.value).toBeUndefined()
await userEvent.click(
await canvas.findByRole('menuitem', { name: /login to add/i }),
)
await waitFor(async () => {
await expect(cookies().get(userCookieKey)?.value).toContain('storybookjs')
})
},
}
export const LogoutShouldDeleteCookie: Story = {
async beforeEach() {
cookies().set(userCookieKey, await createUserCookie('storybookjs'))
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await expect(cookies().get(userCookieKey)?.value).toContain('storybookjs')
await userEvent.click(await canvas.findByRole('button', { name: 'logout' }))
await expect(cookies().get(userCookieKey)).toBeUndefined()
},
}
export const SearchInputShouldFilterNotes: Story = {
parameters: {
nextjs: {
navigation: {
query: { q: 'RSC' },
},
},
},
}
export const EmptyState: Story = {
async beforeEach() {
initializeDB({}) // init an empty DB
},
} |
Is your feature request related to a problem? Please describe
No response
Describe the solution you'd like
I'd like to render react server components in Storybook.
Describe alternatives you've considered
No response
Are you able to assist to bring the feature to reality?
no
Additional context
I'm creating this issue so we can subscribe to updates on any new plan/progress regarding the support of React Server Components in Storybook.
The text was updated successfully, but these errors were encountered: