Setup Next.js, Storybook and Tailwind on a Stackomate Dev-Container
Tags: devcontainer, nextjs, storybook, tailwindcss
Published: 2023-09-01 05:06:01

In this tutorial we will see how to setup a reproducible web development stack containing Next.js, Storybook and Tailwind inside a dev-container. 

Using a dev-container provides a reproducible environment for your team, and guarantees the isolation of your project files. For example, different projects may contain different Node.js and/or Python versions. We will be using the Stackomate CLI, a global npm package capable of generating templates for development containers for variable purposes. 

## Creating a Dev-Container

First, let's create our new dev-containers with the Stackomate CLI by running 
`npx -y stackomate@latest dev-container create` and following the default instructions. You will be prompted to select a **container type**. Below we explain the differences between two common options: 

### Option 1 - Node.js Dev-Container
You may select the `node` dev-container to achieve smaller image sizes and faster startup times. The developer can connect to the container via the terminal.

⚠️ Specifically for our web stack, internal ports `3000` and `6006` will be used by Next.js and Storybook servers, respectively, and thus they must be published to the host. This can be achieved by adding the following lines to the `dev.docker-compose.yml` file created by the stackomate cli:

```
ports:
  - 3000:3000
  - 6006:6006
expose:
  - 3000
  - 6006
```

### Option 2 - Full Desktop Dev-Container
The `desktop-web` type contains a Ubuntu desktop interface with Node.js and a built-in reverse proxy installed. The developer can access Firefox, a default terminal and a simple file manager on the browser. It is a great choice for larger projects requiring complex configuration, such as website stacks requiring the generation of local SSL certificates. The desktop interface will be published internally on port `6080`, by default. If you need another port, you can change the following lines in the generated `dev.docker-compose.yml` file:

```
ports:
  - 6080:6080
expose:
  - 6080
```

ℹ️ **Note:** you may create both container types for your web stack, if desired. You may also run them both at the same time.

Once you have created and edited your dev-containers, follow instructions in the generated `README.md` files to start them. For example, the `node` dev-container can be started by running `cd ./dev-container/node && ./dev.sh` (or `./sudo-dev.sh`, if your Docker installation requires `sudo` permissions).

# Creating Your Next.js App
Now that we have our container running, it is time to start creating our Next.js app inside of it:

- Access `/app` inside your container terminal: `cd /app`;
- Call the `create-next-app` cli: `npx -y create-next-app@latest`;

We recommend using the following options, except for the project name, which may be chosen arbitrarily. With these settings, Next.js will spare us from installing Tailwind and ESLint separately, and our project structure will contain a `src` folder and the latest `App` Router:
```
What is your project named? › my-test-app
Would you like to use TypeScript? › No / (Yes)
Would you like to use ESLint? › No / (Yes)
Would you like to use Tailwind CSS? › No / (Yes)
Would you like to use `src/` directory? › No / (Yes)
Would you like to use App Router? (recommended) › No / (Yes)
Would you like to customize the default import alias? › (No) / Yes
``` 

