Next.js に入門してみた


[Next.js](https://nextjs.org/) (現バージョン 9.4) に入門します。

## 何をするものか？

- Reactでウェブサイトを構築できます
    - サーバサイドで動きます
      - SSRできます
      - APIも生やせます
    - 静的サイトとしてもレンダリングできます
- 基本的に[create-react-app (CRA)](https://github.com/facebook/create-react-app)によく似ています
    - CRAと同じで、設定がなくて楽ちんです 
    - CRAと同じで、裏で勝手にwebpackとか使ってます
- 特定のディレクトリに決まった形式でJSファイルを置けばページとしてルーティングとサーブとレンダリングをよしなにやってくれます
    - ページに該当するJSからReactコンポーネントをexport defaultとして公開すればOKです。
        - `./pages/xxx/index.tsx` → `http://myhost/xxx/`
        - `./pages/foobar/baz.js` → `http://myhost/foobar/baz`
        - `./pages/[var].js` → `http://myhost/${var}.js` (パス変数を使う場合)
            - React側からは `useRouter().query.var` でアクセス可能。 `?xxx=yyyy` みたいなものをマージされてくる。
        - ビルド時・SSR時などに評価させるにはコンポーネントとともに下記の関数をexportすればOK
            - SSR時: `export const getServerSideProps: GetServerSideProps = async () => { ... }`
            - ビルド時: `export const getStaticProps: GetStaticProps = async () => { ... }`
                - パス引数に与えるパス変数のバリエーションを指定したい場合は `export const getStaticPaths: GetStaticPaths = async () => { ... }` でOK。ここで返却したパス変数のバリエーション分静的レンダリングされたHTMLが出来上がったりする。
    - APIは `./pages/api` 以下で公開できます。
        - export defaultで `(req, res) => { ... }` みたいな関数を出力すればOK

## ○○できるの？

[ドキュメント](https://nextjs.org/docs/)見ればだいたい書いてありますが…

- Babel: できる
- PostCSS: できる
- TypeScript: できる
- CSS in JS: できる
    - ビルトインのものがあるけど[Emotion](https://emotion.sh/docs/ssr)と組み合わせたりしたほうが便利そう
- `.env` による環境変数指定: できる
- APIサーバ: できる
- PWA: [こういうのもあるようです](https://github.com/drenther/next-pwa)
- 環境変数によるソース内文字列置換: できる
    - `NEXT_PUBLIC_` で始めればOK
- AMP: [できる](https://nextjs.org/docs/api-reference/next/amp)
- ほかのNode.jsのHTTPサーバの一部に組み込む: [できる](https://nextjs.org/docs/advanced-features/custom-server)
- エラードキュメント: できる
- プラグイン: [ある](https://github.com/vercel/next-plugins)
    - `next.config.js` (Next.jsの設定ファイル)にプラグインをrequireして記述すればOK
- デプロイ: できるに決まってる
    - Vercel: 公式のデプロイサーバ
        - 基本的に無料
            - 別に静的ホスティングじゃなくて、APIとか動かしても無料
        - じゃあ何が有料なのかというと、
            - （Pro）チームコラボ・並列ビルド・パスワード保護・プレビューデプロイ
            - （Enterprise）SLA保証・マルチリージョンサーバレス機能・エンタプライズサポートなど
    - 自前:
        - Node.js アプリとして: `next build` しておいて、 `next start` すればOK
        - 静的サイトの場合は、 `next build` しておいて、  `next export` したら、できあがった `/out` フォルダを公開すればいい

## プロジェクト作成から起動まで

```console
# プロジェクト作成
$ yarn create next-app
 → Default ... を選択

# 開発サーバ起動
$ yarn dev

# ビルド
$ yarn build

# 本番サーバ起動 (yarn buildしないと失敗する)
$ yarn start
 → http://localhost:3000
```

### TypeScript

ほぼ構成をカスタマイズする余地がありません。おまかせですね。

```
# 追加
$ yarn add -D typescript @types/react @types/node

# tsconfig.json というファイルがあれば Next.js が起動時に勝手に中身を書き込んでくれる。つまりカスタマイズするだけ無駄。
$ touch tsconfig.json

$ yarn dev
 → tsconfig.json / next-env.d.ts (型定義) が作成される

```

基本的には `@babel/preset-typescript` によるトランスパイルっぽいです。

あとは好きに `*.tsx` 置けば勝手にやってくれます。

### ディレクトリ

- `public/`
    - CRAと同じ。ここに公開できる静的ファイルを置く
- `pages/`
    - ページ置き場ここ。 `src/pages` より優先される。
- `src/`
    - `pages/`
        - `${projectRoot}/pages/` のかわりにおくことができる
- `.next/`
    - `next build` でできるビルドディレクトリ
- `out/`
    - `next export` でできるビルドディレクトリ

## Next.js + TypeScript + Tailwind CSS (PostCSS) + twin.macro (Babel) + emotion で適当なサイトを作ってみる

リポジトリは https://github.com/knjname/2020-05-31_tryoutNextJs にあります。

### 初期化

```console
# Next.js
$ yarn create next-app

# TypeScript
$ yarn add -D typescript @types/react @types/node
$ touch tsconfig.json

# Tailwind CSS
$ yarn add -D tailwindcss

# emotion
$ yarn add @emotion/core @emotion/styled

# twin.macro
$ yarn add -D babel-plugin-macros twin.macro
```

### Tailwind CSS / PostCSSの設定 (postcss.config.js)

[requireは使ってはいけない](https://nextjs.org/docs/advanced-features/customizing-postcss-config)そうです。 [CSS変換結果のキャッシュの邪魔になるから](https://github.com/vercel/next.js/issues/10117)だそうです。

```js:postcss.config.js
module.exports = {
  plugins: ["tailwindcss"],
};
```

```js:tailwind.config.js
module.exports = {
  purge: ["./pages/**/*.js", "./pages/**/*.tsx"], // 好きにふやしましょう
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
};
```

`pages/index.css` も作っておきましょう。

```css:pages/index.css
@tailwind base;

@tailwind components;

@tailwind utilities;

```

グローバルから参照するように設定します。

```tsx:pages/_app.tsx
import { AppProps } from "next/app";
import "./index.css";

export default ({ Component, pageProps }: AppProps) => {
  return <Component {...pageProps} />;
};

```

### twin.macro / Babelの設定 (.babelrc)

[twin.macro側にNext.jsと併用する時のガイドがあります](https://github.com/ben-rogerson/twin.macro/blob/master/docs/emotion/next.md)が、ベーシックなものでいきます。

```json:.babelrc
{
  "presets": ["next/babel"],
  "plugins": ["babel-plugin-macros"]
}
```

上記に加え、emotionのcssプロパティを使う場合、 `"presets"` に `"@emotion/babel-preset-css-prop"` を追加すればいいようです。

### ここまでの構成の確認

サイトのフロントカバーを作成してみます。

```tsx:pages/index.tsx
import styled from "@emotion/styled";
import tw from "twin.macro";
export default () => (
  <div>
    <section
      style={{
        backgroundImage:
          'url("https://images.unsplash.com/photo-1590690726331-cd7699f1a415?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max")',
      }}
      className="w-screen h-screen bg-no-repeat bg-cover flex justify-center items-center"
    >
      <Component>
        <span className="text-pink-400">M</span>y biograph
        <span className="text-green-400">y</span>
      </Component>
    </section>
  </div>
);

const Component = styled.h1`
  ${tw`text-gray-100 uppercase font-semibold text-6xl`}
`;
```

実行(`yarn dev`)すると、無事（趣味の悪い）フロントカバーが出てきます。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9619/96e2e9d7-b9e9-3656-b8c0-e562eba0ce9e.png" >

このサイトを静的HTMLとしてエクスポートしてみます。

```console
$ yarn build && yarn next export

$ npx http-server out
// => http://127.0.0.1:8080/
```

以下の点を確認してみましょう。

- リンクされているCSSがきちんと必要なクラスだけ含んでいるかなど確認できます。(TailwindCSS/PurgeCSS)
- また、[DevToolsなどでJavaScriptを無効にしても](https://developers.google.com/web/tools/chrome-devtools/javascript/disable)きちんと表示されることを確認することができます。
    - JavaScriptが有効であれば、この後 SSRされた内容とJavaScript側のReactコンポーネントのhydrate処理(同期処理)が行われるはずです。
    - ページの内容に `{new Date().toString()}` などを入れると分かりやすいと思います。

### サイトを発展させる

何も無いのは寂しいので、全ページの一番上にグローバルナビを追加してみましょう。

```tsx:src/_document.tsx
import Document, { Html, Head, Main, NextScript } from "next/document";
import Link from "next/link";

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    return { ...initialProps };
  }

  render() {
    const ListClassName = "text-white px-2";

    return (
      <Html>
        <Head />
        <body>
          <ul className="block flex px-4 py-2 bg-gray-800 sticky top-0 left-0">
            <li className={ListClassName}>
              <Link href="/">
                <a>Top</a>
              </Link>
            </li>
            <li className={ListClassName}>
              <Link href="/profile">Profile</Link>
            </li>
          </ul>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;
```

(2020/06/02 追記) 上記のグローバルメニューですが、 `_app.tsx` の方に追記したほうがいいと思われます…

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9619/18e4f0ba-c54d-ce05-6d21-aff0b8841b71.png" width="400" />


プロファイルのページも足しましょう。

```tsx:src/profile.tsx
export default () => <h2 className="text-6xl">好きな食べ物はうどん！</h2>;
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9619/7c167ff3-b50e-a19d-30d7-c92ee6dc8b22.png" width="400" />

## まとめ

SSRしたり色々するのに非常に楽なプラットフォームだと思いました。

なんとなくReactアプリをCRAではじめるのもアリですが、Next.jsのほうがハマるパターンも結構あると思います。