ちょっと話題の記事

【忙しい人のための】Next.js公式チュートリアルを完走してきたので記事1本で振り返る【ギュッと凝縮】

忙しい人のためにNext.js公式チュートリアルをギュッと凝縮
2023.04.24

本記事はNext.jsのチュートリアルが大きく変わったためリンク切れを起こしています。
技術メモのため記事としては残しますが、リンク切れにご留意ください。
また機会があれば新チュートリアルで記事を書こうと思いますm(_ _)m

こんちには。

データアナリティクス事業本部 インテグレーション部 機械学習チームの中村です。

今回は以下のNext.jsのチュートリアルをほぼ一通り(SEOのところ以外)実施しましたので、ポイントを記事化しました。

チュートリアル自体は、以下のような内容が分かるものとなっています。

  • CRA(create-react-app)のみ使用しているとイメージしづらい、素のHTML + JavaScriptとReactの関係のイメージが分かる
  • Reactがフレームワークではなくて、UIライブラリであるということが明確に理解できる
  • Babelって何している?バンドラって何?などが分かる
  • Next.jsを使うメリットが理解できる、どのようなときに有効なのか把握できる

以降はポイントのみを解説していきます。(私個人のコメントは緑枠で表記しています)

忙しい方やチュートリアルを一度やったことがある方の振り返りになれば幸いです。

Next.jsを触ったことがなくて、記事を読んで興味を持たれた方はぜひ、本編のチュートリアルの方にトライしてみてください。

なお、私自身はCRA(create-react-app)でアプリケーションを書いたことがありますが、Next.jsは勉強中という程度の人間となっています。

それでは見ていきましょう!

(始めにおことわりしておきますが、チュートリアルを1記事に納めているので結構長編です…)

FOUNDATIONS

Next.jsについて

Next.jsとは?

  • Next.jsはReactフレームワーク
  • Webアプリケーションの構成要素
    • UI以外にもルーティング、データフェッチ
    • その他レンダリングやサードパーティとの統合(CMS、認証支払いなど)
  • Reactとは?
    • Reactはフレームワークではなくライブラリであり、インタラクティブなUIを提供するライブラリ
    • ライブラリとはUI構築に役立つ関数を提供するが、どこで使うかは開発者に任されているという意味
    • それゆえに一からアプリケーションを構築するには労力が必要
  • Next.jsはUI、ルーティング、データフェッチ、レンダリング、統合などの一般的なアプリケーション要件を解決
  • これにより開発者とエンドユーザーの体験を向上させる

JavaScriptからReactへ

JavaScriptでUIを更新する

  • DOMはJavaScriptで操作可能なため、素のHTMLとDOMは異なる場合がある
    • もちろん操作によりインタラクティブにDOMツリーを変更することも可能
<!-- index.html -->
<html>
  <body>
    <div id="app"></div>

    <script type="text/javascript">
      // Select the div element with 'app' id
      const app = document.getElementById('app');

      // Create a new H1 element
      const header = document.createElement('h1');

      // Create a new text node for the H1 element
      const headerContent = document.createTextNode(
        'Develop. Preview. Ship. 🚀',
      );

      // Append the text to the H1 element
      header.appendChild(headerContent);

      // Place the H1 element inside the div
      app.appendChild(header);
    </script>
  </body>
</html>
  • この方法の課題
    • 冗長である。DOMをどのように変更するかユーザが記述する必要がある。(命令型的である)
    • そのため見せたいものを記述するだけで、DOMの更新方法はコンピュータに任せる方法が発展(宣言的)
  • 命令型と宣言型
    • UI構築に関しては宣言型である方が好まれる
    • DOMを操作するメソッドを書く代わりに、表示したいものを宣言する
    • UIを構築するための宣言型ライブラリがReact
  • 追加資料

React入門

  • Reactを使う方法
    • reactとreact-domという二つを&#60;script src="..."&#62;で読み込むことで使える
    • DOMを直接操作する代わりにReactDOM.render()メソッドを使用
<!-- index.html -->
<html>
  <body>
    <div id="app"></div>

    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

    <script type="text/javascript">
      const app = document.getElementById('app');
      ReactDOM.render(<h1>Develop. Preview. Ship. 🚀</h1>, app);
    </script>
  </body>
</html>
  • ただしこのままではJavaScriptの部分でHTMLのタグが使用されているため、JavaScriptのSyntaxErrorとなる
  • そのためにJSXが必要
  • JSXとは
    • JavaScriptの機能拡張で、HTMLのような構文でUIを記述可能
    • ブラウザはJSXを理解できないため、JSXをJavaScriptに変換するためにBabelのようなコンパイラが必要
  • Babelを使うフロー
    • Babelを&#60;script src="..."&#62;で読み込み
    • &#60;script type="text/javascript"&#62;の代わりに&#60;script type="text/jsx"&#62;を使うことでBabelに変換対象であることを通知
