Next.jsを触ってみて躓いたところ

2~3ページからなる単純な静的サイトを作成するつもりでNext.jsをSSGとして触ってみたときに
実装で迷ったり、エラーで行き詰まったところの覚書です。
## Next.jsの導入手順と基本的な使い方
まずは必要なパッケージをインストール
```
$ npm install next react react-dom
```
package.jsonにコマンドを追加します。
```json:package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "export": "next export"
  }
}
```
Next.jsは`pages`ディレクトリ配下のファイルやディレクトリ構造に沿ってルーティングしてくれるので
`pages/index.js`を作成します。（その他にも`.jsx`や、Typescriptにも対応しているため`.ts` `.tsx`の拡張子も使えます）

試しにに下記のコードを記述します。
```js:pages/index.js
const TopPage = () => {
  return <h1>Top page</h1>;
};

export default TopPage;
```
コマンドラインで`npm run dev`を実行し、
`http://localhost:3000/`（デフォルトで設定されているポートです。`next dev -p 3001`などで変更できます）にアクセスすると「Top page」という文字が表示されます。

# 躓いたポイント
上記のルールをふまえて、普段通りにコーディングしてみた中で躓いたポイントをいくつか紹介します。

## imgタグが使えない
正確に言うと使えるのですが、画像の実装方法がNext.jsが推奨する方法と違うためエラーのような扱いになります。
エラーを回避するには後述する`.eslintrc`ファイルに設定を加える必要があります。

Next.jsが推奨する方法というのは[next/image](https://nextjs.org/docs/api-reference/next/image)を使用する方法で、下記のように使います。
```js:pages/index.js
import Image from "next/image";

const TopPage = () => {
  return (
    <>
      <h1>Top page</h1>
      <Image 
        src="/hoge.jpg" 
        alt="hoge" 
        width={100} 
        height={100}
      />
    </>
  )
};

export default TopPage;
```
srcの参照先は`public`ディレクトリなので、publicディレクトリを作成し`hoge.jpg`を格納します。
そして`npm run dev`で見てみると100x100のhoge.jpgが表示されることを確認できます。

next/imageを使う利点は下記の機能が自動的に組み込まれるところです。
- 表示環境にあわせた画像の最適化
- 遅延読み込み
- レスポンジブ対応

しかし、開発環境で正常に動くのを確認できから静的サイトとしてパブリックビルド（`npm run export`）してみるとエラーが表示され正しくビルドされません。
[next export with Image API](https://nextjs.org/docs/messages/export-image-api)

理由はリンク先に書いてある通りで、
> This is because Next.js optimizes images on-demand, as users request them (not at build time).

*画像の最適化はビルド時ではなく、オンデマンドでユーザーがリクエストしたときに適用されるから。*
next/imageは静的サイトではなく、Vercelなどでデプロイする場合に使えるようです。（ちなみにNetlifyで試してみましたが失敗しました）
ならばと使い慣れたimgタグに置きかえたところ、今度は **「next/imageを使うように」** とエラーが表示されビルドが正常に完了しません。
### 解決策
`.eslintrc`に下記を追加すると、imgタグを使ってもエラーが表示されず正常にビルドできました。
```conf:.eslintrc
"rules": {
  "@next/next/no-img-element": "off"
}
```

## JSONの読み込みとReactのループ
ローカルのJSONデータをもとにリストを作成したいと考えたときに、JSONの読み込みとループの実装がうまくいきませんでした。
JSONの読み込みというとよく使うのが`fetch()`、`require`、`XMLHttpRequest()`です。
`fetch()`はネットワーク越しにJSONファイルを取得するため使えず、
Node.jsで`XMLHttpRequest()`を使おうとすると個別にXMLHttpRequestのパッケージをインストールする必要があります。
`require`も使えますが、他と統一するために`import`を使いました。
```js
import json from "./data.json"
```

続けて読み込んだJSONデータを加工してリスト化するために、for文でループさせようとして失敗。
```js:pages/index.js
import json from "./data.json"

const TopPage = () => {
  return (
    <>
      <h1>Top page</h1>
      <ul>
        {for(let i in json) {
          <li><p>{json[i]}</p></li>
        }}
      </ul>
    </>
  )
};

export default TopPage;
```
上記を実行すると下記のようなエラーが返ってきます。
```
Error: 
  x Unexpected token `for`. Expected this, import, async, function, [ for array literal, { for object literal, @ for decorator, function, class, null, true, false, number, bigint, string, regexp, `
  | for template literal, (, or an identifier
```
`map`が使えました。
```js:pages/index.js
import json from "./data.json"

const TopPage = () => {
  return (
    <>
      <h1>Top page</h1>
      <ul>
        {json.map((item, index) => {
          <li><p>{item}</p></li>
        })}
      </ul>
    </>
  )
};

export default TopPage;
```
事前に配列に格納して表示することもできます。
このときはループの方法に制限はありません。
```js:pages/index.js
import json from "./data.json"

const TopPage = () => {
  let arr = [];

  for(let i in json) {
    arr.push(<li><p>{json[i]}</p></li>)
  }
  return (
    <>
      <h1>Top page</h1>
      <ul>
        {arr}
      <ul>
    </>
  )
};

export default TopPage;
```
また、この時ループさせる要素にkeyを渡していないとコンソールに下記の忠告が表示されます。
```
Warning: Each child in a list should have a unique "key" prop.
```
回避するには下記のように修正する必要があります。
```diff_javascript
import json from "./data.json"

const TopPage = () => {
  return (
    <>
      <h1>Top page</h1>
      {json.map((item, index) => {
-        <li><p>{item}</p></li>
+        <li　key={item}><p>{item}</p></li>
      })}
    </>
  )
};

export default TopPage;
```

## 共通Javascriptを読み込ませる
Next.jsでは、`pages/_app.js`が共通の親コンポーネントのような動きをします。
[Custom App](https://nextjs.org/docs/advanced-features/custom-app)
全ページ共通のJSを下記のように`pages/_app.js`に記述し読み込ませました。
```js:pages/_app.js
import { useEffect } from 'react';
import { bundle } from '../js/bundle'; //共通JS


const MyApp = ({ Component, pageProps, router }) => {
  useEffect(() => {
    bundle;
  },[router.pathname])

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

export default MyApp;
```

## ほかにも躓いたポイントはいっぱいある・・・
読み込んでいるパッケージが原因で発生する`ReferenceError: window is not defined`や`ReferenceError: document is not defined`のエラーが解消できなかったり、
propsが正しく渡せなかったり…試行錯誤しながらいじっている最中です。
単純にSSGとして使うなら11tyが個人的にわかりやすく扱いやすいです。