こちらをご覧ください
最新の Next.js での環境構築について記事を新しく書いたので、こちらをご覧ください。
はじめに
以前React 開発時にやることという記事を書いたのですが、
今回 Next.js を使ったので、同じように最初にやるべきことを記録しておきます。
以下に書いていることを実行するだけで、 Next.js を簡単にいい感じに始められますが、
一度は公式ドキュメントに目を通してみることをお勧めします。
英語ですが結構読みやすく、Next.jsの概念を掴むことができました。
この記事を実行すると出来上がるもの
元となるコードを生成
yarn create next-app app
typescript等ダウンロード & tsconfig.json の追加
cd app
yarn add -D typescript @types/react @types/node
touch tsconfig.json
tsconfig.json等の自動更新
yarn dev
# tsconfigが自動で設定されているのを確認したら、コントロール + C で一旦停止
tsconfig.json の修正 (strict
がデフォルトでfalse
になっている)
{
"compilerOptions": {
...
"strict": true,
...
}
}
Lint & Formatter の追加
yarn add -D eslint prettier eslint-config-prettier eslint-config-airbnb-typescript
eslintの初期化
./node_modules/.bin/eslint --init
## 以下選択肢 ##
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · Yes
✔ Where does your code run? · browser
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · airbnb
✔ What format do you want your config file to be in? · JavaScript
✔ Would you like to install them now with npm? · Yes
###########
# yarnを使うので、不要ファイル削除
yarn install
rm -rf package-lock.json
prettierとeslintの統合
eslintと統合するため、.eslintrc.js
を修正
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
"airbnb-typescript", // https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md#eslint-configs
// "eslint:recommended",
// "plugin:@typescript-eslint/eslint-recommended",
// "plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"prettier",
"prettier/react",
"prettier/@typescript-eslint",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: "module",
project: "./tsconfig.json",
},
plugins: ["react-hooks", "react", "@typescript-eslint"],
rules: {
"react/destructuring-assignment": ["off"], // props.*の形で使えるように
"arrow-body-style": 0
},
};
pretteir用の設定ファイル追加
echo '{\n\t"printWidth": 120\n}' > .prettierrc
Styled-components、Material-UIの導入
インストール
yarn add @material-ui/core @material-ui/icons styled-components
yarn add -D @types/styled-components babel-plugin-styled-components
不要ファイルを削除
rm -rf pages styles/*.css
ファイル作成
mkdir -p ./src/pages ./src/components && touch .babelrc ./styles/theme.ts ./src/pages/_document.tsx ./src/pages/_app.tsx ./src/pages/index.tsx ./src/components/ButtonWithBackgroundColor.tsx
公式の例などを参考にファイル作成する
公式例(Material-UI)
公式例(styled-components)
Material-UIとstyled componentsで,next.jsのcssをいい感じに管理する (Jest/TypeScript対応版)
以下の点がポイントでしょうか
-
<StylesProvider injectFirst>
を忘れない -
.babelrc
を作成し、babel-plugin-styled-components
を有効化する- こうしないとスタイルを変更した際に
Warning: Prop 'className' did not match. Server: "***" Client: "***"
といったエラーが出ます。
- こうしないとスタイルを変更した際に
-
next styled-components
等で検索すると"plugins": [["styled-components", { "ssr": true, "displayName": true, "preprocess": false }
といった設定がよく出てきます-
displayName
はデバッグ用なので、production
ではfalse
にした方が良さそうです。 -
preprocess
は公式ドキュメントでは見つかりませんでしたので、省いています。-
[babel-plugin] preprocess breaks pseudo selectorsに
Preprocess is obsolete and will be removed soon
と書いているので、削除されたのでしょうか?
-
[babel-plugin] preprocess breaks pseudo selectorsに
-
-
_document.tsx
でのstyles
の順番をMaterial-UI, styled-componentsの順にする- 逆にすると、syled-componentsで上書きしたMaterial-UIのコンポーネントが一瞬表示されます
- 下の例で言うと、
みどり
ボタンやきいろ
ボタンが、ページ表示直後のみDefault色が表示され、色が切り替わるという流れになります。
import { createMuiTheme } from "@material-ui/core/styles";
import { red } from "@material-ui/core/colors";
// Create a theme instance.
const theme = createMuiTheme({
palette: {
primary: {
main: "#226cd6",
},
secondary: {
main: "#19857b",
},
error: {
main: red.A400,
},
background: {
default: "#fff",
},
},
});
export default theme;
/* eslint-disable react/jsx-props-no-spreading */
import { ServerStyleSheets } from "@material-ui/core/styles";
import Document, { Head, Html, Main, NextScript } from "next/document";
import React from "react";
import { ServerStyleSheet } from "styled-components";
import theme from "../../styles/theme";
export default class MyDocument extends Document {
render() {
return (
<Html lang="ja-JP">
<Head>
{/* PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
// Render app and page and get the context of the page with collected side effects.
const MuiSheets = new ServerStyleSheets();
const styledComponentsSheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => styledComponentsSheet.collectStyles(MuiSheets.collect(<App {...props} />)),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [
...React.Children.toArray(initialProps.styles),
MuiSheets.getStyleElement(),
styledComponentsSheet.getStyleElement(),
],
};
} finally {
styledComponentsSheet.seal();
}
};
/* eslint-disable react/jsx-props-no-spreading */
import CssBaseline from "@material-ui/core/CssBaseline";
import { MuiThemeProvider, StylesProvider } from "@material-ui/core/styles";
import { AppProps } from "next/app";
import React from "react";
import { ThemeProvider } from "styled-components";
import theme from "../../styles/theme";
function MyApp({ Component, pageProps }: AppProps) {
React.useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles && jssStyles.parentElement) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<StylesProvider injectFirst>
<MuiThemeProvider theme={theme}>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</MuiThemeProvider>
</StylesProvider>
);
}
export default MyApp;
import { Button } from "@material-ui/core";
import React from "react";
import styled from "styled-components";
const StyledButton = styled(Button)<{ background_color: string }>`
background-color: ${(props) => props.background_color};
`;
export interface Props {
backgroundColor: string;
children: React.ReactNode;
}
const ButtonWithBackgroundColor: React.FC<Props> = (props: Props) => {
return (
<StyledButton variant="contained" background_color={props.backgroundColor}>
{props.children}
</StyledButton>
);
};
export default ButtonWithBackgroundColor;
import { Box, Button } from "@material-ui/core";
import React from "react";
import styled from "styled-components";
import ButtonWithBackgroundColor from "../components/ButtonWithBackgroundColor";
const RedText = styled.span`
font-size: 50px;
color: red;
`;
const StyledButton = styled(Button)`
background-color: green;
`;
const Home: React.FC = () => {
return (
<Box>
<Button color="primary" variant="contained">
プライマリー
</Button>
<StyledButton variant="contained">みどり</StyledButton>
<ButtonWithBackgroundColor backgroundColor="yellow">きいろ</ButtonWithBackgroundColor>
<RedText>あか</RedText>
</Box>
);
};
export default Home;
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true, "displayName": true }]]
}
Storybookの導入
まずは、storybookの初期化をします。
自分で色々ダウンロードする方法もありますが、出来るだけ公式に乗っかっておきます。
npx sb init
preview.js
の編集
themeを作っており、Material-UI, styled-componentsを使用しているので、それらを反映するために、decoratorを作成する必要があります。
参考:https://storybook.js.org/docs/react/essentials/toolbars-and-globals#create-a-decorator
import CssBaseline from "@material-ui/core/CssBaseline";
import { MuiThemeProvider, StylesProvider } from "@material-ui/core/styles";
import React from "react";
import { ThemeProvider } from "styled-components";
import theme from "../styles/theme";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
};
const withThemeProvider = (Story, context) => {
return (
<StylesProvider injectFirst>
<MuiThemeProvider theme={theme}>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Story {...context} />
</ThemeProvider>
</MuiThemeProvider>
</StylesProvider>
);
};
export const decorators = [withThemeProvider];
storiesの追加
import { Meta, Story } from "@storybook/react";
import React from "react";
import ButtonWithBackgroundColor, { Props } from "../../components/ButtonWithBackgroundColor";
export default {
title: "Parts/Button",
component: ButtonWithBackgroundColor,
argTypes: {
backgroundColor: { control: "color" },
},
} as Meta;
const Template: Story<Props> = (args) => <ButtonWithBackgroundColor {...args} />;
export const Yellow = Template.bind({});
Yellow.args = {
backgroundColor: "yellow",
children: "きいろ",
};
export const Green = Template.bind({});
Green.args = {
backgroundColor: "green",
children: "みどり",
};
export const Default = Template.bind({});
Default.args = {
children: "デフォルト",
};
import { Story } from "@storybook/react";
import React from "react";
import Home from "../../pages/index";
export default {
title: "Pages/Home",
component: Home,
};
const Template: Story = () => <Home />;
export const Primary = Template.bind({});
確認
yarn storybook
デプロイ
公式ドキュメントの通りに、Vercelにデプロイします。
最初はfirebase Hostingを使おうかと思っていたのですが、色々調べた結果、純正使っておくのがベターそうだなとなりました。
公式ドキュメント:https://nextjs.org/learn/basics/deploying-nextjs-app
簡単な作業を数回するだけでデプロイすることができました👏
プルリクを作成すればプレビューサイトも作成してくれるみたいですね、感動しました。
おわりに
以上で、Next.js + Material-UI + styled-components + TypeScript + Storybook + ESLint + prettier + Vercel の環境を作成することができました👏
型ありプレビューありでデプロイも簡単にできるので、爆速で開発していけそうですね💪
参考
Next.jsプロジェクトでMaterial UIを使う方法
Next.jsでclassNameが見つからなくなるバグの対処方
Server-side rendered styled-components with Nextjs
[babel-plugin] preprocess breaks pseudo selectors
Next.js 4年目の知見:SSRはもう古い、VercelにAPIサーバを置くな
Next.js周りのボイラープレート(Typescript + Jest + Storybook + ESLint + Prettier + styled-components)
Next.js + TypeScriptのプロジェクトにStorybookを導入する
Next.js+TypeScript 環境で Storybook を使う