loading...

NextJS + TypeScript + TailwindCSS + Storybook project setup

0xcap profile image Andrea Cappuccio Updated on ・3 min read

TUTORIAL BASED ON STORYBOOK v5, ON v6 EVERYTHING STORYBOOK-RELATED WORKS OUT OF THE BOX!

Following is a simple guide to get up and running with the NextJS+TypeScript+TailwindCSS+Storybook combination, a task which took me a lot more time than I originally estimated, due to the unexpected lack of specific guides on how to deal with this particular scenario plus the sparse nature of the information I had to look up to set everything up as desired.

NEXTJS SETUP

yarn create next-app

That's it. Done. A fully-functional NextJS app will be created using the official starter kit.


TAILWINDCSS SETUP

  1. yarn add -D tailwindcss postcss-preset-env to install the TailwindCSS library and some useful PostCSS polyfills
  2. npx tailwind init to generate a tailwind.config.js file.
  3. Edit the TailwindCSS configuration file we just created to enable and configure the built-in CSS purging process. (Notice how I'm using the ".tsx'" extension here, since I already know I'm going to use TypeScript for this project)
module.exports = {
  purge: ['./components/**/*.tsx', './pages/**/*.tsx'],
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}
  1. Create postcss.config.js to configure PostCSS in a minimal way, like the following
module.exports = {
  plugins: [
    "tailwindcss", 
    "postcss-preset-env"
  ]
};
  1. Create /styles/index.css and populate it using the postcss-import-friendly @import directives (instead of using @tailwind)
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

TYPESCRIPT SETUP

  1. yarn add --dev typescript @types/react @types/node
  2. touch tsconfig.json
  3. yarn next to start NextJS, which will automagically recognize our newly created tsconfig.json and inject a valid configuration json into it
  4. Create a /pages/_app.tsx file
import React from "react";
import "../styles/index.css"; // <- applied everywhere in the NextJS application scope

const MyApp = ({ Component, pageProps }) => {
  return <Component {...pageProps} />;
};

export default MyApp;

STORYBOOK SETUP

  1. yarn add @storybook/react babel-loader @babel/core awesome-typescript-loader react-docgen-typescript-loader -D
  2. mkdir .storybook
  3. cp ./tsconfig.json ./.storybook/
  4. Edit /.storybook/tsconfig.json to suit your Storybook-TypeScript integration
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react"            // <- important!
  },
  "exclude": ["node_modules"],
  "include": [                // <- important!
    "../types.d.ts",
      "../next-env.d.ts",
      "../**/*.stories.ts",
      "../**/*.stories.tsx"
    ]
  }
  1. Create /.storybook/main.js and feel free to copy the following configuration
const path = require("path");

module.exports = {
  stories: ["../components/**/**/*.stories.tsx"],
  webpackFinal: async (config) => {
    config.module.rules.push({
      test: /\.(ts|tsx)$/,
      use: [
        {
          loader: require.resolve("awesome-typescript-loader"),
          options: {
            configFileName: path.resolve(__dirname, "./tsconfig.json"),
          },
        },
        /* 
          ** OPTIONAL ** 

          Basically a webpack loader  used to                
          generate docgen information from TypeScript React components. 
The primary use case is to get the prop types 
table populated in the Storybook Info Addon.
        */
        {
          loader: require.resolve("react-docgen-typescript-loader"),
          options: {
            tsconfigPath: path.resolve(__dirname, "./tsconfig.json"),
          },
        },
      ],
    });

    config.resolve.extensions.push(".ts", ".tsx");

    return config;
  },
};
  1. create /.storybook/preview.js and use it to import our stylesheet as follows
// The preview application is essentially just your stories with 
// a framework-agnostic 'router'. 
// It renders whichever story the manager application tells it to render.
// In this case we just use it to import the stylesheet and inject it 
// in the context of our stories

import "../styles/index.css";
  1. Update postcss.config.js to use an object-based format. This is super important in order to solve any issues Storybook's webpack process may encounter while resolving the dependencies!
module.exports = {
  plugins: {
    tailwindcss: {}, 
    "postcss-preset-env": {}
  }
};

DONE

Now you will be able to create your component stories in your own component's folders (ex.: /components/button/1-button.stories.tsx) using this powerful toolset.

P.S.: If you wish to organize your Storybook stories using a different folder structure, you'll need to do no more than editing the stories property inside the exported configuration in /.storybook/main.js using your desired glob patterns

Discussion

markdown guide
 

So glad you wrote this article on my birthday. Thank you very much for writing this. I've been stuck on Storybook postcss.config.js webpack issue. Am quite surprised the fix is as easy as making tailwindcss: {} an object. My StoryBook setup is now loading tailwind styles🙏🏽

 

Happy (belated) birthday man! As you pointed out the solution was pretty simple to implement, yet absolutely unintuitive. I'm glad I saved you some headaches!

 

You kick ass, thanks for the article! FINALLY figured out the missing plugin in my setup...

 

Thank for the positive feedback! What was the missing plugin? BTW if Storybook was the origin of your issues, I strongly suggest you try v6, which makes the integration really seamless.

 

Agreed--I'm on v6 but had to add the tailwind plugin to postcss.config.js as an object for Storybook to work, which you thankfully pointed out :-)

 

Would have never guessed that it should be an object structure. Thanks!