DEV Community 👩‍💻👨‍💻

cvifli
cvifli

Posted on

How do I setup my NextJs development environment

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

cypress                     // For component testing
cypress-react-unit-test
storybook                   // Quickly view react components
tailwindcss                 // CSS framework
typescript                  // Provide type safety
Enter fullscreen mode Exit fullscreen mode

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.

yarn global add create-next-app
create-next-app
Enter fullscreen mode Exit fullscreen mode

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,

yarn add --dev @types/react typescript @types/node
Enter fullscreen mode Exit fullscreen mode

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:

import React from "react";

export default function Home() {
  return <p>hello</p>;
}
Enter fullscreen mode Exit fullscreen mode

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

yarn global add storybook
yarn sb init
Enter fullscreen mode Exit fullscreen mode

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

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

Try StoryBook

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

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

export const TextInputComponent: React.FunctionComponent = () => {
  return (
    <div>
      <input placeholder="type"></input>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode
// ./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 />;
Enter fullscreen mode Exit fullscreen mode

Once the above files are added, run

yarn run storybook
Enter fullscreen mode Exit fullscreen mode

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.

yarn add tailwindcss
yarn tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

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

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};
Enter fullscreen mode Exit fullscreen mode

Note: You must use the interoperable object-based format based on the NextJs document (you can find this note at the bottom).
Then add tailwind into styles/globals.css

@tailwind base;

@tailwind components;

@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Add style to the component

// ./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>
  );
};
Enter fullscreen mode Exit fullscreen mode

And use it in your main page

// ./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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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.

yarn add --dev cypress cypress-react-unit-test
Enter fullscreen mode Exit fullscreen mode

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.

yarn cypress open
Enter fullscreen mode Exit fullscreen mode

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

// cypress.json
{
  "experimentalComponentTesting": true
}
Enter fullscreen mode Exit fullscreen mode
// cypress/support/index.js
import "./commands";
require("cypress-react-unit-test/support");
Enter fullscreen mode Exit fullscreen mode
// cypress/tsconfig.json
{
  "extends": "../tsconfig.json",
  "include": ["./**/*.ts*"],
  "compilerOptions": {
    "baseUrl": ".",
    "jsx": "react",
    "types": ["cypress"]
  }
}
Enter fullscreen mode Exit fullscreen mode
// ./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"]
}
Enter fullscreen mode Exit fullscreen mode
// ./cypress/global.d.ts
/// <reference types="cypress" />
Enter fullscreen mode Exit fullscreen mode
// ./cypress/plugins/index.js
/// <reference types="cypress" />

module.exports = (on, config) => {
  return config;
};
Enter fullscreen mode Exit fullscreen mode

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

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

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.

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

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.

Top comments (0)

12 Rarely Used Javascript APIs You Need

Practical examples of some unique Javascript APIs that beautifully demonstrate a practical use-case.