Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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).
Comment thread
shilman marked this conversation as resolved.

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