[Sep 2020] Building a front-end environment based on Next.js

Next.js
Here are the steps for building a front-end environment based on Next.js. We have chosen popular libraries as of September 2020 with the goal of being able to prototype efficiently.
Update

Here are the steps for building a front-end environment based on Next.js.
We have chosen popular libraries as of September 2020 with the goal of being able to prototype efficiently.
We will use the following as our primary libraries

1. Introduce Next.js

Please execute the following commands on your terminal:

# create project folder
mkdir PROJECT_NAME_DIR; cd PROJECT_NAME_DIR

# setup Next.js
npm init next-app .

# move pages to src/pages
mkdir src ; mv pages/ src/pages

# create configuration file for Next.js
touch next.config.js

1-1. Configure next.config.js

Configure next.config.js as follows:

/* eslint-disable
    @typescript-eslint/no-var-requires
*/

const { resolve } = require('path');

const nextConfig = {
  webpack: (config) => {
    // Set the src directory to the root of the alias
    config.resolve.alias['~'] = resolve(__dirname, 'src');
    return config;
  },
};

module.exports = nextConfig;

2. Support TypeScript

Please execute the following commands on your terminal to support TypeScript:

# install TypeScript libs
yarn add --dev typescript @types/react @types/react-dom @types/node

# create a configuration file for TypeScript
touch tsconfig.json

# automatically create some nessesary files with Next.js dev mode
yarn dev
# once it's generated, it's finished.

# convert js and jsx files to ts and tsx under the src directory
find src -name "*.js" | sed 'p;s/.js$/.tsx/' | xargs -n2 mv

2-1. Modify TypeScript configuration

Please modify tsconfig.json as follows:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "~/*": ["./src/*"]
    }
  }
}

2-2. Support TypeScript in src dir

Please modify src/pages/index.tsx as follows:

import React from 'react';

export default function Home(): ReactElement {
  return (
    <>
      <h1>My page</h1>
    </>
  );
}

3. Override App and Document components in Next.js

Please execute the following commands on your terminal:

# Create _app.tsx and _document.tsx
touch src/pages/_app.tsx && touch src/pages/_document.tsx

# Install sanitize.css
yarn add sanitize.css

3-1. Override App component

Please modify src/pages/_app.tsx as follows:

import * as React from 'react';
import App, { AppProps } from 'next/app';
import 'sanitize.css';

class MyApp extends App {
  render(): JSX.Element {
    const { Component, pageProps }: AppProps = this.props;

    return (
      <React.Fragment>
        <Component {...pageProps} />
      </React.Fragment>
    );
  }
}

export default MyApp;

3-2. Override Document component

Please modify src/pages/_document.tsx as follows:

import Document, { Html, Head, Main, NextScript } from 'next/document';
import {
  DocumentContext,
  DocumentInitialProps,
} from 'next/dist/next-server/lib/utils';