<html>
  <body>
    <div id="app"></div>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!-- Babel Script -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/jsx">
      const app = document.getElementById('app');
      ReactDOM.render(<h1>Develop. Preview. Ship. 🚀</h1>, app);
    </script>
  </body>
</html>

Reactに必要なJavaScriptの知識

ここは今回あまり読んでおらず、解説としてもNext.js自体からは離れるため割愛します

React コアコンセプト

  • 以下の3つ
    • コンポーネント
    • プロップス
    • ステート

コンポーネントでUIを構築する

  • Reactのコンポーネントについて
    • コンポーネントにすることで保守性が高くなる。
    • Reactのコンポーネントは単なるJavaScriptの関数であり、JSXをreturnする関数として作成
    • ただし、HTMLやJavaScriptと区別するために大文字で記述し、HTMLタグと同じようにangle bracketsで囲む必要がある
    • コンポーネントをDOMにレンダリングするには、ReactDOM.render()に第一引数として渡す
<script type="text/jsx">
  const app = document.getElementById("app")

  function Header() {
     return (<h1>Develop. Preview. Ship. 🚀</h1>)
   }

   ReactDOM.render(<Header />, app)
</script>
  • コンポーネントは入れ子構造にできる
    • ツリー構造を構築できる
    • ReactDOM.render()にはトップのコンポーネントを渡せばよい
function Header() {
  return <h1>Develop. Preview. Ship. 🚀</h1>;
}

function HomePage() {
  return (
    <div>
      <Header />
    </div>
  );
}

ReactDOM.render(<HomePage />, app);

プロップスでデータを表示する

  • プロップスとは
    • プロップスによりコンポーネントにプロパティを渡すことが可能
    • プロップスは読み取り専用であるため、親から子への方向の一方通行のデータフロー
    • プロップスはオブジェクトになるので、デストラクチャリングも可能
  • JSXでプロップスなどの変数を使う方法
    • JSX構文で中括弧{}で囲うことで可能
    • 中括弧は「JSX Land」にいながら「JavaScript Land」に入る方法と考えることが可能
function HomePage() {
  return (
    <div>
      <Header title="React 💙" />
    </div>
  );
}

function Header({ title }) {
  return <h1>title</h1>;
}
  • JavaScript式は何でも使える
    • オブジェクトのプロパティ
    • テンプレートリテラル
    • 関数の戻り値
    • 三項演算子 { title ? title : 'default title'}
function Header({ title }) {
    return <h1>{title ? `Cool ${title}` : 'Default Title'}</h1>;
}
  • リストの反復
    • mapを使って表現
    • 要素にはkey propsを渡すよう警告がでる
    • これは、Reactが配列のアイテムを一意に識別するための何かを必要とするためで、keyを与えるとDOMのどの要素を更新すべきかを知ることが可能
    • 例えばアイテムIDのように一意であることが保証されたものを使うことを推奨
function HomePage() {
  const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];

  return (
    <div>
      <Header title="Develop. Preview. Ship. 🚀" />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
    </div>
  );
}

ステートによる相互作用

  • Reactではイベント名はキャメルケースとなる(onClick, onChange, onSubmitなど)
      <button onClick={handleClick}>Likes ({likes})</button>
  • useStateフックによりステートをコンポーネントに追加
  • ステートとは通常はユーザのインタラクションによって引き起こされる部分と考える
  • コンポーネントに渡されるプロップスと異なり、ステートはコンポーネント内で開始され保存する
  • 子コンポーネントにプロップスとして状態情報を渡すことはできるが、更新のロジックはコンポーネント内に保持が必要

どうしても子コンポーネントに更新させたい場合、良い設計はさておきイベントハンドラとなる関数自体(以下でいうと`handleClick`)をプロップスで子コンポーネントに与えることで可能です。

function HomePage() {
  // ...
  const [likes, setLikes] = React.useState(0);

  function handleClick() {
    setLikes(likes + 1);
  }

  return (
    <div>
      {/* ... */}
      <button onClick={handleClick}>Likes ({likes})</button>
    </div>
  );
}

likesはステートの値、setLikesはlikesを更新できる関数、useState(0)の0は初期値です。
onClickには関数オブジェクト自体(`handleCllick`)を渡す必要があり、関数オブジェクトを実行した状態(`handleClick()`)で渡すと無限ループになります(最初よくやるミス)。
handleClickをわざわざ定義したくない人は`onClick={ () => setLikes(likes + 1) }`とすればできないこともないです。

Reactの学習を継続する方法

ReactからNext.jsへ

  • ここまでの以下のようなサンプルコードをNext.jsに移行する手順について学ぶ
<html>
  <body>
    <div id="app"></div>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!-- Babel Script -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/jsx">
      const app = document.getElementById('app');
      {/* ... */}
      ReactDOM.render(<HomePage />, app);
    </script>
  </body>
</html>

Next.jsをはじめよう

  • 空のpackage.jsonを作成
// package.json
{
}
  • npmでreact, react-dom, nextをインストール
