How do I setup my NextJs development environment
Tags: nextjs, cypress, storybook, typescript
Published: 2020-10-21 10:21:36

---
title:  How do I setup my NextJs development environment
published: true
description: In this post, I will introduce how do I setup my development environment for NextJs. 
tags: nextjs,cypress,storybook,typescript
---


In this post, I will introduce how do I setup my development environment for NextJs. My development environments include:

```shell
cypress                     // For component testing
cypress-react-unit-test
storybook                   // Quickly view react components
tailwindcss                 // CSS framework
typescript                  // Provide type safety
```

In this post, I will assume you have NodeJs and yarn installed in your global environment. Also, I am assuming you know why we need those dependencies and won't explain much about them.

## TLDR
Just checkout the repository:  https://github.com/cuichenli/nextjs-typescript-cypress-storybook-tailwind

## Let's start with NextJs

To start with, let's install `create-next-app` via `yarn` so we can use it to setup some basic stuff.

```shell
yarn global add create-next-app
create-next-app
```

After you type the above commands in your terminal, `create-next-app` will ask you the name of the project, I will use `my-app` here.
Once you are done with the questions, you can run `cd` to go into the newly created project.

## Typescript

There might be some other typescript templates out there, but I chose to do it myself, as I would like to see what are the things we need to do. So, to start with,

```shell
yarn add --dev @types/react typescript @types/node
```

Next we should convert the generated `_app.js` and `index.js` in `pages` directory to `tsx` file. For `_app.js`, simple rename it, for `index.js`, rename it and remove the imported `css` (you may need some other configurations to use those import, I will not introduce it here) and simplify the `index.js` file like this:

```tsx
import React from "react";

export default function Home() {
  return <p>hello</p>;
}
```

Once the files are renamed, you can run `yarn dev` to start the `NextJs` development server. `NextJs` will kindly help you to generate one simple `tsconfig.json` and one `next-env.d.ts` file as it detected you are using `TypeScript`.

BTW, in this step, I also moved `pages` into `src` directory. This is optional of course.

## StoryBook

I started with add `storybook` as global command here, and then use the recommend `sb init` to initialize the files

```shell
yarn global add storybook
yarn sb init
```

Once the commands finished, you will see two new directories

- `.storybook`: This one contains the main configurations for StoryBook, it is created in the root directory.
- `stories`: This one stores the stories you would like to use. StoryBook will generate a bunch of example stories for you, you can just delete them. In my case, this directory is generated in `src` directory. And I manually moved it out to root directory.

### Configure StoryBook

The generated configurations files `.storybook/main.js` contains the main configurations for your storybook. In my case, since I moved the generated `stories` directory, I need to change the entry `stories`

```js
module.exports = {
  // Update stories to where you would like to store them.
  stories: ["../stories/**/*.stories.mdx", "../stories/**/*.stories.tsx"],
  addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
};
```

### Try StoryBook

I added one simple component to try if the StoryBook is configured correctly.

```tsx
// src/components/text-input.tsx
import React from "react";

export const TextInputComponent: React.FunctionComponent = () => {
  return (
    <div>
      <input placeholder="type"></input>
    </div>
  );
};
```

```tsx
// ./stories/text-input.stories.tsx
import React from "react";
import { TextInputComponent } from "../src/components/text-input";
export default {
  title: "story/text-input",
};

export const Primary: React.FunctionComponent = () => <TextInputComponent />;
```

Once the above files are added, run

```shell
yarn run storybook
```

and you will see the newly created component.

## TailwindCSS

You will soon realize the component is a little bit boring - and it is time to add some style on it to make it a little bit better. So the next step is to configure TailwindCSS.

```shell
yarn add tailwindcss
yarn tailwindcss init -p
```

The above two commands will install tailwind and generate the default configurations for you.

You will see two newly added configuration files - `postcss.config.js` and `tailwind.config.js`.
Following the official guidance of tailwind, I added two plugins under `postcss.config.js`

```js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};
```

