Back to blog

Storybook for React Server Components

Use RSCs in Storybook by upgrading to Storybook 8.0 alpha

loading
Michael Shilman
@mshilman
Last updated:

React Server Components (RSC) are a new programming model for React-based web UIs. In contrast to traditional React “client” components, they only render on the server. This leads to a variety of performance and security benefits, but it is also a huge departure from the React tools and libraries we use today.

One of the most impacted areas is component-driven development and testing. Tools like Storybook, Testing Library, and Playwright/Cypress Component Testing all assume that the user’s components are being rendered in the browser (or JSDom). But with server components, that’s no longer the case.

This creates the question: what does it mean to do isolated component development and testing for the server?

Today, I’m excited to release RSC support in Storybook’s Next.js framework as an experimental answer to this question. It is a purely client-side implementation, making it compatible with the entire ecosystem of Storybook addons and integrations.

Read on to learn how it works, how to use it, and how you can try it today!

Servers are from Mars, clients are from Venus

RSCs have two major differences from traditional client components, both of which are present in the following example:

// ApiCard.tsx

import { ComponentProps } from 'react';
import { Card } from './Card';
import { findById } from './db';

export async function DbCard({ id }: {id: number}) {
  let props;
  try {
    const contact = await findById(id);
    props = { state: 'success', contact };
  } catch (e) {
    props = { state: 'error' };
  }
  return <Card {...props} />;
}
  1. The first difference is that our component is async, which is not supported on the client.
  2. The second difference is that our component can access Node code directly, in this case the findById function that wraps an authenticated database connection.

RSC does a lot under the hood to implement these two differences. This code only ever runs on the server, and it generates a static JSON-like structure which is streamed down to the client.

Storybook is a pure client application. It produces a static build of pure HTML/CSS/JS with no Node in sight! So, supporting RSC would require figuring out either how to get RSCs to render on the client OR rearchitecting Storybook for servers.

We started by focusing on the client approach. We want to minimize impact to our users, who have written millions of stories and hundreds of addons, all based on the current architecture.

So, how the heck does it work?

Getting async with it

The first challenge to getting RSCs to render on the client is configuring how to support async components. It turns out that this is already supported (unofficially) in Next.js’s canary React version. Special thanks to JamesManningR and julRuss, who contributed this simple solution!

import { Suspense } from 'react';

export const ClientContact = ({ id }) => (
  <Suspense><DbCard id={id} /></Suspense>
);

Starting in Storybook 8, @storybook/nextjs can wrap your stories in Suspense using the experimentalRSC feature flag in .storybook/main.js:

// .storybook/main.js
export default {
  features: {
    experimentalRSC: true,
  }
};

You can also do this manually in 7.x versions of @storybook/nextjs by wrapping your RSC stories in a decorator.

Note: This solution doesn’t yet work in other Storybook React frameworks (e.g. react-vite, react-webpack5) because they do not use Next.js’s canary version of React. Hopefully, this limitation is removed by the next version of React.

Mocked and loaded

Solving the async problem only gets us halfway there. Our DbCard component also references node code which fetches the data to populate the component. This is a problem in the browser, which cannot execute Node code!

To work around this problem, we recommend establishing a clean data access layer. This is also recommended as a best practice by the architect of RSC.

Once you have the data access layer, you can mock it out so that it can run in the browser and so that you can precisely control the data that it returns to exercise different UI states (loading, error, success, etc.).

You can mock the data access layer using module mocks or network mocks, both of which are supported in Storybook.

Modules: There is a community addon, storybook-addon-module-mock, that provides jest.mock-style mocking (for Webpack projects only). You can also use webpack/vite aliases for a simpler but more limited solution. We plan to provide ergonomic module mocking in a future version of Storybook.

Network APIs: To mock network requests, we recommend Mock Service Worker (msw). Storybook also supports numerous other network and GraphQL mocking addons.

Bringing this back to our example, here’s what a story might look like using storybook-addon-module-mock:

// DbCard.stories.js

import { StoryObj, Meta } from '@storybook/react';
import { createMock } from 'storybook-addon-module-mock';

import { DbCard } from './DbCard';
import * as db from './db';

export default { component: DbCard };

export const Success {
  args: { id: 1 },
  parameters: {
    moduleMock: {
      mock: () => {
        const mock = createMock(db, 'findById');
        mock.mockReturnValue(Promise.resolve({
          name: 'Beyonce',
          img: 'https://blackhistorywall.files.wordpress.com/2010/02/picture-device-independent-bitmap-119.jpg',
          tel: '+123 456 789',
          email: 'b@beyonce.com'
        }))
        return [mock];
      },
    },
  },
}

Full demo: API + module mocking

For the entire example above including both the module mocked database version and the MSW2-mocked API version, please check our full RSC demo Storybook or its GitHub repo.

A contact card for Chuck Norris (with the email address gmail@chucknorris.com), demonstrated within Storybook

What’s the catch?

In this post, we’ve successfully written a story for our first RSC in Storybook and shown how this is all implemented under the hood.

This was all pretty straightforward, but the approach has limitations:

  1. Fidelity. The pure client implementation differs dramatically from the server-side, streaming RSC implementation that is running in your application.
  2. Convenience. The mocking solutions here can definitely be improved. Not only is our current module mocking solution verbose, but it doesn’t play nicely with Storybook args/controls.

We plan to work on both of these limitations in subsequent iterations, which is why we’ve labeled this solution as experimental.

Use Storybook for RSC today 🎊

To use Storybook for RSC, upgrade your Storybook to 8.0-alpha:

npx storybook@next upgrade --prerelease

Then, enable the experimental feature in your .storybook/main.ts:

// .storybook/main.js
export default {
  features: {
    experimentalRSC: true,
  },
};

For more information, see the @storybook/nextjs docs.

This is the first of our posts detailing the contents of Storybook 8.0, our next major version, and we’ll have lots more to come in the months ahead. Keep up with all the news on the next release by following us on social media or signing up for the Storybook newsletter!

Join the Storybook mailing list

Get the latest news, updates and releases

6,587 developers and counting

We’re hiring!

Join the team behind Storybook and Chromatic. Build tools that are used in production by 100s of thousands of developers. Remote-first.

View jobs

Popular posts

How to make Storybook 2-4x faster

Optimize build performance for Storybook 7.6
loading
Kasper Peulen
Join the community
6,587 developers and counting
WhyWhy StorybookComponent-driven UI
Open source software
Storybook

Maintained by
Chromatic
Special thanks to Netlify and CircleCI