結論
いい設定方法が見つからなかったため、結局ページのファイルに直接埋め込むことにしました。
まずはサブディレクトリの設定をします。
next.config.js
を以下のようにしてください。
const isProd = process.env.NODE_ENV === 'production';
const basePath = isProd ? 'ここにパスを入れる' : ''; // 例: /sub-directory
/** @type {import('next').NextConfig} */
const nextConfig = {
publicRuntimeConfig: {
basePath, // basePathを他のファイルから読み込めるようにする
}
}
module.exports = nextConfig
このコードでは、
-
isProd
変数に本番環境かどうかが格納される -
basePath
変数にサブディレクトリのパスが格納される -
publicRuntimeConfig
によってbasePath
がコンポーネント側から参照できるようになる
次に、public内にあるファイルを読み込みたいコード(ここではpage.tsx
)の最初に、
以下の行を記述します。
// なんかいろいろなimport文
// 画像ファイルのパス変換用
import getConfig from "next/config";
const { basePath } = getConfig().publicRuntimeConfig;
これを書くことによって、basePath
変数にnext.config.js
で設定したサブディレクトリの文字列が格納されます。
といってもnext.config.js
に書いたbasePath
変数の値がそのまま入っているだけですが...
実際にpublic内のファイルを読み込みたい時は、以下のようにして使います。
ここでは public/vercel.svg
という画像ファイルを、Image
コンポーネント(Next.jsに標準で入ってるimg
みたいなやつ)で表示させています。
// さっき書いたやつとかそれ以外のimportとか
// あとは処理もあるかもしれない
export default function Home() {
return (
<main>
// 略
<Image
src= {`${basePath}/vercel.svg`} // basePath変数を埋め込む
alt="Vercel Logo"
className={styles.vercelLogo}
width={100}
height={24}
priority
/>
// 略
</main>
)
}
ここではImage
要素のsrc
属性でbasePath
変数を使っています。
basePath
はただの文字列なので、このようにテンプレートリテラルで埋め込むことができます。
この方法はとても単純なため、手を加えるのは簡単です。
その代わり例外処理やフォーマットの修正は一切やっていません。
なので実際に使うならその辺りの処理を入れたほうがいいと思います。
概要: 何がしたかったか
- とりあえず
create next-app
で出てくるページを表示したかった - サイトを公開する場所を、開発環境ではルートに、本番環境ではサブディレクトリにしたかった
- GitHub Pagesで公開したかった
というわけで、Dockerを使って開発環境を整え、yarn create next-app
でNext.jsプロジェクトを準備し、yarn dev
でページが正常に表示されていることを確認し、GitHub Pagesで公開しました。
デプロイが成功して喜んだのも束の間、公開されたページを見たら、中央にあるはずの NEXT.svgという画像が表示されていませんでした。
なんで?と思ってsrc
属性を見たら、なんと/next.svg
と書かれているではありませんか!!(泣)
実際に画像があるのは/nextjs-page/next.svg
なのに...
もちろん右上の方にあるvercel.svg
も表示されていませんでした。
画像の公開先は変えられません(たぶん)。
なので変えるなら画像のsrc
属性のほうです。
画像はnextjs-page
というサブディレクトリにあるので、パスの最初に/nextjs-page
を追加すればうまく表示されるようになります。
ということで上の方法の登場です。
-
next.config.js
でサブディレクトリの設定をする - 画像(などの
/public
内にあるファイル)を読み込みたいコンポーネントがあるファイルの上の方に、2行ほどコードを追記する - テンプレートリテラル(``)を使ってパスを修正
色々考えた結果こうなりました。
本当にどうすればよかったのか...
環境
- MacOS Sonoma 14.1 (Apple M1)
- dockerを使用(
node:18.17.1
) - yarn 1.22.19
- Next.js 14.0.4
- TypeScript 5.3.3
- React 18.2.0
ことの経緯
もともと、以下の記事を参考にしてNext.jsのページを公開しようとしていました。
この記事はタイトルの通り、Next.jsで作ったサイトをGitHub Pagesで公開するというものです。
序文より引用:
この記事は
- create-next-app で Next.js アプリを作成
- GitHub にプッシュ
- GitHub Actions でビルド& GitHub Pages にデプロイ
までの工程をバカみたいに丁寧に書き起こしたものです。
という感じでやっていました。
リポジトリ名は変更
上の記事には以下のように書いてあります。
Repository name
は必ずユーザー名.github.io
にして、
Public
を選択してください。
Repository name
を任意の名前にするとnext.config.js
のbasePath
などを変更する必要が出てきます。(要検索)
とのことなのですが、残念ながら私は諸事情によりユーザー名.github.io
という名前のリポジトリが作れませんでした。
そのため、リポジトリ名はnextjs-page
でいくことにしました。
察しの良い方は気づいていると思いますが、画層がうまく表示されなかったのはこれが原因です。
ファイル構成
こんな感じになりました。
nextjs-page
├── .github
│ └── workflows
│ └── nextjs.yml
├── .next
├── README.md
├── compose.yaml
├── dockerfile
├── .gitignore
├── .eslintrc.json
├── next-env.d.ts
├── next.config.js
├── package.json
├── public # 画像などのファイルはここ
│ ├── next.svg
│ └── vercel.svg
├── src
│ └── app # ページのファイルはここ
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.module.css
│ └── page.tsx
├── tsconfig.json
└── yarn.lock
create-next-app
を叩くと出てくるファイルそのままです。
GitHub Actionsを使うためのファイルがありますが、それ以外に変更点はないと思います。
いろいろ調べる
画像が表示されてない!!なんで!?
と思い、いろいろ調べました。
原因は前述の通りsrc
属性のパスが間違っていることなのですが、じゃあどうやって解決したら良いのかがなかなか出てきませんでした。
まず目にしたのが、basePath
とassetPrefix
でした。
basePathとassetPrefix
この二つは何かというと、next.config.js
に書く設定です。
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // これはGitHub Pagesで公開するために必要
basePath: 'ここに設定を書く',
assetPrefix: 'ここに設定を書く',
}
module.exports = nextConfig
-
basePath
:すべてのベースとなるパス。 - 設定したパスにページがデプロイされるようになる。
ここに設定するのは/
から始まる文字列。/nextjs-page
など。
詳しくはこちら。 -
assetPrefix
:アセットファイル(CSSなど)のパス。
/public
にある画像ファイルなどのパスは影響を受けない。
ここに設定するのはURLか/
から始まる文字列。
詳しくはこちら。
この二つをいじればうまく画像が表示されるようになるのではないかと思って、いろいろ試そうと思いました。
ですが、よく考えたらこのバグはローカル環境では発生していません。
なのでいろいろ試す前にローカル環境でバグを再現します。
最終的な解決法方にこの二つが登場していないところから察せるかもしれませんが、この二つをいじっても解決しませんでした。
ですが一応、その実験の過程は書いておこうと思います。
ローカルでバグを再現する
まず、このバグはページの公開先が/nextjs-page
になっていることによって起こっています。
Next.jsのページはデフォルトだと/
にデプロイされるので、これを設定で変えないといけません。
そこで、basePath
を使います。
上に書いたbasePath
という設定ですが、これを変えるとパスのベースが変わります。
例えばbasePath: '/nextjs-page'
という行をnext.config.js
に追記すると、
デフォルトで用意されているページがlocalhost:3000
ではなくlocalhost:3000/nextjs-page
に表示されるようになります。
なのでnext.config.js
を編集します。
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // GitHub Pagesで公開するために必要
+ basePath: '/nextjs-page'
}
module.exports = nextConfig
この行を追記するだけであら不思議、画像がリンク切れで表示されなくなり、コンソールには404エラーが出力されるようになります。
これでバグの再現ができました。
いろいろ試してみる
というわけで、basePath
とassetPrefix
それぞれで設定をしたときとしていないときの違いを見てみました。
下はその結果をまとめた表です。
組み合わせ | ページの場所 | 読み込んでいるアセット | 読み込んでいる画像 | 実際のアセット | 実際の画像 |
---|---|---|---|---|---|
どっちも指定 | /nextjs-page | /nextjs-page | / | /nextjs-page | /nextjs-page |
basePathのみ | /nextjs-page | /nextjs-page | / | /nextjs-page | /nextjs-page |
assetPrefixのみ | / | /nextjs-page | / | / | / |
未指定 | / | / | / | / | / |
- 「指定」と書いてあるところで指定しているパスは
/nextjs-page
です。 - 「読み込んでいるアセット」とは、実際に表示されるHTMLの該当するタグの、
パスが書かれている属性の値です。 - 「読み込んでいる画像」も上と同じです。
- 自分のissueからのコピペです。
上の表から読み取れることは、こんな感じでしょうか。
- 「読み込んでいる画像」はどちらの影響も受けない
- 「読み込んでいるアセット」はどちらの影響も受ける
-
basePath
を変えるとアセットや/public
内のファイルの公開先が変わる
どうやら悲しいことに、Next.jsでは /public
内にあるファイルのパスの自動修正等は行われないみたいです。
自力で実装
さて、良い感じの方法はなさそうだ。
じゃあどうするか?自力でそれっぽい実装をするしかない。
ということでやっていきます。
とりあえずbasePath
あたりの情報を漁っていた時に見つけた以下の記事を参考にしました。
basePath
とassetPrefix
あたりの設定も書いてあったので助かりました。
そして登場するのがpublicRuntimeConfig
です。
publicRuntimeConfig
これは何かというと、私もよくわかってないのですが、ここに書いた値はコンポーネント側から参照できるみたいです。
例えばこんな感じにします。
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
publicRuntimeConfig: {
example: 'さんぷるでーた',
}
}
module.exports = nextConfig
上のようにすると、コンポーネント側からこの設定が見れます。
import getConfig from "next/config";
const { publicRuntimeConfig } = getConfig(); // serverRuntimeConfigもあった
console.log(publicRuntimeConfig.example); // さんぷるでーた
詳しくは以下のページをご覧ください。
上のページを見てみたら、こんなことが書いてありました。
一般的には、環境変数 を使用して設定を行うのがいいでしょう。なぜなら、ランタイム設定はレンダリング / 初期化時の余計な処理につながり、またAutomatic Static Optimizationと互換性がないからです。
どうやら環境変数の方がいいみたいです。
環境変数については全然分からないので解説できないです。すみません...
環境変数を使う場合は自力で調べてください。
実際のページで使う
といってもここで出来上がったのが冒頭に書いたソースコードです。
なのであんまり書くことないです。
では、このpublicRuntimeConfig
を使ってサブディレクトリをコンポーネント側で取得してみます。
まずはnext.config.js
にサブディレクトリの情報を書きます。
+ const basePath = '/nextjs-page'; // 変数にした
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
basePath, // きっと元からあった。これがどことのdiffなのかとか気にしない。
publicRuntimeConfig: {
- example: 'さんぷるでーた',
+ basePath,
}
}
module.exports = nextConfig
そしたらコンポーネント側で使います。
上の方に2行コードを追記したら準備完了です。
グローバル変数?しらんがな
import Image from 'next/image'
import styles from './page.module.css'
+ import getConfig from "next/config";
+ const { basePath } = getConfig().publicRuntimeConfig;
export default function Home() {
return (
<main>
// 略
<Image
- src= "/vercel.svg"
+ src= {`${basePath}/vercel.svg`} // basePath変数を埋め込む
alt="Vercel Logo"
className={styles.vercelLogo}
width={100}
height={24}
priority
/>
// 略
</main>
)
}
詳しくは冒頭のコード解説を見てください。
上のようにすると、無事にリンク切れがなくなります。やったね。
ローカルではルートにデプロイする
今のままだとバグの再現のためにローカル環境でもルートにサイトがデプロイされてしまうので、これを元に戻したいと思います。
これをするためには、ローカル(開発)環境のときと本番環境の時で処理を変えなければいけません。
何をもってして判断するのか?というと、NODE_ENV
という環境変数の値です。
// NODE_ENV環境変数がproduction(=本番環境)かどうか
const isProd = process.env.NODE_ENV === 'production';
// 本番環境ならサブディレクトリを'/nextjs-page'に、
// そうじゃないなら''にする
const basePath = isProd ? '/nextjs-page' : '';
こんな感じにすることで、NODE_ENV
の値によってサブディレクトリを変えることができます。
最終的に出来上がるのはこんな感じです。(冒頭のコードと同じとも言う)
const isProd = process.env.NODE_ENV === 'production';
const basePath = isProd ? '/nextjs-page' : '';
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
- basePath,
publicRuntimeConfig: {
basePath, // basePathを他のファイルから読み込めるようにする
}
}
module.exports = nextConfig
本番環境であるGitHub Pagesでは、basePath
という設定が空でも/nextjs-page
(リポジトリ名)にデプロイされるため、basePath
という設定の行は消しています。
変数名もbasePath
なのややこしいって?何も考えてなかったんだよ!!(後悔)
これで、ローカルでは/
にデプロイされるようになりました。
また、page.tsx
に変更を加えたことにより、画像のリンク切れも直りました。
参考