Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Configure Storybook to work with Next.js, TypeScript, and CSS Modules

In addition to the Storybook for React setup, you'll also need to install these packages:

npm i -D @babel/core babel-loader css-loader style-loader
const path = require('path');
module.exports = {
stories: ['../stories/**/*.stories.js', '../stories/**/*.stories.tsx'],
addons: ['@storybook/addon-actions', '@storybook/addon-links'],
presets: [path.resolve(__dirname, './next-preset.js')]
};
const path = require('path');
module.exports = {
webpackFinal: async (baseConfig, options) => {
// Modify or replace config. Mutating the original reference object can cause unexpected bugs.
const { module = {} } = baseConfig;
const newConfig = {
...baseConfig,
module: {
...module,
rules: [...(module.rules || [])]
}
};
// TypeScript with Next.js
newConfig.module.rules.push({
test: /\.(ts|tsx)$/,
include: [
path.resolve(__dirname, '../components'),
path.resolve(__dirname, '../stories')
],
use: [
{
loader: 'babel-loader',
options: {
presets: ['next/babel'],
plugins: ['react-docgen']
}
}
]
});
newConfig.resolve.extensions.push('.ts', '.tsx');
//
// CSS Modules
// Many thanks to https://github.com/storybookjs/storybook/issues/6055#issuecomment-521046352
//
// First we prevent webpack from using Storybook CSS rules to process CSS modules
newConfig.module.rules.find(
rule => rule.test.toString() === '/\\.css$/'
).exclude = /\.module\.css$/;
// Then we tell webpack what to do with CSS modules
newConfig.module.rules.push({
test: /\.module\.css$/,
include: path.resolve(__dirname, '../components'),
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: true
}
}
]
});
return newConfig;
}
};
// If you need global CSS, you can import it here and Storybook will automatically include it in all stories.
// You don't need this if you don't have any global CSS.
import '../src/styles.css';
@kris10cabrera

This comment has been minimized.

Copy link

@kris10cabrera kris10cabrera commented Apr 27, 2020

@justincy thanks for this. Where do main.js, next-preset.js, and preview.js go? Inside of .storybook/?

@justincy

This comment has been minimized.

Copy link
Owner Author

@justincy justincy commented Apr 27, 2020

@kris10cabrera

This comment has been minimized.

Copy link

@kris10cabrera kris10cabrera commented Apr 27, 2020

Thanks. Last question, is Typescript required to use storybook with NextJS? @justincy

@justincy

This comment has been minimized.

Copy link
Owner Author

@justincy justincy commented Apr 27, 2020

@justincy

This comment has been minimized.

Copy link
Owner Author

@justincy justincy commented Apr 27, 2020

@kris10cabrera I don't believe there's anything special you need to do in order to use Storybook with Next.js if you're not using Typescript.

@kris10cabrera

This comment has been minimized.

Copy link

@kris10cabrera kris10cabrera commented Apr 27, 2020

Thank you @justincy

@ademilter

This comment has been minimized.

Copy link

@ademilter ademilter commented May 22, 2020

thaaaanks @justincy

postcss-loader

{
  loader: 'postcss-loader',
  options: {
    sourceMap: true,
    config: {
      path: './.storybook/'
    }
  }
}
@mateusvahl

This comment has been minimized.

Copy link

@mateusvahl mateusvahl commented Jun 1, 2020

my hero

@hasantezcan

This comment has been minimized.

Copy link

@hasantezcan hasantezcan commented Jun 8, 2020

Thanks 🎈

@trinwin

This comment has been minimized.

Copy link

@trinwin trinwin commented Jul 5, 2020

Thank you 🎉

I use scss so I replace CSS module part with this ↓


// SCSS preset for Storybook
 newConfig.module.rules.push({
      test: /\.(s*)css$/,
      loaders: ["style-loader", "css-loader", "sass-loader"],
      include: path.resolve(__dirname, "../src/styles/global.scss"),
 });

@marteinn

This comment has been minimized.

Copy link

@marteinn marteinn commented Aug 4, 2020

Brilliant, thanks for the guide @justincy!

@RichardBosworth

This comment has been minimized.

Copy link

@RichardBosworth RichardBosworth commented Sep 18, 2020

Is there a way to handle SCSS modules?

I've created a "componentname.module.scss" file, and it works in NextJS but can't seem to get it to load in Storybook!

@justincy

This comment has been minimized.

Copy link
Owner Author

@justincy justincy commented Sep 18, 2020

@RichardBosworth See the comment by trinwin about SCSS.

@RichardBosworth

This comment has been minimized.

Copy link

@RichardBosworth RichardBosworth commented Sep 18, 2020

@RichardBosworth See the comment by trinwin about SCSS.

Am I right in thinking that the above only applies to the global SCSS file? I'm trying to get Storybook specifically to work with CSS Modules but using SCSS instead of plain CSS. So my Component file references an "import styles from './componentfile.module.scss'.

