2~3ページからなる単純な静的サイトを作成するつもりでNext.jsをSSGとして触ってみたときに
実装で迷ったり、エラーで行き詰まったところの覚書です。
Next.jsの導入手順と基本的な使い方
まずは必要なパッケージをインストール
$ npm install next react react-dom
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
の拡張子も使えます)
試しにに下記のコードを記述します。
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を使用する方法で、下記のように使います。
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
理由はリンク先に書いてある通りで、
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タグを使ってもエラーが表示されず正常にビルドできました。
"rules": {
"@next/next/no-img-element": "off"
}
JSONの読み込みとReactのループ
ローカルのJSONデータをもとにリストを作成したいと考えたときに、JSONの読み込みとループの実装がうまくいきませんでした。
JSONの読み込みというとよく使うのがfetch()
、require
、XMLHttpRequest()
です。
fetch()
はネットワーク越しにJSONファイルを取得するため使えず、
Node.jsでXMLHttpRequest()
を使おうとすると個別にXMLHttpRequestのパッケージをインストールする必要があります。
require
も使えますが、他と統一するためにimport
を使いました。
import json from "./data.json"
続けて読み込んだJSONデータを加工してリスト化するために、for文でループさせようとして失敗。
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
が使えました。
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;
事前に配列に格納して表示することもできます。
このときはループの方法に制限はありません。
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.
回避するには下記のように修正する必要があります。
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
全ページ共通の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が個人的にわかりやすく扱いやすいです。