Next.js vs CRA

2021/8/3

thumbnail

はじめに

業務で Next.js を触る機会があったのでキャッチアップを兼ねて本ブログを作成しました。その過程で学んだ Next.js についての知見を本記事にまとめました。

なぜ CRA(Create React App)と比較するのか

React の代表的なフレームワークである CRA(Create React App)と比較しながら進めていきます。他にも有名な React のフレークワークとして Gatsby、Razzle、UmiJS 等がありますが、npm のダウンロード数が多い CRA と比較していきます。CRA より Gatsby の方がダウンロード数は多いですが、SSG 専用のフレームワークなので比較対象とはしませんでした。

比較する観点

Next.js と CRA を次の観点で比較していきます。

  • レンダリング
  • ルーティング
  • モジュールバンドーラーのカスタマイズ
  • 表示速度
  • デプロイ

レンダリングの比較

レンダリングを比較してきます。CRA は CSR(Client Side Rendering)のみをサポートしていますが、Next.js では CSR、SSR(Server Side Rendering)、SSG(Static Side Generation)をサポートしています。

CSR のみでもサービスを作ることはできますが、次の理由によりアプリケーションが肥大化するにつれてページの表示スピードが落ちてユーザ体験と SEO に悪影響が出る可能性があります。

  • ページを表示毎に HTML をクライアント側で生成するためにページ表示されるまでの時間がクライアント環境に大きく依存する。
  • アプリケーションが大きくなるにつれてバンドルサイズが大きくなります。そのため初回のページ表示時に画面が表示されるまでの時間が遅くなります。モジュールバンドラーでコード分割をして明示的に遅延読み込みを行い対策は可能です。

検索エンジンにインデックスさせないチャットツール、タスク管理ツールのようなページであれば、SEO 観点で表示速度を考慮する必要はありません。そのためユーザが許容できるページ速度であれば問題ありません。ところが検索エンジンにインデックスさせる SNS、EC、メディア等のようなサービスではできるだけページの表示スピードを可能な限り速くして SEO で有利になるようにしておくべきです。

SSR ではサーバ環境で HTML をレンダリング、SSG ではビルド環境で HTML をレンダリグします。そのためクライアント環境によらず安定したページ表示スピードを出すことができます。SSR で安定した表示スピードを実現するためにはアクセスの増減に伴いサーバをスケールする必要があります。

ダッシュボード系のサービスであってもサービスの紹介、FAQ、ドキュメント等はは検索エンジンにインデックスさせる必要があるため、部分的に SSG を使いたくなります。

Next.js を使うと、サービスの紹介ページでは SSG で HTML をビルド時に生成しておき、ログイン後のページのユーザ毎に HTML が異なるページでは CSR で HTML を生成することができます。

ルーティングの比較

ルーティングを比較していきます。 Next.js はルーティングを標準でサポートしておりディレクトリ構造でルーティングのパスを定義します。CRA は React のアプリケーションにおける開発、テスト、ビルドを解決するためのフレームワークなのでルーティングは提供していません。そのため CRA ではルーティングを定義するためにはサードパーティのライブラリを使用します。

CRA の開発で一般的に使われてるreact-router-domを使うと次のようにルーティングを定義るすことができます。

import React, { lazy } from 'react' import { Switch, Route } from 'react-router-dom' import Home from './routes/Home' import About from './routes/About' const App = () => ( <Router> <Switch> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Switch> </Router> )

Next.js でがファイルシステムのパスでルーティングのパスを定義します。プロジェクト生成時に作られた pages ディレクトリ以下にファイルを追加すると 1 つのファイルが 1 つのページに対応します。

pages ディレクトリにファイルを作成してコンポーネントをデフォルトでイクスポートすることでルーティングを定義することができます。

// pages/index.js const Home = () => { return ( <div> <h1>Home page</h1> <p>Welcome!</p> </div> ) } export default Blogs

ページ遷移は Next.js が提供している Link コンポーネントを使うと Link コンポーネントが画面に表示されたらリンク先のページをプリフェッチするため、ページ遷移 A が速くなります。

import Link from 'next/link' const Home = () => { return ( <div> <nav> <Link href="/"> <a>Home</a> </Link> <Link href="/about-us"> <a>About us</a> </Link> </nav> </div> ) } export default Home

CRA と Next.js のルーティングでできることはほぼ変わりません。Next.js 本体がルーティング機能を提供しており、かつファイルシステムベースのルーティングなので Next.js を使用しているプロジェクトであれば強制的にディレクトリ構成が統一されるので、ディレクトリ構成を決める際の議論がなくなります。新たにプロジェクトに参画したメンバーがキャッチアップをしやすいというメリットもあります。

モジュールバンドーラーのカスタマイズの比較

モジュールバンドーラーのカスタマイズを比較していきます。CRA と Next.js 共にスタイルシート、画像、動画のようなメジャーなコンテンツであればデフォルトの設定でバンドル可能です。しかしブラウザのサポート環境を変更したいなどカスタマイズする必要が出てくる場合があります。

CRA と Next.js 共にモジュールバンドーラーのカスタマイズはできますが、CRA は標準でサポートしていないのでサードパーティのパッケージを使う必要があります。

CRA はモジュールバンドラーを隠蔽しているためモジュールバンドラーをカスタマイズすることができません。webpack の設定は react-scripts 内のコード上にあります。

CRA の yarn eject コマンドを使うと react-scripts 内の設定コードをプロジェクトにコピーすることができます。開発サーバ、ビルド、テスト用のスクリプトがコピーされます。このコピーされたものをカスタマイズすることが可能です。webpack のビルド設定は scripts/build.js にコピーされています。package.json はコピーしたスクリプトを実行するように変更されます。

// package.json { // before yarn eject "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, // after yarn eject "scripts": { "start": "node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js" } }

yarn eject は不可逆な操作なので一度実行すると元には戻せません。そのため CRA が提供するモジュールバンドラーメンテナンスの恩恵を受けられなくなります。

外部パッケージを使うことによって yarn ejectすることなく CRA の設定をオーバライドすることが可能です。react-app-rewired は CRA の設定をオーバライドすることが可能なパッケージの一つです。

react-app-rewired では config-overrides.js に設定を追加して、package.json を書き換えてるだけでカスタマイズ可能です。

// config-overrides.js module.exports = function override(config, env) { //do stuff with the webpack config... return config }
// package.json { "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject" } }

Next.js はモジュールバンドラーのカスタマイズをサポートしています。 next.config.js に設定を追加することでカスタマイズすることができます。

// next.config.js module.exports = { webpack: (config, options) => { config.module.rules.push({ test: /\.mdx/, use: [ options.defaultLoaders.babel, { loader: '@mdx-js/loader', options: pluginOptions.options, }, ], }) return config }, }

ページ表示速度の比較

ページ表示速度の比較していきます。CRA は JavaScript を用いてブラウザ環境で DOM を生成しますが、Next.js の場合は可能な限り必要な DOM はビルド時に生成されるので、レンダリング観点でも Next.js の方がページ速度は速くなります。 レンダリング観点以外でも Next.js はページ表示速度を速くするための機能を提供しています。画像の最適化、JavaScript バンドルファイルのコード分割、リンク先ページをプリフェッチなどを提供しています。

Web サイトが表示されるまでの流れを考えながら説明していきます。Web サイトが表示されるまでの大きな流れは次の通りです。

  1. コンテンツ(HTML、CSS、JavaScript) のダウンロード
  2. HTML と CSS を DOM と CSSOM へ変換
  3. JavaScript を実行して DOM と CSSOM に変更を加える
  4. DOM と CSSOM を元に Render Tree を構築
  5. Render Tree を元に要素の位置を計算(Layout を決める)
  6. Layout を元に描画をする

Next.js は 1 のコンテンツのダウンロード時間を短くすることで機能をページ表示速度を速くしています。

Next.js では ダウンロードする JavaScript のデータサイズを小さくするコード分割機能を提供しています。コート分割により各ページで必要な JavaScript のみをダウンロードすることができます。CRA のような SPA はサイト全体で 1 つ HTML ファイルと JavScript ファイルは 1 つです。一方、Next.js ではページごとに HTML ファイルと JavaScript ファイルを生成します。そのため各ページで必要な JavaScript コードのみバンドルされています。

JavaScript はテキストファイルなのでデータサイズが小さいと考える人も多いと思いますが、実際は違います。Wdb Almanacによると JavaScript のデータサイズはデスクトップで 約 472KB、モバイルサイトで 約 430KB になります。HTML と CSS は 80KB 未満に収まるのと比較すると JavaScript のデータサイズが大きいのがわかります。JavaScript ファイルは依存パッケージが多くなる、旧ブラウザに対応のビルドをする等の理由で大きくなります。

アプリケーション開発者側でコード分割の Next.js を設定をする必要はありません。CRA も明示的に設定することで JavaScript のコード分割は可能です。React の lazy 関数を使います。1 つのページに対応するコンポーネントごとにコード分割することができます。

