はじめに

Hello, Swift TypeScript ラバーなみなさん。これからラバーになるみなさん。

今日も今日とて React + TypeScript のお話です。前回と前々回で以下の記事を書きました。

調査編で出たように、現在 Reactの公式サイト では、create-react-app コマンドではなく、Next.js などのフレームワークでのプロジェクト立ち上げを推奨しています。

そこで、今回はタイトルのように Next.js で静的サイトの構築を行ってみます!

動的サイトを構築してみたい場合は、公式のチュートリアルがめちゃくちゃわかりやすいです。

導入

環境

  • npm: 10.2.4
  • Node.js: 20.11.0
  • TypeScript: 5.0.0

構築

まずはプロジェクト作成です。
任意のディレクトリに移動し、以下を実行します。

$ npx create-next-app@latest

いきなりプロジェクト名を指定して作成することもできます。

$ npx create-next-app@latest my-sample-app

今回は最初にプロジェクト名を指定しないパターンで実行します。
実行するといくつかの質問が表示されます。
以下は今回の回答例と適当な和訳です。

What is your project named? // プロジェクト名は?
→ my-sample-app

Would you like to use TypeScript? // TypeScript使う?
→ Yes

Would you like to use ESLint? // ESLint使う?
→ Yes

Would you like to use Tailwind CSS? // Tailwind CSS使う?
→ Yes

Would you like your code inside a `src/` directory? // 実装を行うディレクトリ名は`src/`にする?(Noにすると`app/`になる)
→ No

Would you like to use App Router? (recommended) // App Router使う?
→ Yes

Would you like to use Turbopack for `next dev`? // `next dev`したときにTurbopack使う?
→ Yes

Would you like to customize the import alias (`@/*` by default)? // インポートエイリアスをカスタマイズする?
→ No

すべて回答すると Next.js フレームワーク で React + TypeScript のプロジェクトが作成されます。

作成したプロジェクトディレクトリに移動します。

$ cd my-sample-app

プロジェクトを開きましょう。
今回は VScode を使用します(code コマンドのインストール済みとします)

$ code .

プロジェクトが開きました。
Next.js で静的サイトを書き出すには、いくつか変更が必要です。
next.config.jsnextConfig を以下のように書き加えます。

const nextConfig: NextConfig = {
    output: "export", // add
};

ちなみに、output: “export”というのはなにかというと、Next.jsアプリを静的サイト(Static Site Generation: 以下SSG)としてエクスポートするための設定です。

この設定を追加すると、Next.js の $ next build 時に out/ ディレクトリに静的ファイルが生成されます。
その後、$ next export コマンドを使うと、この out/ フォルダの内容をそのままホスティングできる形に出力できます。

ただし、Server Side Rendering(以下SSR)とAPI Routesは使用ができなくなります。
要は動的サイトではなく、静的サイト専用になるという意味です。

書き加えたら、もう静的サイトとしてビルドできます。
すぐにブラウザで確認するのであれば、

$ npm run dev

静的サイトとして書き出して確認するには、

$ npm run build
...書き出し完了後
$ npx serve@latest out
...serve のインストールが始まります

以下の画面が表示されたら成功です!

ちなみに

ビルドファイルのアップロード先がサブディレクトリにある場合、上記の手順では CSS や Image のパス解決ができません。

以下の Tips を試してみてください。

  1. サブディレクトリのパスを入手する
  2. next.config.jsassetPrefixbasePath を記述する
  3. (おまけ)環境ごとに env で切り替える
1. サブディレクトリのパスを入手する

まずはアップロード先の URL を入手しましょう。
例えば、https://hogehoge.com/fugafuaの場合、サブディレクトリに該当する場所は/fugafugaになります。

2. next.config.jsassetPrefixbasePath を記述する

以下のように書き加えます。

const prefixPath = '/fugafuga';

const nextConfig: NextConfig = {
  output: 'export',
  basePath: prefixPath,    // アプリのベースURLを変更する
  assetPrefix: prefixPath, // 静的アセットの配信URLを変更する
};

basePath は、実際に Web にアップロードした際のサブディレクトリ名を指定します。
デフォルトは/で、サブディレクトリがない場合はそのまま使用します。
https://hogehoge.com/でウェブサイトが閲覧できる状態)

assetPrefix は静的アセットの読み込み先を指定します。
ですが、サブディレクトリ経由のアクセスの場合、パスの解決ができず 404 になってしまうため、ここにも prefixPath を設定します。

3. (おまけ)環境ごとに env で切り替える

次に開発時とステージング環境での検証時と本番環境で、全部パスが違う…
みたいなことはよくあるので、env で切り替える方法を紹介します。

ちなみに設定をしないと、環境を切り替えるたびに以下のように指定する必要があります。

$ NODE_ENV=develop npm run build

まずは環境分の env ファイルを用意します。
今回はルートディレクトリに.env.develop.env.stagingを用意します。
本番のパスはまだわからないという、あるあるな状況です。

以下の2つの環境変数を両方のファイルに記述します。

  • ASSET_PREFIX_ENABLED=: prefix path があるかどうか
  • ASSET_PREFIX_URL=: prefix path の記述

develop での動作確認時は環境構築時に紹介したように、npm run buildnpx serve@latest outを併用する予定です。

その場合、だいたいhttp://localhost:3000にホストされるので、サブディレクトリがありません。

ですので、以下のようになります。

ASSET_PREFIX_ENABLED=false
ASSET_PREFIX_URL=

staging 環境では、サブディレクトリが/staging-appの上で動くと想定します。
URL はhttps://example.com/staging-app/のようになりますので、以下のように env に記述します。

ASSET_PREFIX_ENABLED=true
ASSET_PREFIX_URL=/staging-app

次にnext.config.jsprefixPath を以下のように変更します。

const prefixPath = process.env.ASSET_PREFIX_ENABLED === 'true' ? process.env.ASSET_PREFIX_URL : '';

この記述で、ASSET_PREFIX_ENABLEDtrue の場合は prefix を作成し、ない場合は空の状態にします。
ASSET_PREFIX_URLの有無だけでもいいかもしれませんが)

最後に package.json のビルドの script を変更します。
以下のようにしました。

"scripts": {
    "dev": "NODE_ENV=develop next dev --turbopack",
    "build:develop": "NODE_ENV=develop next build",
    "build:staging": "NODE_ENV=staging next build",
    "build": "NODE_ENV=production next build",
    "start": "NODE_ENV=production next start",
    "lint": "NODE_ENV=develop next lint"
}

NODE_ENVは使用する env ファイルを指定しています。NODE_ENV=develop であれば、.env.developが適用されます。

コマンドは以下のように使用します。

$ npm run build:develop
$ npx serve@latest out

これで、サブディレクトリ差分問題も解決!!

まとめ

それでは良い開発ライフを!
…と終わってしまうと面白くないので、Next.js の特徴を紹介します!

特徴

from GitHub Copilot Chat さん

Next.js は、React をベースにした人気の JavaScript フレームワークで、以下のような特徴があります。

  1. サーバーサイドレンダリング(SSR): サーバー側でページをレンダリングすることで、初期表示が高速になり、SEO にも有利
  2. 静的サイト生成(SSG): ビルド時に HTML を生成し、静的ファイルとして出力することで、レスポンスが高速になり、サーバーの負荷を軽減
  3. ファイルベースのルーティング: `pages`ディレクトリ内のファイル構成に基づいて自動的にルーティングが設定され、直感的で分かりやすいルーティングが可能
  4. ビルトインの CSS サポート: `styled-jsx`やグローバル CSS を使って、簡単にスタイリングが行える
  5. 画像の最適化: `next/image`コンポーネントを使って、画像の遅延読み込みやサイズ調整、WebP 形式への変換などが自動的に行われる

Next.js を使うことで、効率的に高速で使いやすい Web アプリケーションを開発できます。

深堀り from GitHub Copilot Chat さん + 感想

ファイルベースのルーティング

Next.js の「ファイルベースのルーティング」は、プロジェクト内の pages ディレクトリにあるファイルの構成に基づいて、自動的にルートが作成される仕組みです。通常の React アプリでは react-router などを使ってルーティングを定義しますが、Next.js ではファイルやフォルダの構造そのものがルーティングのルールになります。

静的サイト対応
1. 基本的なルーティング

pages フォルダの直下にある .tsx(または .js)ファイルが、自動的に対応する URL にマッピングされます。

/pages
  ├── index.tsx      # http://localhost:3000/ に対応
  ├── about.tsx      # http://localhost:3000/about に対応
  ├── contact.tsx    # http://localhost:3000/contact に対応

例えば、pages/about.tsx ファイルがあれば、http://localhost:3000/about にアクセスすると、そのコンポーネントが表示されます。

2. ネストされたルート

フォルダ構造を使うことで、ネストされたルート(サブディレクトリ)を作成できます。

/pages
  ├── index.tsx
  ├── blog
  │   ├── index.tsx  # http://localhost:3000/blog に対応
  │   ├── post.tsx   # http://localhost:3000/blog/post に対応

このように、blog/index.tsxhttp://localhost:3000/blog に、blog/post.tsxhttp://localhost:3000/blog/post に対応します。

動的サイト専用
3. 動的ルーティング

ファイル名を角括弧 [ ] で囲むと、その部分が動的なパラメータとして扱われます。

/pages
  ├── index.tsx
  ├── blog
  │   ├── [id].tsx  # http://localhost:3000/blog/123 のようなURLに対応

例えば pages/blog/[id].tsx を作成すると、http://localhost:3000/blog/123http://localhost:3000/blog/abc など、id の部分が変化する URL に対応できます。

この動的なパラメータは useRouter() フックで取得できます。

4. キャッチオールルーティング

さらに、/[...slug].tsx のようにすると、任意の深さのパスをキャッチできます。

/pages
  ├── blog
  │   ├── [...slug].tsx  # http://localhost:3000/blog/a/b/c にも対応

この場合、useRouter() で slug を配列として取得できます。

…といろいろ紹介してもらいました。

ディレクトリでルーティングを切ってくれるのは、とても開発がしやすかったので、試してみてください!

画像の最適化

1. next/image が 静的サイト で使える機能
  • ローカルの画像(public/ 配下)
  • Lazy Loading(遅延読み込み)
  • プレースホルダー(ぼかし・ドミナントカラー)
  • 外部画像の表示(next.config.js で許可が必要)
2. next/image が静的サイトで使えない機能
  • 外部画像の最適化(自動圧縮、リサイズ)が使えない
    • On-Demand Image Optimizationが使用不可になるので、画像はそのままのサイズでエクスポートされる
  • srcに外部URLを使用できない
    • next/imageはローカル画像ファイルのみ扱える
    • 使用したい場合、<img>タグを使用するかunoptimizedオプション(<img>と同じになる)を付ける
    • もしくは後述のgetStaticPropsを使用する
  • layoutの一部機能が使用できない
    • intrinsic, responsive, fillなどのレイアウトが機能しない
    • 通常と同じようにwidth/heightを使用する
3. SSG で next/image を使う方法

public/ フォルダの画像は SSG 時にそのまま使用できます。

import Image from "next/image";

export default function Home() {
   return (
      <div>
         <h1>Next.js Image Optimization</h1>
         <Image
            src="/images/sample.jpg" // public フォルダ内の画像
            width={600}
            height={400}
            alt="Sample Image"
         />
      </div>
   );
}

SSG では public/ の画像は事前にビルドされるので、最適化が適用される。

4. getStaticProps を使う(外部画像の利用)

next/image で 外部の画像 を使う場合は、getStaticProps を利用してデータを事前取得できます。

import Image from "next/image";

export async function getStaticProps() {
  return {
    props: {
      imageUrl: "https://example.com/image.jpg", // 外部画像URL
    },
  };
}

export default function Home({ imageUrl }: { imageUrl: string }) {
  return (
    <div>
      <h1>Static Image</h1>
      <Image src={imageUrl} width={600} height={400} alt="External Image" />
    </div>
  );
}

ポイント

getStaticProps を使うことでSSG でも外部画像を取得可能ですが、next.config.js はドメインの許可が必要になります。

next.config.js の設定

module.exports = {
  images: {
    domains: ["example.com"], // 外部画像のドメインを許可
  },
};
5. blur プレースホルダー(ぼかし画像)も SSG 可能

画像のプレースホルダー(blur)機能も、SSG で利用可能です。

<Image
  src="/images/sample.jpg"
  width={600}
  height={400}
  alt="Blurred Image"
  placeholder="blur"
/>

public/ の画像は事前に処理されるので、SSG でもプレースホルダーが適用可能

感想

Next.js の next/image はかなり強力な API で、これだけでも Next.js を採用するモチベーションになりました。

まとめ

動的サイトで本領を発揮する Next.js ですが、静的サイトでもパワフルな API を使用できるため、複数ページがある場合などは検討する価値があります。

前回の Vite との違いは、

  • Vite: ビルドツール
  • Next.js: フレームワーク

という違いがあります。

シンプルにプロジェクトを作成したいなどの要望の場合は Vite を、本格的なプロジェクトの場合は Next.js を選択肢の一つとして考えてみてください。

それでは、よき開発ライフを!!



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる