DEV Community

Andrea Cappuccio
Andrea Cappuccio

Posted on • Edited on • Originally published at stackrant.com

NextJS + TypeScript + TailwindCSS + Storybook project setup

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: [],
}
Enter fullscreen mode Exit fullscreen mode
  1. Create postcss.config.js to configure PostCSS in a minimal way, like the following
module.exports = {
  plugins: [
    "tailwindcss", 
    "postcss-preset-env"
  ]
};
Enter fullscreen mode Exit fullscreen mode
  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";
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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"
    ]
  }
Enter fullscreen mode Exit fullscreen mode
  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;
  },
};
Enter fullscreen mode Exit fullscreen mode
  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";
Enter fullscreen mode Exit fullscreen mode
  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": {}
  }
};
Enter fullscreen mode Exit fullscreen mode

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

Top comments (7)

Collapse
 
brandiqa profile image
Michael Wanyoike

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🙏🏽

Collapse
 
hood profile image
Andrea Cappuccio

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!

Collapse
 
thinkjrs profile image
Jason R. Stevens, CFA

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

Collapse
 
hood profile image
Andrea Cappuccio

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.

Collapse
 
thinkjrs profile image
Jason R. Stevens, CFA

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 :-)

Collapse
 
andersravn profile image
Anders Ravn

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

Collapse
 
hood profile image
Andrea Cappuccio

Glad I helped you too!