ℹ️ If you ever want to add Tailwind to an existing Next.js project, you can follow their [official guide](https://tailwindcss.com/docs/guides/nextjs) for more instructions.

⚠️ If you have created the Next.js project **without** the `src` folder, please make sure to update all imports in the next parts of this article to reflect the folder structure changes. For example, the `src/app/globals.css` file would otherwise be located in `app/globals.css`.

- Wait for the bootstrapping process to be complete;
- `cd /app/my-test-app`;

ℹ️ You can ignore linting warnings at the `./src/app/globals.css` file, such as `Unknown at rule @tailwindcss(unknownAtRules)`. If you really want to fix it though, you can follow [this solution](https://www.codeconcisely.com/posts/tailwind-css-unknown-at-rules/#adding-custom-at-rules) to add `customAt` rules.

## Installing Storybook

We have Next.js and Tailwind installed, and now we must install Storybook and integrate all three:

- Run `npx -y storybook@latest init`

ℹ️ Storybook should automatically detect the Next.js project type. It should also detect ESLint and ask:
```
We have detected that you're using ESLint. Storybook provides a plugin that gives the best experience with Storybook and helps follow best practices: https://github.com/storybookjs/eslint-plugin-storybook#readme

Would you like to install it? › (Y/n)
```
Choose **Y** to proceed with the [Storybook ESLint Plugin](https://github.com/storybookjs/eslint-plugin-storybook) installation.

Once Storybook has finished installing, it will automatically start on port `6006`. 

- Go to your browser and open http://localhost:6006

Now Storybook has been seamlessly integrated with Next.js. You can check your `package.json` file for a `@storybook/nextjs` dependency. More details about all Next.js features supported by Storybook can be viewed on their [package documentation page](https://www.npmjs.com/package/@storybook/nextjs).

## Adding Support for Tailwind on Storybook

Although Storybook has detected and integrated with our Next.js project, it has not detected Tailwind by default.

ℹ️ You can verify it by editing the `src/stories/Header.tsx` file: add the `bg-red-500` class to the element containing `className="storybook-header"`. If the header background does not turn red, then Tailwind is not working with Storybook yet.



Now, we integrate Tailwind with Storybook:

- Include the `src/stories` directory in your `tailwind.config.js`, so that Tailwind can find and target React components in that folder: 

```
const config: Config = {
  content: [
    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
    './src/stories/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  // ...
```

⚠️ Before proceeding, we recommend to **commit your git changes** in the project directory: for example, run `git add -A` and `git commit -m 'chore: add storybook and tailwind'`.

- Stop storybook, and install the `@storybook/addon-styling` package in your project: 

```
npm i -D @storybook/addon-styling
```
- Now run a codemod to help with the integration:
```
npx addon-styling-setup
```

You should see an output like this:

```
=========================================

 🧰 Configuring @storybook/addon-styling

=========================================

(1/3) Project summary
  • Built with webpack
  • Styled with tailwind

(2/3) .storybook/main.ts
  • Registering @storybook/addon-styling.

(3/3) .storybook/preview.ts
  • Adding import for withThemeByClassName decorator
  • Adding import for stylesheet
  • Adding withThemeByClassName decorator to config

✨ Done
```

- Go to `.storybook/preview.ts`, and adjust the line below:
```
/* TODO: update import to your tailwind styles file. If you're using Angular, inject this through your angular.json config instead */
import '../src/index.css';
```

to

```
/* Import for our tailwind global styles file */
import '../src/app/globals.css';
```

- Remove the styles generated by Next.js from the `src/app/globals.css` file, and just leave the Tailwind imports:

```
@tailwind base;
@tailwind components;
@tailwind utilities;
```

- Open your storybook again by running `npm run storybook`

🎉 Now, you should now see the `Header.tsx` component with the **red background** in the `Header` story! 🎉

### Integrating Tailwind Themes with Storybook

Tailwind can allow the user to change the appearance of a website using light and dark themes. However, some minor changes must be made if you want to toggle themes in the Storybook preview, which we will see next.

ℹ️ To verify support for dark themes, add `dark:bg-yellow-600` to your `Header.tsx` component, and then toggle from `light theme` to `dark theme` in the top dropdown many times. Notice how only one of the background colors is used. This happens because, by default, Tailwind *"uses the prefers-color-scheme CSS media feature"* [(source)](https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually). Instead, we want to toggle it programatically:

- Go to `tailwind.config.ts` and set the `darkMode` property to support manual toggling by class:

```
const config: Config = {
  darkMode: 'class',
  content: [
    // ...
}
```

Now, you should see the `Header.tsx` component change its background color from red to yellow depending on the theme you choose! 

🎉 You can now develop your Next.js website with Tailwind and Storybook, happy coding! 🎉

ℹ️ Once you want to exit your dev-container, use `CTRL+C` to exit the storybook server, and type `exit` to stop and remove the development-container. Don't worry about your project files, they will not be lost as they are being mapped to the host 🙂.

If desired, we can further push our dev-containers to GitHub, and distribute them with other people in the team. 

## Conclusion

In this tutorial, we described a series of steps to create a complex project stack using Next.js, Tailwind and Storybook, along with some challenges and fixes to integrate the existing tools. We have used the Stackomate CLI to help us generate a dev-container and redistribute our development environment in a more predictable way. In the next articles, we will see further improvements that our stack can use to support more common features desired during development.