npm install react react-dom next
  • これにより、index.htmlに対して不要なものを削除が可能となる
    • index.htmlからreactとreact-domを読み込む&#60;script src="..."&#62;を削除可能(npmでreactとreact-domはインストール済みなので)
    • &#60;html&#62;&#60;body&#62;はNext.jsが作るので不要
    • app要素とReactDOM.renderもNext.jsが管理するので不要
    • JSXをJavaScriptにコンパイルするBabelも不要(Next.jsがJSXを変換するコンパイラを含むため)
    • 同様にBabelを読み込む&#60;script src="..."&#62;や変換対象であることを示す&#60;script type="text/jsx"&#62;も不要
    • React.useStateがuseStateでOK

「React.useStateがuseStateでOK」はインポートの仕方によると考えています。

  • index.htmlはJSXのみとなったためindex.jsxにリネーム
  • index.jsxをpagesというフォルダに移動(Next.jsにとってpagesが重要な意味を持ちます)
  • Next.jsがどのコンポーネントをメインコンポーネントとするか判断するためにexport defaultをindex.jsxのどこかに追記
  • package.jsonに"next dev"を追加
  • ここまででReactからNext.jsの以降できたため、以下で開発サーバの立ち上げが可能
    • localhost:3000にアクセスすれば確認でき、Fast Refreshも可能に
npm run dev

ちなみにReactのアプリを一から構築する際に良く使用されるCRA(`create-react-app`)も、Next.jsなどのフレームワークと同様にReactアプリケーションの構築に必要な様々なものを準備することができます。
このCRAはフレームワークの台頭以前からあるものですが、その経緯と将来になどについて興味があれば以下を参照ください。
・Dan氏によるCreate React Appの将来、およびReactとフレームワークの関係性についてのコメントの翻訳 | Zenn

次のステップ

  • 次のレッスンでは、関連するWeb開発の概念を紹介しながら、Next.jsがどのように機能するかを探る

Next.jsのしくみ

  • 以下の内容を理解していく
    • コードが実行される環境:開発環境と本番環境
    • コードが実行されるとき:ビルドタイムとランタイムの比較
    • レンダリングが行われる場所:クライアントとサーバー

開発環境から本番環境へ

  • 開発環境と本番環境で最適化の方針が異なる
  • 開発環境は開発者向けに最適化
    • TypeScriptとESLintの統合、Fast Refreshなど、開発者の体験を向上させる
  • 本番環境はエンドユーザーに最適化
    • コードを変換して、パフォーマンスやアクセシビリティを向上させる
  • 上記のため、開発環境を本番環境に移行するために多くのことが必要
    • コンパイル(Compiling)、ミニファイ(Minifying)、バンドル(Bundling)、コード分割(Code Splitting)
  • Next.jsは、これらの変換や基礎的なインフラストラクチャの多くを処理し、アプリケーションの本番稼働を容易にする
  • これが可能なのは、Next.jsが低レベルプログラミング言語であるRustで書かれたコンパイラや様々な利用できるプラットフォームであるSWC(Speedy Web Compiler)を備えているため

コンパイル

  • コンパイルとは
    • JSX、TypeScript、JavaScriptのモダンバージョンなどの開発者に優しい言語をブラウザが理解できるJavaScriptに変換すること
    • コンパイルは開発環境でも行われるが、本番環境のビルドの際にも行われる

ミニファイ

  • ミニファイとは
    • 開発者は、人間が読みやすいように最適化されたコードを書く
    • このコードには通常、コメント、スペース、インデント、複数行など、実行には必要のない余分な情報が含まれる
    • コードの機能を変えることなく、これらを削除するプロセスがミニファイ
    • ファイルサイズを小さくすることで、アプリケーションのパフォーマンスを向上させることが目的
  • Next.jsでは、JavaScriptとCSSのファイルが自動的にミニファイされ、本番に使用される

バンドル

  • バンドルとは
    • 開発者は、モジュール、コンポーネント、および関数に分割して、複数のファイルでより大きなアプリケーションを構築する
    • バンドルとは、依存関係を解決し、ファイル(またはモジュール)をブラウザ用に最適化されたバンドルにマージするプロセス
    • 具体的にはアプリケーションの依存関係グラフを解決し、ファイル数を減らす
    • これにより、ユーザーがウェブページを訪れた際にファイルへのリクエスト数を減らすことを目的とする

コード分割

  • コード分割とは
    • 開発者は通常、アプリケーションを複数のページに分割し、異なるURLからアクセス可能とする(エントリポイントが複数となる)
    • コード分割はこれを元にバンドルを、各エントリーポイントで必要な小さなチャンクに分割するプロセス
    • Next.jsには、pages/の各ファイルは、ビルドステップにおいて、自動的に独自のJavaScriptバンドルにCode Splittingされる
    • ページ間で共有されるコードは、別に分割され、再度同じコードがダウンロードされることを防ぐ
  • 最初のロード後、次に移動する可能性のある他のページをプリロードし始める
  • ダイナミックインポートは、最初に読み込まれるコードを手動で分割する別の方法

ビルドタイムとランタイム

  • ビルドタイムとランタイム
    • ランタイムはビルド後にユーザのリクエストに応えてアプリケーションが実行される期間のこと
    • それより前のアプリケーションを本番用に準備する一連のステップがビルドタイム

クライアントとサーバー

  • クライアントとサーバー
    • Webアプリケーションの場合、クライアントはブラウザなどを指し、サーバーはデータセンターのコンピュータなどを指す
    • クライアントはリクエストをサーバーに送信し、受け取った応答をユーザが操作可能なインターフェースに変換する
    • サーバーはアプリケーションのコードを格納し、リクエストを受けて適切なレスポンスを返す

レンダリング

  • レンダリングとは
    • Reactで書いたコードをHTMLに変換する作業
    • レンダリングはサーバーでもクライアントでも行われる
  • 3種類の方式がある
    • CSR : クライアントサイドレンダリング
    • SSR : サーバーサイドレンダリング
    • SSG : 静的サイト生成
  • サーバーサイドレンダリングと静的サイト生成は、クライアントへ送信前にHTML変換がされるため、プリレンダリングともいう
  • CSR : クライアントサイドレンダリング
    • 空のHTMLとUI構築のためのJavaScriptが送られ、最初のレンダリングをユーザのデバイス側で行う
    • 標準的なReactのアプリケーションはこちらとなる
  • プリレンダリング
    • Next.jsではデフォルトで全ページをプリレンダリングする(サーバー上でHTMLを作成する)
    • これにより、CSRのレンダリング中のようにユーザに白紙が表示される、などが発生しないことがメリット
  • SSR : サーバーサイドレンダリング
    • リクエストごとにHTMLがサーバー上で生成されて送付される
    • サーバーからは生成されたHTML、JSONデータ、インタラクティブなJavaScriptの命令がクライアントに送付される
    • クライアントでは、HTMLを使用して高速にページを表示し、ReactがJSONデータとJavaScriptの命令によりインタラクティブな動作を実現する(このプロセスをハイドレーションと呼ぶ)
    • Next.jsでは、getServerSidePropsを使うことで、サーバーサイドでページをレンダリングすることが可能
    • 詳細は「Data Fetching: getServerSideProps | Next.js」参照
  • SSG : 静的サイト生成
    • 実行時にはサーバーは存在せず、デプロイ時にビルドタイムで一度生成され、HTMLはCDNに保存され、リクエストごとに再利用する
    • Next.jsでは、getStaticPropsを使うことで、静的にページを生成する
    • 詳細は「Data Fetching: getStaticProps | Next.js」参照
  • 使い分け
    • Next.jsではページ単位でこれらのレンダリング方法を選択可能
    • 特定のユースケースに適したレンダリング方法については、データフェッチのドキュメントを参照する
    • 詳細は「Data Fetching: Overview | Next.js」参照

CDNとエッジ

  • ネットワークの構成
    • アプリケーションのコードは、オリジン、CDN、エッジなどに分散
  • オリジンサーバー
    • オリジンサーバーはアプリケーションのオリジナル版を保存
    • オリジンサーバーはリクエストを受信すると計算を行ってレスポンスを送信するが、結果作業の結果はCDNに移動させることが可能
  • CDN(Content Delivery Network)
    • クライアントとオリジンサーバー間に配置し、最も近いCDN拠点がユーザにレスポンスすることが可能
    • これによりオリジンサーバーの負荷が軽減され、コンテンツの配信も高速化される
  • エッジ
    • エッジは概念でありCDNもエッジの一部である
    • エッジサーバーはコードの断片の実行をすることも可能
    • つまりコンテンツのキャッシュも、コードの実行もユーザに近いところで行うことが可能
    • Next.jsでは、Middlewareを使ってEdgeでコードを実行することができ、React Server Componentsを使えばすぐに実行することもできる
    • 詳細は「Advanced Features: Middleware | Next.js」、「React 18: Overview | Next.js」参照

CREATE YOUR FIRST APP

ここからは、Next.jsのサンプルプロジェクトを用いた説明となっています。
DevelopersIOのブログ記事でもやってみた記事が詳しく掲載されています。
・公式チュートリアルでNext.jsに入門してみた (1) 〜アプリ新規作成、ページ遷移、スタイリング編〜 | DevelopersIO
こちらのシリーズでかなり詳しく記載されているので、ここでは要点を箇条書きにするのにとどめます。詳細は上記のブログも併せてごらんください。

Create a Next.js App

セットアップ

  • create-next-appでテンプレートをGitHubから取得して構築

Welcome to Next.js

  • 開発サーバーにアクセスできる

ページの編集

  • pages/index.jsを編集して、画面がFast Refreshで更新されることを確認

Next.jsにおけるPages

  • Next.jsでは、pages配下のパスでルートと自動的に関連付けされる
    • pages/index.jsが`http://localhost:3000/`のルートに関連付けられる
    • 同様にpages/posts/first-post.jsはhttp://localhost:3000/posts/first-postに関連付けられる
  • pages配下で.jsファイルからexport defaultされたReactコンポーネントをページという
    • もちろん、ReactコンポーネントなのでJSXをreturnする必要がある
  • HTMLやPHPファイルを使ってWebサイトを構築するのと同じようなもの
  • HTMLを書く代わりにJSXを書き、Reactコンポーネントを使用するイメージ

Linkコンポーネント

  • Next.jsでは、さまざまなコンポーネントがあらかじめ準備されており、&#60;Link&#62;もその一つ
  • &#60;Link&#62;&#60;a&#62;タグの代わりに使うイメージで、hrefというプロパティは同じ

クライアントサイドナビゲーション

  • クライアントサイドナビゲーションとは
    • リンクによりクライアントサイドナビゲーションが可能に
    • JavaScriptを使ってページ遷移を行うこと
    • ブラウザのページ遷移よりも高速に動作
    • &#60;Link&#62;ではなく&#60;a&#62;タグを使う場合はフルリフレッシュとなる
  • コード分割とプリフェッチ
    • 前提としてNext.jsではコード分割を自動で行い、各ページで必要なものだけを読み込む
    • 本番ビルドでは、Linkコンポーネントがブラウザのビューポートに表示されると自動的にバックグラウンドでリンク先をプリフェッチする
    • これによりページ遷移が瞬時に機能する
  • Linkコンポーネントについては、next/linkのAPIリファレンスを参照
  • ルーティング全般については、ルーティングのドキュメントで詳しく解説

Assets, Metadata, CSS

  • Next.jsには、CSSと Sassが組み込まれているが、ここではCSSを使用
  • ここでは以下を学ぶ
    • Next.jsに静的ファイル(画像など)を追加する方法
    • 各ページの&#60;head&#62;の中身をカスタマイズする方法
    • CSS Modulesを使用してスタイリングされた再利用可能なReactコンポーネントを作成する方法
    • pages/_app.jsで グローバルCSSを追加する方法
    • Next.jsでスタイリングするための便利なTipsの紹介

Assets

  • 静的Asset
    • Next.jsではpublic配下に画像などの静的Assetを配置することが可能
    • publicのファイルはpagesと同様にアプリケーションのルートから参照可能
    • publicディレクトリは、robots.txt、Google Site Verification、およびその他の静的資産にも便利
    • 静的ファイルについては「Basic Features: Static File Serving | Next.js」も参照
  • Imageコンポーネント
    • &#60;img&#62;タグの代わりに使用することで様々な機能を提供
    • 異なる画面サイズでの画像の応答性を確保すること
    • サードパーティ製のツールやライブラリで画像を最適化する
    • ビューポートに入ったときだけ画像を読み込む
  • 特徴の詳細
    • ビルド時ではなくユーザーからのリクエストに応じてオンデマンドで画像を最適化するため、画像が増えてもビルド時間は増加しない
    • 画像はデフォルトで遅延ロードされるため、画面外の画像によって読み込み速度が低下したりが発生しない
    • Googleが検索ランキングに利用しようとしているCumulative Layout Shiftを回避するように常に描画することが可能
    • heightとwidthのPropsはソース画像と同じアスペクト比で設定が必要
    • 詳しくは「next/image | Next.js」を参照

Metadata

  • Headコンポーネント
    • &#60;head&#62;タグの代わりにこのコンポーネントを使う
    • &#60;head&#62;はもともとページのタイトルとか付けるタグ
    • 詳しくは「next/head | Next.js」参照
    • lang属性を追加するなど、&#60;html&#62;タグをカスタマイズしたい場合は、pages/_document.jsファイルを作成することで可能
    • 詳しくは「Advanced Features: Custom Document | Next.js」参照

Third-Party JavaScript

  • Scriptコンポーネント
    • 分析、広告、カスタマーサポートウィジェットなど、ゼロから記述する必要のない新しい機能をサイトに導入するために使用
    • &#60;script&#62;タグの代わりにこのコンポーネントを使うことができる
    • &#60;script&#62;タグを使うと、同じページで取得される他のJavaScriptコードに対して、いつロードされるかを明確に把握することができなくなる
  • Scriptコンポーネントにすると、制御が可能なように拡張することができる
    • strategy="lazyOnload"により遅延読み込みが可能
    • onLoadにイベントハンドラを設定することで、読み込み後の処理を指定可能

CSSスタイリング

CSS Modulesの使用方法

  • CSS Modulesを使用することでユニークなクラス名が自動生成し、名前衝突を避けてCSSの利用が可能
  • CSS Modulesを使用するためには、.module.cssというsuffixを持つファイルを作成し、そこにCSSを記述する
  • Reactコンポーネント特有のスタイルであれば、同じ階層に.module.cssファイルを作成する
    • components/layout.jsに対して、components/layout.module.cssを作成
  • 以下のようにインポートして使用
import styles from './layout.module.css';

export default function Layout({ children }) {
  return <div className={styles.container}>{children}</div>;
}
  • またNext.jsのコード分割機能は、CSS Modulesの.module.cssファイルに対しても機能する
  • これにより、各ページで読み込まれるCSSを最小限にし、バンドルサイズを小さくることが可能
  • 詳細は以下も参考
  • ちなみにcomponentsフォルダは、共通で使用するReactコンポーネントの置き場として採用されることが多い

グローバルスタイル

  • グローバルスタイルとは
    • CSS Modulesは、コンポーネントレベルのスタイルに便利
    • その反面、あるCSSをすべてのページで読み込むようにしたい場合はグローバルスタイルを使用する
  • グローバルスタイルの使用方法
    • pages/_app.jsというファイルを作成
    • この_app.jsexport defaultは、アプリケーション内のすべてのページをラップするトップレベルのReactコンポーネントとなっている
    • このReactコンポーネントを使用して、ページ間を移動するときに状態を保持したり、ここで行うようなグローバルスタイルを記述することが可能
    • _app.jsの詳細は「Advanced Features: Custom App | Next.js」を参照
    • _app.jsを追加した場合は開発サーバを再起動する必要がある
    • _app.jsからのみグローバルなCSSファイルのインポートが可能で、それ以外の場所でグローバルCSSをインポートすることはできない
    • 詳細は「https://nextjs.org/docs/basic-features/built-in-css-support#adding-a-global-stylesheet」参照
    • グローバルなCSSファイルはどこにでも置けて、どんな名前でも使用可能
  • 以下が最終的なpages/_app.jsの記載となる
// `pages/_app.js`
import '../styles/global.css';

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

このサンプルプロジェクトですが、`globals.css`ではなく`global.css`であることに注意が必要です(`globals.css`はもともとプロジェクト内にありますが汗)

Polishing Layout

この章はレイアウトの磨き上げの章で、様々な修正が入っている

Reactコンポーネントに変数名だけのPropsを記述すると、boolean扱いとなる

Styling Tips

プリレンダリングとデータフェッチ

プリレンダリング

  • プリレンダリングはNext.jsで最も重要な概念の一つ
  • プリレンダリングは、クライアントサイドのJavaScriptですべてを行うのではなく、各ページのHTMLをあらかじめ生成することを意味
  • 生成された各HTMLは、そのページに必要な最小限のJavaScriptコードと関連付けられる
  • ブラウザでページが読み込まれると、そのJavaScriptコードが実行され、ページが完全にインタラクティブに(これをハイドレーションという)
  • アプリケーションがプレーンなReactである場合、プリレンダリングがないため、JavaScriptを無効にするとアプリが表示されない

プリレンダリングの2つの形態

  • 静的サイト生成(SSG)とサーバーサイドレンダリング(SSR)
  • どちらを採用するか
    • 基本的には、静的サイト生成を可能な限り使用することが推奨
    • 静的サイト生成とした場合、CDNで提供することができるので、リクエストごとにサーバー側でレンダリングするよりもはるかに高速に
    • 使い分けとしては、ユーザリクエストに先行してページをプリレンダリングすることが可能ならば、静的サイト生成を選択すべき
    • リクエストの都度、ページが変化する場合はサーバーサイドレンダリングを使用する

まとめると以下のような形かと思います。
・CSR : 更新が頻繁なケース(カウンタなどの簡単なUI操作などを想定)
・SSR : リクエストごとに更新が必要なケース
・SSG : それ以外

データありの静的サイト生成とデータなしの静的サイト生成

  • データありの静的サイト生成
    • ページによっては、まず外部データを取得しないとHTMLをレンダリングできない場合がある
    • ビルド時にファイルシステムへのアクセス、外部APIの取得、データベースへの問い合わせが必要な場合がある
    • Next.jsはこのようなデータありの静的サイト生成もサポート
  • ページ単位のReactコンポーネントのexportに、getStaticPropsを書くことで実現する

ブログデータの準備

  • この章は、サンプルプログラム向けのデータ準備について説明
    • gray-matterでmarkdownを読み込み、それをデータとして使用
    • fsは、ファイルシステムからファイルを読み込むためのNode.jsモジュール
    • pathは、ファイルパスを操作するためのNode.jsモジュール
    • matterは、各マークダウンファイルのメタデータを解析するためのライブラリ
    • サンプルではlibフォルダを作成して、ファイルからの読み込み処理を実装しているが、任意のフォルダ名が使用可能で、こういったモジュールはlibutilsを使うケースが多い

getStaticPropsの実装

  • getStaticPropsで、return { props: { ... } }とすれば、コンポーネント側のPropsでデータを受け取ることが可能
export async function getStaticProps() {
  // ...

  return {
    props: {
      data
    },
  };
}

export default function Home ({ data }) {
  // ...
}

getStaticPropsの詳細

  • getStaticPropsでは、外部APIからの取得やデータベースへの問い合わせも可能
    • これはgetStaticPropsがサーバーサイドでのみ実行されるため
    • つまり、ブラウザに送信されることなく、直接データベースへの問い合わせのようなコードを書くことが可能
  • 開発環境と本番環境の比較
    • 開発サーバー(npm run devなどで起動する)では、リクエストごとにgetStaticPropsが実行される
    • 本番環境では、getStaticPropsはビルド時に実行される
    • この本番環境での動作はgetStaticPathsが返すフォールバックキーを使って強化することが可能
    • 詳細「https://nextjs.org/docs/api-reference/data-fetching/get-static-paths#fallback-false
    • ビルド時に実行することを前提としているため、クエリパラメータやHTTPヘッダなど、リクエスト時にしか利用できないデータを利用することはできない
  • pages/内でのみ許可される
    • getStaticPropsは、pages/配下からしか書き出せず、pages/以外にあるファイルに書くことはできません
    • この制限の理由の1つは、Reactはページがレンダリングされる前に必要なデータをすべて持っている必要があるため
  • リクエスト時にデータを取得する必要がある場合
    • getStaticPropsは構築時に一度だけ行われるため、頻繁に更新されるデータやユーザーのリクエストごとに変更されるデータには適さない
    • この場合はサーバーサイドレンダリング、またはクライアントサイドレンダリングを使用する

リクエスト時のデータ取得

  • サーバーサイドレンダリングを使用する方法
    • getStaticPropsの代わりに、getServerSidePropsを使う
    • getServerSidePropsはリクエスト固有のパラメータをcontextで渡すことが可能
export async function getServerSideProps(context) {
  return {
    props: {
      // props for your component
    },
  };
}
  • クライアントサイドレンダリングを使う方法もある
    • データをプリレンダリングする必要がない場合はこちらの戦略もあり
    • 外部データを必要としないページの部分を静的に生成(プリレンダリング)する
    • ページが読み込まれたら、JavaScriptを使ってクライアントから外部データを取得し、残りの部分に入力する
    • このアプローチは、たとえばユーザーのダッシュボードページで有効
    • ダッシュボードはプライベートなユーザー専用ページなので、SEO対策も無関係で、ページもプリレンダリングが不要
    • データは頻繁に更新されるため、リクエストタイムのデータフェッチが必要
  • SWR
    • Next.jsの開発チームではSWRというデータフェッチ用のReactフックを作成している
    • https://swr.vercel.app/
    • クライアントサイドでデータをフェッチする場合は、これを強く推奨
    • キャッシュ、再バリデーション、フォーカストラッキング、インターバルでのリフェッチなど、さまざまな処理を行うことが可能
import useSWR from 'swr';

function Profile() {
  const { data, error } = useSWR('/api/user', fetch);

  if (error) return <div>failed to load</div>;
  if (!data) return <div>loading...</div>;
  return <div>hello {data.name}!</div>;
}

SWR以外の選択肢として、react-queryなどもあるので、ここはプロジェクトに応じて好みのものを使えば良いと考えられます。

ダイナミックルート

  • 以下について学ぶ
    • getStaticPathsを使用して、動的なルートを持つページを静的に生成する方法
    • 各ブログ記事のデータを取得するgetStaticPropsの書き方
    • remarkを使ってマークダウンをレンダリングする方法
    • 動的経路を持つページへのリンク方法
    • ダイナミックルートに関するTIPS

ページパスが外部データに依存するケース

  • Next.jsでは、外部データに依存するパスを持つページを静的に生成することができる
  • 実現するステップは以下
    • pages/posts/配下に[id].jsというページ(ファイル)を作成
    • [id].jsにはこれまでと同様にページのコンポーネントを記述
    • 加えてその[id].js内でgetStaticPathsをエクスポートし、[id]に入る可能性のあるリストをオブジェクトで返す必要がある
    • getStaticPropsでは、これらをparamsから取得できる

getStaticPathsの実装

  • 以下のような形式となる
    • getStaticPathsは単なる文字列のリストではなく、オブジェクトの配列である必要がある
    • またその各要素のオブジェクトはparamsをキーに持ち、paramsに更にidをキーに持つオブジェクトを含む必要がある
    • getStaticPropsでは、返したオブジェクトの配列の各要素にparams.idという形でアクセス可能
// pages/posts/[id].js

export default function Post() {
  return <Layout>...</Layout>;
}

export async function getStaticPaths() {
  // Return a list of possible value for id
  // ex.
  //   const paths = [ { params: { id: 'ssg-ssr' } }, { params: { id: 'pre-rendering' } } ]
  //   return {paths, fallback: false}
}

export async function getStaticProps({ params }) {
  // Fetch necessary data for the blog post using params.id
}

getStaticPropsの実装

特に再掲するようなポイントがないため詳細を略します。

マークダウンのレンダリング

Postページの磨き上げ

細かいコードや、CSSスタイルやレイアウト調整については詳細は割愛します。

Indexページの磨き上げ

こちらも特に挙げるようなポイントがないため詳細を略します。

ダイナミックルートの詳細

  • getStaticPathsはgetStaticPropsと同様に任意のデータソースからフェッチが可能
  • 開発環境と本番環境の比較
    • 開発サーバー(npm run devなどで起動する)では、リクエストごとにgetStaticPathsが実行される
    • 本番環境では、getStaticPathsはビルド時に実行される
  • fallbackの詳細
    • 今回はfallback: falseとしたため、getStaticPathsにないパスにアクセスすると404となる
    • fallback: trueと設定すると、404とならずにページのfallbackバージョンを提供する
    • fallback: blockingと設定すると、新しいパスはgetStaticPropsでサーバーサイドレンダリングされ、将来のリクエストのためにキャッシュされる
    • 詳しくは「https://nextjs.org/docs/api-reference/data-fetching/get-static-paths#fallback-false」を参照
  • キャッチオールルート
    • ダイナミックルートは、括弧の中に3つのドット(たとえば今回の場合はpages/posts/[...id].js)を追加することで、すべてのパスをキャッチするように拡張可能
    • この場合、getStaticPathsが返す形式も多少変更が必要
    • キャッチオールルートの詳細は「https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes」も参照
export async function getStaticPaths() {
  return [
    {
      params: {
        // Statically Generates /posts/a/b/c
        id: ['a', 'b', 'c'],
      },
    },
    //...
  ];
}

export async function getStaticProps({ params }) {
  // params.id will be like ['a', 'b', 'c']
}

API Routes

  • Next.jsはAPI Routesをサポートしており、Node.jsのサーバーレス関数としてAPIエンドポイントを簡単に作成することが可能

APIルーティングの作成

  • APIルートは、Next.jsアプリの中にAPIエンドポイントを作成できる
  • pages/api/ディレクトリ内に、以下のような関数を作ることで実現
// pages/api/hello.js

export default function handler(req, res) {
  res.status(200).json({ text: 'Hello' });
}

APIルート詳細

  • getStaticPropsやgetStaticPathsからAPI Routesにfetchするべきではない
  • API Routesの優れたユースケースの一つは、フォーム入力の処理
    • API RoutesにPOSTリクエストを送信させることが可能
    • そして、それを直接データベースに保存するコードを書くことも可能
    • API Routeのコードはクライアント側にバンドルに含まれないので、安全にサーバーサイドのコードを書くことが可能
  • プレビューモード
    • 静的サイト生成は、ページがヘッドレスCMSからデータをフェッチする場合に便利です。
    • しかしヘッドレスCMSで原稿を書いていて、その原稿をすぐにページでプレビューしたいときには、理想的ではない
    • このような問題を解決するために、Next.jsにはAPI Routesを利用したプレビューモードという機能がある
    • 詳細は「Advanced Features: Preview Mode | Next.js」を参照
  • 動的なAPIルーティング

Next.jsアプリをデプロイする

  • 学ぶこと
    • VercelにNext.jsをデプロイする方法
    • DPSワークフロー(Develop, Preview, and Ship)
    • Next.jsアプリを独自のホスティングプロバイダーにデプロイする方法

こちらの章はここは読むだけにとどめています。

Push to GitHub

特に挙げるようなポイントがないため詳細を略します。

Deploy to Vercel

特に挙げるようなポイントがないため詳細を略します。

Next.jsとVercel

  • VercelはNext.jsのクリエイターによって作られており、Next.jsを第一級にサポート
  • Next.jsアプリをVercelにデプロイすると、デフォルトで以下のような機能が使用できる
    • 静的サイト生成やアセット(JS、CSS、画像、フォントなど)を使用するページは、自動的にVercel Edge Networkから提供され、高速に配信できる
    • サーバーサイドレンダリングとAPI Routesを使用するページは、自動的に分離されたServerless Functionsとなり、ページのレンダリングやAPIリクエストは無限に拡張される
  • カスタムドメインの使用
  • 環境変数
  • HTTPS対応
    • HTTPSはデフォルトで有効になっており(カスタムドメインを含む)、余分な設定は不要で、SSL証明書は自動更新される
  • DPS(Develop, Preview, and Ship)が推奨のワークフロー
  • Develop
    • Next.jsでコードを書き、Next.jsの開発サーバーを起動して、そのホットリロード機能を利用
  • Preview
    • GitHub上のブランチに変更をプッシュし、Vercelがプレビューデプロイメントを作成し、URL経由で利用
    • このプレビューURLを他の人と共有し、フィードバックを得ることが可能
    • コードレビューに加え、デプロイメントプレビューを行うことが可能
  • Ship
    • 本番に出荷するために、プルリクエストをmainにマージする

その他のホスティング

  • 独自のホスティングの手順
    • ホスティングプロバイダーでnpm run buildを行う
    • ビルド後npm run startでサーバーを起動

次のおすすめのステップ

まとめ

いかがでしたでしょうか。本記事がNext.jsを使われる方の参考やきっかけになれば幸いです。