6
6

More than 3 years have passed since last update.

Next.jsをGitHub Pagesにデプロイしたらリンクが壊れた

Last updated at Posted at 2020-05-13

この記事の 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 がルートとなるようにした。

next.config.js
module.exports = {
  assetPrefix: process.env.NODE_ENV === "production" ? "/PROJECT_NAME" : "",

  publicRuntimeConfig: {
    basePath: process.env.NODE_ENV === "production" ? "/PROJECT_NAME" : "",
  },
};

assetPrefix は生成される静的サイトのうち _next ディレクトリが置かれるパスを指定するもの1
publicRuntimeConfig には任意の定数を設定できる

次に、文字列に /PROJECT_NAME を付与するモジュールを作成する。

/lib/basepath.ts
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 の一部を例に示す。

/components/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 の方を囲う。

/pages/index.tsx
...
              <Link href="/posts/[id]" as={B(`/posts/${id}`)}>
                <a>{title}</a>
              </Link>
...

次に、 npm scripts を設定する。

package.json
...
  "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 のラッパーコンポーネントを作ってパスを修正する方法。

通常のリンクで使う href と動的ルーティングで使う as の振り分けを実装する力がなかったのと、imglink などの参照を含むあらゆるタグのラッパーコンポーネントを用意するのが辛そうだったのでやめた。そのほか、外部リンクと内部リンクの振り分け、ないしタグの使い分けも必要になりそう。


  1. 本来は静的リソースを CDN に置くために使う 

  2. HtmlSpecialChars の慣習に習って 1 文字にすれば許せる程度の手間かなと 

6
6
0

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
6
6