Everything works great when I use just plain CSS Modules. But when I try to use sass-loader to load the modules, it throws an error. If I try to pass the options in your original posting to sass-loader, it says that they're incorrect.

@justincy

This comment has been minimized.

Copy link
Owner Author

@justincy justincy commented Sep 18, 2020

@RichardBosworth

Am I right in thinking that the above only applies to the global SCSS file?

I don't believe so. The code snippet appears to be loading both scss and css module files and then explicitly including one global css file.

Did you try it?

@RichardBosworth

This comment has been minimized.

Copy link

@RichardBosworth RichardBosworth commented Sep 22, 2020

@RichardBosworth

Am I right in thinking that the above only applies to the global SCSS file?

I don't believe so. The code snippet appears to be loading both scss and css module files and then explicitly including one global css file.

Did you try it?

So the solution was:

newConfig.module.rules.push({
            test: /\.module\.(s*)css$/,
            include: path.resolve(__dirname, '../components'),
            use: [
                'style-loader',
                {
                    loader: 'css-loader',
                    options: {
                        importLoaders: 1,
                        modules: true,
                    },
                },
                'sass-loader',
            ],
        });

Adding the sass-loader after the css-loader modules bit. Also adding the (s*)css bit.

@rohanvachheta

This comment has been minimized.

Copy link

@rohanvachheta rohanvachheta commented Oct 2, 2020

thanks

@emadabdulrahim

This comment has been minimized.

Copy link

@emadabdulrahim emadabdulrahim commented Oct 6, 2020

Thanks @justincy!

@oguzkaracar

This comment has been minimized.

Copy link

@oguzkaracar oguzkaracar commented Oct 17, 2020

thaaaanks @justincy

postcss-loader

{
  loader: 'postcss-loader',
  options: {
    sourceMap: true,
    config: {
      path: './.storybook/'
    }
  }
}

addition of this for: postcss-loader@4.0.4

{
   loader: "postcss-loader",
	options: {
	   postcssOptions: {
		sourceMap: true,
		config: path.resolve(__dirname, "../postcss.config.js"),
           },
        },
},
@drskullster

This comment has been minimized.

Copy link

@drskullster drskullster commented Oct 23, 2020

I managed to get SCSS modules working with @storybook/preset-scss :

{
  name: '@storybook/preset-scss',
  options: {
    cssLoaderOptions: {
      modules: {
        auto: true
      }
    }
  }
},

Global style import also works in preview.js.

https://github.com/storybookjs/presets/tree/master/packages/preset-scss

EDIT: I added the auto option to css modules to prevent classes in my global files to be scoped.

@ilkeryilmaz

This comment has been minimized.

Copy link

@ilkeryilmaz ilkeryilmaz commented Oct 23, 2020

I managed to get SCSS modules working with @storybook/preset-scss :

{
  name: '@storybook/preset-scss',
  options: {
    cssLoaderOptions: {
      modules: true,
    }
  }
},

Global style import also works in preview.js.

https://github.com/storybookjs/presets/tree/master/packages/preset-scss

The clean solution, thanks @drskullster

@andymeek

This comment has been minimized.

Copy link

@andymeek andymeek commented Dec 2, 2020

Thanks @justincy!

I'm experiencing some issues with Storybook not applying directives (e.g., @apply) correctly, however, it works with NextJS.

Do you have any ideas about this, and do they work for you?

@justincy

This comment has been minimized.

Copy link
Owner Author

@justincy justincy commented Dec 2, 2020

@andymeek I don't use @apply. Are you using Tailwind? I got that working once with @apply by including postcss-loader.

use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 1,
            modules: true
          }
        }, 'postcss-loader'
      ]
@njcaballero

This comment has been minimized.

Copy link

@njcaballero njcaballero commented Dec 10, 2020

I managed to get SCSS modules working with @storybook/preset-scss :

{
  name: '@storybook/preset-scss',
  options: {
    cssLoaderOptions: {
      modules: {
        auto: true
      }
    }
  }
},

Global style import also works in preview.js.

https://github.com/storybookjs/presets/tree/master/packages/preset-scss

EDIT: I added the auto option to css modules to prevent classes in my global files to be scoped.

This worked for me too! thanks @drskullster

@EyalPerry

This comment has been minimized.

Copy link

@EyalPerry EyalPerry commented Dec 27, 2020

Below is the config I used to get css-modules to work with tailwind + scss + anything you put in postcss- the same as in the SPA via Next.js v10.
Note that I manually installed all of the below packages since I've had some PostCss warnings which indicated I am not passing in any config, which made me suspect PostCss7 was used somewhere below. If you experience the lack of tailwind styling in storybook- that's probably it.
Also note that sass-loader comes last in the loader array.

 const removeIndex = newConfig.module.rules.findIndex(
      (rule) => rule.test.toString() === "/\\.css$/"
    );

    if (removeIndex !== -1) {
      newConfig.module.rules.splice(removeIndex, 1);
    }

    newConfig.module.rules.push({
      test: /\.(s*)css$/,
      loaders: [
        "style-loader",
        {
          loader: "css-loader",
          options: {
            importLoaders: 1,
            modules: { auto: true },
          },
        },
        {
          loader: "postcss-loader",
          options: {
            postcssOptions: {
              config: path.resolve(__dirname, "..", "postcss.config.js"),
            },
          },
        },
        "sass-loader",
      ],
    });

