- 1. Introduce Next.js 1. Introduce Next.js
- 2. Support TypeScript 2. Support TypeScript
- 3. Override App and Document components in Next.js 3. Override App and Document components in Next.js
- 4. Support PWA 4. Support PWA
- 5. Introduce Styled Component 5. Introduce Styled Component
- 5-3. Add to document component override 5-3. Add to document component override
- 6. Recoil 6. Recoil
- 7. Add EditorConfig / ESLint / Prettier 7. Add EditorConfig / ESLint / Prettier
- 8. Add Jest 8. Add Jest
- 9. Add Husky 9. Add Husky
- 10. Add react-aria and react-stately 10. Add react-aria and react-stately
- Conclution Conclution
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
- Framework: Next.js https://nextjs.org/docs/getting-started
- Static typing: TypeScript https://github.com/microsoft/TypeScript
- PWA: next-pwa https://github.com/shadowwalker/next-pwa
- Implementing logic with a generic UI: react-aria + react-steadly https://github.com/adobe/react-spectrum
- Styling: styled-components https://styled-components.com/
- State management: Recoil https://recoiljs.org/
- Rules & Formatting: EditorConfig + ESLint https://eslint.org/ + Prettier https://prettier.io/
- Test: Jest https://jestjs.io/ + React Testing Library https://github.com/testing-library/react-testing-library
- Git Hook: Husky https://github.com/typicode/husky
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!!