import React, { lazy } from 'react' import { Switch, Route } from 'react-router-dom' // コード分割 const Home = lazy(() => import('./routes/Home')) const About = lazy(() => import('./routes/About')) const App = () => ( <Router> <Switch> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Switch> </Router> )

Next.js は画像に関してもデータサイズを小さくする機能を提供しています。Image コンポーネントを使うとブラウザに応じて適切のフォーマットで画像を返します。PNG と JPG の画像を Image コンポーネントに渡すとクライアントがモダンなブラウザであれば WebP を返します。Chrome、Safari、Firefox、Edge のようなモダンなブラウザは WebP 対応しています。 WebP の公式サイトによると WebP は可逆圧縮の場合においても PNG よりデータサイズが 26%小さくなります。Wdb Almanacによると Web サイトの画像のデータサイズはデスクトップサイトで 982KB、モバイルサイトで 約 877KB です。そのため、約 255KB(982KB x 0.26)のデータサイズを小さくすることができます。

Next.js は Link タグを使うことでユーザがページ遷移する前に予めリンク先のページをダウンロードする機能があります。Link タグが画面内に入ったらリンク先ページをダウンロードします。

import Link from 'next/link' function Home() { return ( <ul> <li> <Link href="/"> <a>Home</a> </Link> </li> <li> <Link href="/about"> <a>About Us</a> </Link> </li> </ul> ) } export default Home

デプロイの比較

デプロイの比較をしていきます。CRA はビルドして出力したコートを Web サーバに配置すればデプロイ完了です。Next.js の場合はデプロイの方法が 2 つあります。1 つはに Node.js サーバを動かす方法、もう一つは静的ファイルとして Web サーバに配置する方法です。

Node.js サーバは Next.js に内包されており、コマンドを呼び出すだけでサーバを立ち上げることができます。

# Node.jsホスティング用にビルド next build # Node.jsサーバを立ち上げる next start

Node.js サーバを動かす場合のホスティング方法は 2 つあります。1 つは自前で Node.js をインストールしたサーバ用意してセルフホスティングする方法、もう一つはVercelAWS Amplifyのようなプラットフォームを利用する方法です。Node.js サーバは負荷の高い SSR と画像の最適化を行うためセルフホスティングする場合はメモリと CPU のリソース管理が重要になります。必要に応じてスケールアウトさせる必要があるためサーバ運用のコストが大きくなります。そのため、プラットフォームを利用するのがおすすめです。

Next.js では next export コマンドを実行すると Node.js サーバホスティング用のビルドを静的ホスティングできるように修正できます。next export で出力されらディレクトリを Web サーバに配置すればデプロイ完了です。

# Node.jsホスティング用にビルド next build # ビルド結果をもとに静的ホスティング用ファイルを出力 next export

next export で出力されたディレクトリを Web サーバに配置すればデプロイ完了です。

まとめ

Next.js は CRA(Create React App)で開発をする際にぶつかる課題を解決するように設計されてる印象を受けました。解決されている課題としては次のようなものがあります。

  • サービス紹介ページ、ドキュメント、FAQ のようなクライアントサイドレンダリングする必要のないページでもクライアントサイドレンダリングになる。結果的にページが表示されるまでに時間がかかり、SEO 観点で不利になる。
  • コード分割を明示的に行わない限り全ての JavaScript コードが 1 つのファイルにバンドルされる。そのためアプリケーションの成長に伴い表示速度が低下する。
  • webpack の設定をカスタマイズしたい場合は CRA(react-scripts)に内包されている設定を eject する必要があり、eject は不可逆な操作である。ただしサードパーティの npm パッケージを使ってオーバライドすることは可能です。
  • 画像の最適化はフレームワーク外で行う必要がある。画像のダウンロードサイズを小さくするために端末とブラウザに応じてサイズと圧縮形式を最適化するためには画像サーバを別途用意する必要がある。画像サーバのオープンソースではthumborがあります。

Next.js を使用するとインフラが Vercel や AWS Amplify のようなプラットフォームに強く依存してしまいますが、それでも様々な提供されており非常に便利なので使う価値があります。Next.js は現在も積極的に開発されているので今後どうなって行くのか楽しみです


author

Koki Kitamura

医療系の会社のアプリケーションエンジニアです。フロントエンド、バックエンド、インフラでそれぞれ新規開発と保守運用の経験があります。OSS活動も行っており、PRを送った経験と送られた経験の両方あります。はてブのテクノロジーの人気エントリーに技術記事が載ったこともあります。