This is my package.json:

"devDependencies": {
    "@storybook/react": "6.1.11",
    "css-loader": "5.0.1",
    "postcss": "8.2.1",
    "postcss-flexbugs-fixes": "5.0.2",
    "postcss-import": "14.0.0",
    "postcss-loader": "4.1.0",
    "postcss-preset-env": "6.7.0",
    "sass": "1.30.0",
    "sass-loader": "10.1.0",
    "style-loader": "2.0.0"
  }

As a result, I was able to use both tailwind and scss syntax in my css modules:

.disabled {
  @apply hover:shadow-none;

  @extend .non-interactive;
}

Make sure you import your global css files in preview.js, the same way your _app component would.

@canerorenn

This comment has been minimized.

Copy link

@canerorenn canerorenn commented Jan 30, 2021

Thanks @justincy

A config for NextJS Absolute Imports and Module path aliases

somewhere convenient in next-preset.js

baseConfig.resolve.alias = {
  ...baseConfig.resolve.alias,
  "@/components": path.resolve(__dirname, "../components"),
};
@batur

This comment has been minimized.

Copy link

@batur batur commented Feb 28, 2021

Thanks @justincy

@joebartels

This comment has been minimized.

Copy link

@joebartels joebartels commented Mar 31, 2021

What a fun time figuring this out. thanks everyone above for the information 🙏 🙇.
Unfortunately none of yall's solutions seemed to work for my setup, but they gave me the direction to figure it out 😄

Here are relevant pieces to my setup. Most notably typescript, nextjs, tailwind, postcss, sass, css modules

"dependencies": {
    "next": "10.1.2",
    "react": "17.0.2",
    "tailwindcss": "^2.0.4"
},
"devDependencies": {
    "@storybook/react": "^6.2.0",
    "css-loader": "^3.6.0",
    "postcss": "^8.2.9",
    "postcss-loader": "^4.2.0",
    "sass": "^1.32.6",
    "sass-loader": "^10.1.1",
    "style-loader": "^1.3.0",
    "typescript": "^4.2.3",
}
// .storybook/main.js
const path = require("path");

module.exports = {
    stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
    addons: [
        "@storybook/addon-links",
        "@storybook/addon-essentials",
    ],
    webpackFinal: async (config) => {
        config.module.rules.push({
            test: /\.(sc|sa|c)ss$/,
            use: [
                "style-loader",
                {
                    loader: "css-loader",
                    options: {
                        importLoaders: 1,
                        modules: {
                            auto: true,
                        },
                    },
                },
                {
                    loader: "postcss-loader",
                    options: {
                        // it "compiles" when I omit these options but I didn't confirm whether the correct postcss
                        // version is imported when omiting this.. So having it here helps me sleep at night
                        implementation: require("postcss"),
                        postcssOptions: {
                            config: path.resolve(__dirname, "..", "postcss.config.js"),
                        },
                    },
                },
                "sass-loader",
            ],
            include: path.resolve(__dirname, "..", "src"),
        });

        return config;
    },
};

Key reasons why the above worked:

  1. { modules: { auto: true } } is magical.
    • Most notably it only enables CSS modules on filenames that match /\.module(s)?\.\w+$/i which allows you to use same rule for regular css files (css files that are not imported as modules into jsx/tsx) and not have to manually exclude .module.scss files (as others have done above 👆)
  2. test: /\.(sc|sa|c)ss$/
    • this project uses both scss and css (and potentially sass) 😨
  3. include: path.resolve(__dirname, "..", "src"),
    • without this, some 3rd party .css imports get processed and break for w/e reason 🤷‍♂️
  4. The order of the loaders, style-loader -> css-loader -> postcss-loader -> sass-loader
    • is only way everything seemed to work together

And here are things that DID NOT work and my (possibly incorrect) understanding on why...

@storybook/addon-postcss

I added it because I got a deprecation warning that relying on implicit PostCSS loader is deprecated, but b/c I'm manually including the postcss-loader for .css files I don't think it's necessary and is just a distraction and not really doing anything.

@storybook/preset-scss

This was the most frustrating false hope. It's ONLY useful if you don't need to process s[ca]ss files with postcss. But because I'm using tailwindcss (which relies on postcss) it's completely useless. See why, here.

P.S. what was crucial to debugging this was printing the webpack storybook is using, via:

npm run storybook -- --debug-webpack

Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment