この記事の Next.js はバージョン 9.3 です。以下を参照したほうが良いかもしれません。
Next.js 9.5のアップデートにより、GitHub Pagesで妥協しない動的ルーティングが可能になった
背景
新しくなった Next.js のチュートリアル を完走し、せっかくだからと GitHub Pages にデプロイしてみたところ、ハイパーリンクや画像が正しく参照されなかった。
一般ユーザーの GitHub Pages の URL は https://USER_NAME.github.io/PROJECT_NAME/
の形式だが、 Next.js は原則としてルートからの絶対パスでリンクするため、例えばチュートリアルで作成するブログサイトにおける 1 つ目の記事は https://USER_NAME.github.io/posts/ssg-ssr
にリンクされてしまう。
注記
本記事は Next.js のチュートリアルのコードを前提としているが、筆者の趣味で TypeScript に置き換えられているので注意。JavaScript で書いても大差ないはず。
なお筆者は TypeScript も React もチュートリアルしか触ったことがない初学者なので、より良い方法や間違った使い方があれば教えてください!
結論
公式な方法が見当たらないので、/PROJECT_NAME
を環境変数に持たせて無理やり参照に追加した。
必要な環境変数を設定する。今回はビルド時に production
を指定したときだけ /PROJECT_NAME
がルートとなるようにした。
module.exports = {
assetPrefix: process.env.NODE_ENV === "production" ? "/PROJECT_NAME" : "",
publicRuntimeConfig: {
basePath: process.env.NODE_ENV === "production" ? "/PROJECT_NAME" : "",
},
};
assetPrefix
は生成される静的サイトのうち _next
ディレクトリが置かれるパスを指定するもの1。
次に、文字列に /PROJECT_NAME
を付与するモジュールを作成する。
import { format } from 'url';
import getConfig from 'next/config';
const { publicRuntimeConfig = {} } = getConfig() || {};
const { basePath } = publicRuntimeConfig;
export default (path: string): string =>
format((basePath || "") + (path || ""));
先ほどのモジュールを任意の名前2でインポートして、サイト内のパスを指定するすべての文字列を囲う。
ここでは layout.tsx
の一部を例に示す。
import Head from 'next/head'
import styles from './layout.module.css'
import utilStyles from '../styles/utils.module.css'
import Link from 'next/link'
import B from '../lib/basepath';
...
<>
<Link href={B("/")}>
<a>
<img
src={B("/images/profile.jpg")}
className={`${styles.headerImage} ${utilStyles.borderCircle}`}
alt={name}
/>
</a>
</Link>
<h2 className={utilStyles.headingLg}>
<Link href={B("/")}>
<a className={utilStyles.colorInherit}>{name}</a>
</Link>
</h2>
</>
...
index.tsx
にある動的ルーティングは href
ではなく as
の方を囲う。
...
<Link href="/posts/[id]" as={B(`/posts/${id}`)}>
<a>{title}</a>
</Link>
...
次に、 npm scripts を設定する。
...
"scripts": {
"dev": "next dev",
"build": "env NODE_ENV='production' next build",
"start": "next start",
"export": "next export"
},
...
build
に環境変数 production
を設定し、静的サイトを生成する export
を追加。
最後に以下を参照して .github/workflows/gh-pages.yml
を追加し、GitHub のパブリックリポジトリに PUSH すれば良い。
調べたこと
相対パスで参照する
チュートリアルの例では layout.tsx
などのコンポーネントが参照をもつ場合、相対位置を予め決めることができず破綻する。
Issue
類似の Issue がいくつか上がっているものの、現時点 (2020/5/14) で未解決。
どうやら結構難しいらしい。
Multi Zones
同じホスト上に別のパスで複数のアプリケーションをデプロイするための機能。
今回とは別な環境向けのような気がした。
先人たち
いずれも next/link
のラッパーコンポーネントを作ってパスを修正する方法。
- Next.js app on Github Pages - ITNEXT
- Deploy your NextJS Application on a different base path (i.e. not root)
通常のリンクで使う href
と動的ルーティングで使う as
の振り分けを実装する力がなかったのと、img
や link
などの参照を含むあらゆるタグのラッパーコンポーネントを用意するのが辛そうだったのでやめた。そのほか、外部リンクと内部リンクの振り分け、ないしタグの使い分けも必要になりそう。
-
本来は静的リソースを CDN に置くために使う
publicRuntimeConfig
には任意の定数を設定できる。 ↩ -
HtmlSpecialChars の慣習に習って 1 文字にすれば許せる程度の手間かなと ↩