0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-05-10

2~3ページからなる単純な静的サイトを作成するつもりでNext.jsをSSGとして触ってみたときに
実装で迷ったり、エラーで行き詰まったところの覚書です。

Next.jsの導入手順と基本的な使い方

まずは必要なパッケージをインストール

$ npm install next react react-dom

package.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の拡張子も使えます)

試しにに下記のコードを記述します。

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を使用する方法で、下記のように使います。

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

理由はリンク先に書いてある通りで、

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タグを使ってもエラーが表示されず正常にビルドできました。

.eslintrc
"rules": {
  "@next/next/no-img-element": "off"
}

JSONの読み込みとReactのループ

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

import json from "./data.json"

続けて読み込んだJSONデータを加工してリスト化するために、for文でループさせようとして失敗。

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が使えました。

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;

事前に配列に格納して表示することもできます。
このときはループの方法に制限はありません。

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.

回避するには下記のように修正する必要があります。

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に記述し読み込ませました。

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 definedReferenceError: document is not definedのエラーが解消できなかったり、
propsが正しく渡せなかったり…試行錯誤しながらいじっている最中です。
単純にSSGとして使うなら11tyが個人的にわかりやすく扱いやすいです。

0
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?