export default class MyDocument extends Document {
  render(): JSX.Element {
    return (
      <Html lang="ja">
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

4. Support PWA

Please execute the following commands on your terminal:

# Install PWA module
yarn add next-pwa

4-1. Modify next.js configuration

Please modify next.config.js as follows:

/* eslint-disable
    @typescript-eslint/no-var-requires
*/

const { resolve } = require('path');
const withPWA = require('next-pwa');
const runtimeCaching = require('next-pwa/cache');

const nextConfig = {
  pwa: {
    dest: 'public',
    runtimeCaching,
  },
  webpack: (config) => {
    // set the src directory to the root of the alias
    config.resolve.alias['~'] = resolve(__dirname, 'src');
    return config;
  },
};

module.exports = withPWA(nextConfig);

4-2. Create manifest.json

Please create public/manifest.json by https://app-manifest.firebaseapp.com/.

Please execute the following commands on your terminal:

mkdir public && mkdir public/images

4-3. Create favicon

Create public/favicon.ico by https://favicon.io/.

5. Introduce Styled Component

Please execute the following commands on your terminal:

yarn add styled-components @babel/plugin-proposal-optional-chaining

touch babel.config.js

5-1. Modify Babel configuration

Please modify babel.config.js as follows:

// eslint-disable-next-line no-undef
module.exports = function (api) {
  api.cache(true);

  const presets = ['next/babel'];
  const plugins = [
    // eslint-disable-next-line no-undef
    [
      'styled-components',
      { displayName: process.env.NODE_ENV !== 'production', ssr: true },
    ],
    '@babel/plugin-proposal-optional-chaining',
  ];

  return {
    plugins,
    presets,
  };
};

5-2. Modify override of app component

Modify src/pages/_app.tsx as follows:

import * as React from 'react';
import App, { AppProps } from 'next/app';
import 'sanitize.css';
import { ThemeProvider } from 'styled-components';

const theme = {};

class MyApp extends App {
  render(): JSX.Element {
    const { Component, pageProps }: AppProps = this.props;

    return (
      <ThemeProvider theme={theme}>
        <Component {...pageProps} />
      </ThemeProvider>
    );
  }
}

export default MyApp;

5-3. Add to document component override

Add to src/pages/_document.tsx as follows:

import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
import {
  DocumentContext,
  DocumentInitialProps,
} from 'next/dist/next-server/lib/utils';

export default class MyDocument extends Document {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />),
        });

      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      };
    } finally {
      sheet.seal();
    }
  }

  render(): JSX.Element {
    return (
      <Html lang="ja">
        <Head>
          <link rel="shortcut icon" href="/favicon.png" />
          <link rel="manifest" href="/manifest.json" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

5-4. Add sample code of Styled Component

Add sample code of Styled Component to src/pages/index.tsx as follows:

import styled from 'styled-components';
import React from 'react';

export default function Home(): ReactElement {
  return (
    <>
      <Title>My page</Title>
    </>
  );
}

const Title = styled.h1`
  color: red;
  font-size: 50px;
`;

6. Recoil

Execute the following commands on your terminal:

# Install Recoil module
yarn add recoil

6-1. Adding settings to app component

Adding Recoil settings to src/pages/_app.tsx as follows:

import * as React from 'react';
import App, { AppProps } from 'next/app';
import 'sanitize.css';
import { RecoilRoot } from 'recoil';
import { ThemeProvider } from 'styled-components';

const theme = {};

class MyApp extends App {
  render(): JSX.Element {
    const { Component, pageProps }: AppProps = this.props;

    return (
      <RecoilRoot>
        <ThemeProvider theme={theme}>
          <Component {...pageProps} />
        </ThemeProvider>
      </RecoilRoot>
    );
  }
}

export default MyApp;

6-2. Add sample code of Recoil

Add sample code of Recoil to src/pages/index.tsx as follows:

import styled from 'styled-components';
import React, { ReactElement } from 'react';
import { atom, useRecoilState } from 'recoil';

const textState = atom({
  default: '', // default value (aka initial value)
  key: 'textState', // unique ID (with respect to other atoms/selectors)
});

export default function Home(): ReactElement {
  const [text, setText] = useRecoilState(textState);

  const onChange = (event: {
    target: { value: string | ((currVal: string) => string) };

  }) => {
    setText(event.target.value);
  };

  return (
    <>
      <Title>My page</Title>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </>
  );
}

const Title = styled.h1`
  color: red;
  font-size: 50px;
`;

7. Add EditorConfig / ESLint / Prettier

Execute the following commands on your terminal:

# Install Eslint and prettier module
yarn add --dev eslint eslint-plugin-react prettier eslint-config-prettier eslint-plugin-prettier

# Install Eslint with TypeScript
yarn add --dev @typescript-eslint/parser @typescript-eslint/eslint-plugin

# Create some configuration files for EditorConfig / ESLint / Prettier
touch .editorconfig && touch .eslintrc.js && touch .prettierrc.js

7-1. Modify EditorConfig

Modify .editorconfig as follows:

# editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

7-2. Modify configuration of ESlint

Modify .eslintrc.js as follows:

module.exports = {
  env: {
    browser: true,
    es6: true,
    jest: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:@typescript-eslint/eslint-recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
    'prettier/@typescript-eslint',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2020,
    sourceType: 'module',
  },
  plugins: ['react', '@typescript-eslint'],
  rules: {
    '@typescript-eslint/ban-ts-ignore': 'off',
    eqeqeq: ['error', 'always', { null: 'ignore' }],
    'no-console': 'error',
    'no-debugger': 'error',
    'react/jsx-no-bind': [
      'error',
      {
        allowArrowFunctions: true,
        allowBind: false,
        allowFunctions: false,
        ignoreDOMComponents: false,
        ignoreRefs: false,
      },
    ],
    'react/prop-types': 'off',
    'react/react-in-jsx-scope': 'off',
    'sort-keys': [
      'error',
      'asc',
      { caseSensitive: true, minKeys: 2, natural: false },
    ],
  },
  settings: {
    react: {
      version: 'detect',
    },
  },
};

7-3. Modify configuration of Prettier

Modify .prettierrc.js as follows:

module.exports = {
  arrowParens: 'always',
  trailingComma: 'all',
};

7-3. Modify npm-scripts of package.json

Modify npm-scripts of package.json as follows:

{
  "scripts": {
    "eslint": "eslint src --ext .ts,.tsx,.js,.jsx --ignore-path .gitignore .",
    "eslint:fix": "yarn eslint --fix",
    "prettier": "prettier --write \"{,src//,public//}*.{js,jsx,ts,tsx,json,yaml,md}\" --ignore-path .gitignore"
  }
}

8. Add Jest

Execute the following commands on your terminal:

# install jest and related modules
yarn add --dev jest jest-dom jest-css-modules

# install typescript support of jest
yarn add --dev ts-jest @types/jest

# insall React Testing Library
yarn add --dev @testing-library/react

# create configuration files of Jest
touch jest.config.js && mkdir src/__tests__

# create a sample test file
touch src/__tests__/Sample.test.tsx

8-1. Configure Jest

Modify jest.config.js as follows:

module.exports = {
  globals: {
    // configuration to change some TypeScript settings at test time
    'ts-jest': {
      tsConfig: {
        jsx: 'react',
      },
    },
  },
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
  moduleNameMapper: {
    // prevent css files from being read during test
    '\\.css$': '<rootDir>/node_modules/jest-css-modules',
    // set the src directory to the root of the alias
    '^~/(.+)': '<rootDir>/src/$1',
  },
  preset: 'ts-jest',
  roots: ['<rootDir>/src'],
};

8-2. create sample test

Modify src/__tests__/Sample.tset.tsx as follows:

import * as React from 'react';
import { render, cleanup } from '@testing-library/react';
import Index from '~/pages/index';
import { RecoilRoot } from 'recoil';

afterEach(cleanup);

describe('Index page', (): void => {
  it('link to Next.js site', (): void => {
    const { getByText } = render(
      <RecoilRoot>
        <Index />
      </RecoilRoot>
    );
    expect(getByText('My page').getAttribute('class')).toBeDefined();
  });
});

8-3. Modify npm-scripts of package.json

Modify npm-scripts of package.json as follows:

{
  "scripts": {
    "test": "jest src/__tests__/.*/*.test.tsx?$"
  }
}

9. Add Husky

Execute the following commands on your terminal:

yarn add --dev husky

9-1. Modify npm-scripts of package.json

Modify npm-scripts of package.json as follows:

{
  "scripts": {
    "pre-commit": "yarn prettier --fix && yarn eslint:fix && yarn build"
  },
  "husky": {
    "hooks": {
      "pre-commit": "yarn pre-commit",
      "pre-push": "yarn test"
    }
  }
}

10. Add react-aria and react-stately

It is recommended to use react-aria and react-stately for the actual implementation of the UI. To help you separate the UI from the logic and to not have to worry about the behavioral differences between devices, we've provided a couple of mechanisms to help you use react-aria and react-stately.

Conclution

Happy Hacking!!

この記事が気に入ったら応援お願いします🙏
Price Rank Dev
Developer
Price Rank Dev
icon help
I use Next.js (React) and Firebase (Firestore / Auth) for development. We are also developing APIs for Ruby on Rails and GraphQL. Our team members are 6 Vietnamese and Japanese engineers.
ブログのURLPrice Rank DevのTwitterアカウント実績として紹介したいページのURL ①実績として紹介したいページのURL ②