**Note**: You must use the interoperable object-based format based on the `NextJs` [document](https://nextjs.org/docs/advanced-features/customizing-postcss-config) (you can find this note at the bottom).
Then add tailwind into `styles/globals.css`

```css
@tailwind base;

@tailwind components;

@tailwind utilities;
```

### Add style to the component

```tsx
// ./src/components/text-input.tsx
import React from "react";

export const TextInputComponent: React.FunctionComponent = () => {
  return (
    <div>
      <input
        placeholder="type"
        className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
      ></input>
    </div>
  );
};
```

And use it in your main page

```tsx
// ./src/pages/index.tsx
import { Fragment } from "react";
import { TextInputComponent } from "../components/text-input";

export default function Home() {
  return (
    <Fragment>
      <p>Hello</p>
      <TextInputComponent />
    </Fragment>
  );
}
```

Then you can start your `NextJs` server to view it on your main page.

### What about StoryBook

Then you start your StoryBook server, and found - the style is not applied. Why? My understanding is that `StoryBook` does not know we are using `tailwind` at all - while `NextJs` knows it because it is imported in the `_app.tsx` file. So our next step is to let `StoryBook` be aware of this as well. To achieve this, we should update the `.storybook/preview.js` file

```js
import "../styles/globals.css";
export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
};
```

Then you start your `StoryBook` server again, still, you find there is no style (or even worse, you may see error message - I can not recall exactly you will see now.) Basically the reason is `StoryBook`'s `webpack` component is not using `postcss-loader` - while `NextJs` has builtin support for it. To change it, we should update the `.storybook/main.js` file

```js
module.exports = {
  stories: ["../stories/**/*.stories.mdx", "../stories/**/*.stories.tsx"],
  addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
  webpackFinal: (config) => {
    return {
      ...config,
      module: {
        ...config.module,
        rules: [
          // Filter out the default css loader
          ...config.module.rules.filter((rule) => /\.css$/ !== rule.test),
          {
            test: /\.css/,
            use: [
              {
                loader: "postcss-loader",
                options: {
                  plugins: [require("tailwindcss"), require("autoprefixer")],
                  postcssOptions: {
                    ident: "postcss",
                    sourceMap: true,
                  },
                },
              },
            ],
          },
        ],
      },
    };
  },
};
```

**Note1** We are filtering out one built in `css` rule here, as the builtin one is not applicable for `postcss`.
**Note2** When speicfy `plugins`, you can not simply put a string here, it has to be required object.

Once the configurations are placed, you can start your storybook server again and see your styled component.

## Cypress and Cypress-React-Unit-Test

To test the newly added component, we can use `cypress`.

```shell
yarn add --dev cypress cypress-react-unit-test
```

Although in the document of `cypress-react-unit-test` it says we can initialize `cypress` with `cypress-react-unit-test init` command, but I failed to achive it. So I will do it manually. To start, start `cypress` server once.

```shell
yarn cypress open
```

The above command will initialize your basic cypress configurations - it creates one `cypress.json` file and one `cypress` directory.

Then we can follow the document in `cypress-react-unit-test` to setup it

```json
// cypress.json
{
  "experimentalComponentTesting": true
}
```

```js
// cypress/support/index.js
import "./commands";
require("cypress-react-unit-test/support");
```

```json
// cypress/tsconfig.json
{
  "extends": "../tsconfig.json",
  "include": ["./**/*.ts*"],
  "compilerOptions": {
    "baseUrl": ".",
    "jsx": "react",
    "types": ["cypress"]
  }
}
```

```json
// ./tsconfig.json
{
  "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": "preserve",
    "baseUrl": "src" // <- Add this line
  },
  "include": ["next-env.d.ts", "src"],
  "exclude": ["node_modules", ".storybook"]
}
```

```ts
// ./cypress/global.d.ts
/// <reference types="cypress" />
```

```js
// ./cypress/plugins/index.js
/// <reference types="cypress" />

module.exports = (on, config) => {
  return config;
};
```

**Note** We need to add the `baseUrl` line to satisfy cypress, otherwise it will complain (sorry I can not recall what it was complaining about exactly).

After that, you can start to write a simple cypress test

```tsx
// cypress/component/text-input.spec.tsx
import React from "react";
import { mount } from "cypress-react-unit-test";
import { TextInputComponent } from "../../src/components/text-input";
import "../../styles/globals.css";
describe("HelloWorld component", () => {
  it("works", () => {
    mount(<TextInputComponent />);
  });
});
```

Then you run `yarn cypress open` and click the test you just added - however, it wont make it. You will see it is complaining that there is no loader for this file. Guess `cypress` or `cypress-react-unit-test` does not have builtin support for Typescript - so we need to add the webpack loader manually. In addition, you also want to set the loader for `css`, as cypress does not support it by default either.

```js
/// <reference types="cypress" />

const webpackPreprocessor = require("@cypress/webpack-preprocessor");
const commonOptions = require("../../webpack.config");

commonOptions.resolve = {
  extensions: [".tsx", ".ts", ".js", ".css"],
};

commonOptions.module.rules.push({
  test: /tsx$/,
  use: [
    {
      loader: "babel-loader",
      options: {
        presets: ["@babel/preset-react", "@babel/preset-typescript"],
      },
    },
  ],
});

commonOptions.module.rules.push({
  test: /css$/,
  use: [
    "style-loader",
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        plugins: [require("tailwindcss"), require("autoprefixer")],
      },
    },
  ],
});

module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  const options = {
    // send in the options from your webpack.config.js, so it works the same
    // as your app's code
    webpackOptions: commonOptions,
    watchOptions: {},
  };

  on("file:preprocessor", webpackPreprocessor(options));
  return config;
};
```

Forgive the bad naming, I am really lazy to change it atm. 

You may notice that the loader used here is different to the ones we used in `StoryBook`, because if we use the same configuration as the ones in `StoryBook`, it wont work. I am still unsure why this is happening.

OK, with all the configuration above, you can now use cypress without any issue. 

## Main Take Away
`WebPack` is really important.