Skip to content

Commit

Permalink
Merge pull request #25091 from storybookjs/shilman/add-rsc
Browse files Browse the repository at this point in the history
NextJS: Add experimental RSC support
  • Loading branch information
shilman authored Dec 5, 2023
2 parents eb3e9bb + 6433e19 commit e8fb099
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 4 deletions.
34 changes: 34 additions & 0 deletions code/frameworks/nextjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- [Runtime Config](#runtime-config)
- [Custom Webpack Config](#custom-webpack-config)
- [Typescript](#typescript)
- [Experimental React Server Components (RSC)](#experimental-react-server-components-rsc)
- [Notes for Yarn v2 and v3 users](#notes-for-yarn-v2-and-v3-users)
- [FAQ](#faq)
- [Stories for pages/components which fetch data](#stories-for-pagescomponents-which-fetch-data)
Expand Down Expand Up @@ -908,6 +909,39 @@ Storybook handles most [Typescript](https://www.typescriptlang.org/) configurati
}
```
### Experimental React Server Components (RSC)
If your app uses [React Server Components (RSC)](https://nextjs.org/docs/app/building-your-application/rendering/server-components), Storybook can render them in stories in the browser.
To enable this set the `experimentalNextRSC` feature flag in your `.storybook/main.js` config:
```js
// main.js
export default {
features: {
experimentalNextRSC: true,
},
};
```
Setting this flag automatically wraps your story in a [Suspense](https://react.dev/reference/react/Suspense) wrapper, which is able to render asynchronous components in NextJS's version of React.
If this wrapper causes problems in any of your existing stories, you can selectively disable it using the `nextjs.rsc` [parameter](https://storybook.js.org/docs/writing-stories/parameters) at the global/component/story level:
```js
// MyServerComponent.stories.js
export default {
component: MyServerComponent,
parameters: { nextjs: { rsc: false } },
};
```
Note that wrapping your server components in Suspense does not help if your server components access server-side resources like the file system or Node-specific libraries. To deal work around this, you'll need to mock out your data access layer using [Webpack aliases](https://webpack.js.org/configuration/resolve/#resolvealias) or an addon like [storybook-addon-module-mock](https://storybook.js.org/addons/storybook-addon-module-mock).
If your server components access data via the network, we recommend using the [MSW Storybook Addon](https://storybook.js.org/addons/msw-storybook-addon) to mock network requests.
In the future we will provide better mocking support in Storybook and support for [Server Actions](https://nextjs.org/docs/app/api-reference/functions/server-actions).
### Notes for Yarn v2 and v3 users
If you're using [Yarn](https://yarnpkg.com/) v2 or v3, you may run into issues where Storybook can't resolve `style-loader` or `css-loader`. For example, you might get errors like:
Expand Down
2 changes: 2 additions & 0 deletions code/frameworks/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"import": "./dist/font/webpack/loader/storybook-nextjs-font-loader.mjs"
},
"./dist/preview.mjs": "./dist/preview.mjs",
"./dist/previewRSC.mjs": "./dist/previewRSC.mjs",
"./next-image-loader-stub.js": {
"types": "./dist/next-image-loader-stub.d.ts",
"require": "./dist/next-image-loader-stub.js",
Expand Down Expand Up @@ -152,6 +153,7 @@
"./src/index.ts",
"./src/preset.ts",
"./src/preview.tsx",
"./src/previewRSC.tsx",
"./src/next-image-loader-stub.ts",
"./src/images/decorator.tsx",
"./src/images/next-legacy-image.tsx",
Expand Down
15 changes: 11 additions & 4 deletions code/frameworks/nextjs/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,17 @@ export const core: PresetProperty<'core'> = async (config, options) => {
};
};

export const previewAnnotations: PresetProperty<'previewAnnotations'> = (entry = []) => [
...entry,
join(dirname(require.resolve('@storybook/nextjs/package.json')), 'dist/preview.mjs'),
];
export const previewAnnotations: PresetProperty<'previewAnnotations'> = (
entry = [],
{ features }
) => {
const nextDir = dirname(require.resolve('@storybook/nextjs/package.json'));
const result = [...entry, join(nextDir, 'dist/preview.mjs')];
if (features?.experimentalNextRSC) {
result.unshift(join(nextDir, 'dist/previewRSC.mjs'));
}
return result;
};

// Not even sb init - automigrate - running dev
// You're using a version of Nextjs prior to v10, which is unsupported by this framework.
Expand Down
10 changes: 10 additions & 0 deletions code/frameworks/nextjs/src/previewRSC.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Addon_DecoratorFunction } from '@storybook/types';
import { ServerComponentDecorator } from './rsc/decorator';

export const decorators: Addon_DecoratorFunction<any>[] = [ServerComponentDecorator];

export const parameters = {
nextjs: {
rsc: true,
},
};
14 changes: 14 additions & 0 deletions code/frameworks/nextjs/src/rsc/decorator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as React from 'react';
import type { StoryContext } from '@storybook/react';

export const ServerComponentDecorator = (
Story: React.FC,
{ parameters }: StoryContext
): React.ReactNode =>
parameters?.nextjs?.rsc ? (
<React.Suspense>
<Story />
</React.Suspense>
) : (
<Story />
);
5 changes: 5 additions & 0 deletions code/frameworks/nextjs/template/stories/RSC.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export const RSC = async ({ label }) => <>RSC {label}</>;

export const Nested = async ({ children }) => <>Nested {children}</>;
35 changes: 35 additions & 0 deletions code/frameworks/nextjs/template/stories/RSC.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { RSC, Nested } from './RSC';

export default {
component: RSC,
args: { label: 'label' },
};

export const Default = {};

export const DisableRSC = {
tags: ['test-skip'],
parameters: {
chromatic: { disable: true },
nextjs: { rsc: false },
},
};

export const Error = {
tags: ['test-skip'],
parameters: {
chromatic: { disable: true },
},
render: () => {
throw new Error('RSC Error');
},
};

export const NestedRSC = {
render: (args) => (
<Nested>
<RSC {...args} />
</Nested>
),
};
20 changes: 20 additions & 0 deletions code/lib/cli/src/sandbox-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ const baseTemplates = {
renderer: '@storybook/react',
builder: '@storybook/builder-webpack5',
},
modifications: {
mainConfig: {
features: { experimentalNextRSC: true },
},
},
skipTasks: ['e2e-tests-dev', 'bench'],
inDevelopment: true,
},
Expand All @@ -128,6 +133,11 @@ const baseTemplates = {
renderer: '@storybook/react',
builder: '@storybook/builder-webpack5',
},
modifications: {
mainConfig: {
features: { experimentalNextRSC: true },
},
},
skipTasks: ['e2e-tests-dev', 'bench'],
},
'nextjs/default-ts': {
Expand All @@ -139,6 +149,11 @@ const baseTemplates = {
renderer: '@storybook/react',
builder: '@storybook/builder-webpack5',
},
modifications: {
mainConfig: {
features: { experimentalNextRSC: true },
},
},
skipTasks: ['e2e-tests-dev', 'bench'],
},
'nextjs/prerelease': {
Expand All @@ -150,6 +165,11 @@ const baseTemplates = {
renderer: '@storybook/react',
builder: '@storybook/builder-webpack5',
},
modifications: {
mainConfig: {
features: { experimentalNextRSC: true },
},
},
skipTasks: ['e2e-tests-dev', 'bench'],
},
'react-vite/default-js': {
Expand Down
5 changes: 5 additions & 0 deletions code/lib/types/src/modules/core-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,11 @@ export interface StorybookConfigRaw {
* This will make sure that your story renders the same no matter if docgen is enabled or not.
*/
disallowImplicitActionsInRenderV8?: boolean;

/**
* Enable asynchronous component rendering in NextJS framework
*/
experimentalNextRSC?: boolean;
};

build?: TestBuildConfig;
Expand Down
1 change: 1 addition & 0 deletions scripts/tasks/test-runner-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const testRunnerBuild: Task & { port: number } = {
'--junit',
'--maxWorkers=2',
'--failOnConsole',
'--skipTags="test-skip"',
];

// index-json mode is only supported in ssv7
Expand Down

0 comments on commit e8fb099

Please sign in to comment.