React, Next.js何を使うべきか: SSR, CSRでの問題, SSG, ISRについて、Next.jsの紹介、結論
前置き
以前の記事では主に歴史的な観点で今までなぜReactを使っているかについてお話しました。
結論的には、約20年前に頻繁に利用したJSP(JAVA),PHPと言うSSR技術からReactと言うCSR技術に転換すると、インタレクティブ機能実装、
画像DOMと言う新たな概念によって綺麗なデータ/UIの切り替え
コンポーネント単位でUIを開発することで、再利用性向上が可能からです。
ついでにレンダリングもChrome(ブラウザー)側で実装するので
その分、サーバー代も減少できるでしょう。
ただ、CSRを利用する場合は、
SEO(Search Engine Optimization)が上手くできないデメリットが存在します。
今回の記事ではReactのデメリットをNext.jsではどのように改善しているかを主に技術的な観点で話した後、最後に結論に述べたいと思っています。
SSR, CSRでの問題
以前の記事を読んでいただいた方ならご存知かと思いますが、
現時点のCSRの致命的なデメリットはSEOが上手くできないところになります。
もう少し具体的に説明すると
<!DOCTYPE html>
<html lang="en">
<head>
<!-- 基本的なメタタグ -->
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!-- SEO用メタタグ -->
<meta name="description" content="Your page description here" />
<meta name="keywords" content="React, JavaScript, Web Development, Frontend, SEO" />
<meta name="author" content="Your Name or Company Name" />
<!-- Open Graph (Facebook, LinkedIn など) -->
<meta property="og:title" content="Your Page Title" />
<meta property="og:description" content="Your page description here" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://yourdomain.com" />
<meta property="og:image" content="https://yourdomain.com/og-image.jpg" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Your Page Title" />
<meta name="twitter:description" content="Your page description here" />
<meta name="twitter:image" content="https://yourdomain.com/twitter-image.jpg" />
<meta name="twitter:site" content="@yourTwitterHandle" />
<meta name="twitter:creator" content="@yourTwitterHandle" />
<title>Your Page Title</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<div id="root"></div>
<script src="/bundle.js"></script>
</body>
</html>
実際、上のコード通り、
一般的なCSR環境で一つのindex.htmlファイルを持っておりますが、
正確にこのページにのみ、SEOが可能になります。
つまり、一般的なCSRはメインページのみSEOが可能になります。
前回の記事で「SEOがうまくいかない」と書いたのは、まさにこの点が原因です。
この状況で何の技術を選択すれば良いでしょうか。
しかし、何を選択してもデメリットが存在するのでジレンマに陥ることになります。
SSRを選択すれば、綺麗なインタレクティブなUI挙動が、
CSRを選択すれば、SEOの限界が存在と言うジレンマです。
もちろん、両方選択する方法もありますが、 保守観点ではあまり良いではないでしょう。
ならこの方法はいかがでしょうか。
各ページごとにindex.htmlがあるようにページを作るすることです。
つまり、各ページごとに、クローラーが読み込みしやすいような
静的なページを作ることはどうでしょうか。
SSG(Static Site Generation), ISR(Incremental Static Regeneration)について
Next.jsでは静的ページを作る二つの方法を提供しています。
もちろん、SSRを説明した時にお話いただいた通り、
リクエストがある時にhtmlを作成して送信するのはサーバー観点で、
正確にはサーバー運用コスト観点ではあまりにも効率が良くないです。
この問題のソリューションとして、Next.jsでは二つのソリューションを提供しています。
これが上の画像で説明しているStatic Site GenerationとIncremental Static Regenerationと言うものです。
ご覧の通り、根本的には両方静的ページを作成して、クライアントからリクエストなどがある場合、
静的ページをそのまま送信する方法になります。
各SSGとISRは以下の特徴を持っています。
SSG(Static Site Generation)
- ビルド時点の情報を取り込んでいるHTMLを作成
- クライアント(ブラウザー、他のサーバーなど)からリクエストある場合、ビルド時点のHTMLを送信
ISR(Incremental Static Regeneration)
- 最初はビルド時点の情報を取り込んでいるHTMLを作成
- 設定した時間以後、リクエストがある場合、新たなHTMLを作成
- クライアント(ブラウザー、他のサーバーなど)からリクエストある場合、新たなHTMLを送信
SSGとISRはビルド時点にHTMLファイルを作成し、リクエストがある場合、ファイルをそのまま送信する観点では同様ですが、
ISRの場合は、ビルド更新時間があり、SSGよりは最新情報を提供できると言うメリットがあります。
もちろん、静的ページを作成するので、一般的な認識通り、
当然CDN(Content Delivery Network)との組み合わせも良いです。
このソリューションによって、
クライアントからリクエストある時に、HTMLファイルを新たに作成して送信するSSRよりは、サーバーのリソース管理とリクエストスピードと言うサーバー側の懸念を大分解決できます。
さらに、各ページの情報をしっかり取り込んでいる静的ページ、
つまり、ページ別SEOが可能になりますので、SSRのクリティカルな欠点をある程度解決できると考えても良いでしょう。
Next.jsの紹介
せっかくなので私の印象に残っている特徴を三つだけ紹介したいと思っています。
ディレクトリ構成、use clientキーワード、Hydrationになります。
ディレクトリ構成
Reactはいわゆる、JavaScriptライブラリーのため、自由にディレクトリ構成が可能です。
しかし、Next.jsではファイル基盤ルーティング(file-based routing)を採用しているため、
ある程度構成が決まっています。
そのため、Next.jsはフレームワークに近いと考えても過言ではないでしょう。
このライブラリーとフレームワークの違いについても機会があればまたお話したいと考えています。
メインページ(site)、チャンネルページ(channel)、ライブラリーページ(library)、プレーリストページ(playlist)が存在しているウェブアプリであれば、以下の構成になります。
src/
└── app/
├── (site)/
│ ├── components/
│ ├── error.tsx
│ ├── layout.tsx
│ ├── loading.tsx
│ └── page.tsx
├── channel/
│ └── [id]/
│ ├── error.tsx
│ ├── layout.tsx
│ ├── loading.tsx
│ └── page.tsx
├── explore/
│ ├── error.tsx
│ ├── layout.tsx
│ ├── loading.tsx
│ └── page.tsx
├── library/
│ ├── error.tsx
│ ├── layout.tsx
│ ├── loading.tsx
│ └── page.tsx
└── playlist/
├── error.tsx
├── layout.tsx
├── loading.tsx
└── page.tsx
ご覧の通り、エラーページに対するファイル、レイアウトに対するファイル、ローディングページに対するページ、該当するルーティングに対するページファイル見たいにディレクトリだけではなく、
ファイル名もほぼ決まっていることが見えます。
use clientキーワード
Next.jsはデフォルトとしてSSRと認識します。
そのため、該当するページをSSRではなく、
CSRで稼働するようにしたい場合はファイルにキーワードを記入する必要があります。
これが、use clientと言うキーワードです。
ただ、その中でuse clientと言うキーワードで存在するファイルに関しては
SSRでの実行はスキップし、全ての処理をCSRで行います。
上のディレクトリ構成で、ローディングページとかエラーページは特にSSRで実行する理由がないので
例としてローディングページファイルのコードを確認して見ましょう。
"use client"
import LoadingBar from "@/components/LoadingBar";
import React from "react";
const loading = () => {
return (
<LoadingBar />
);
};
export default loading;
上のコード通り、上段にはuse client
キーワードがあるのを確認できます。
結果的には、このローディングページは全ての処理をCSRで実行します。
Hydrationについて
上の説明した通り、Next.jsはSSR,CSRが両方存在するハイブリットになります。
SSR・CSRと言う構成が両方存在しているので二つを連結する作業、
つまり総合する作業が必要になります。
この作業がHydrationと言う技術になります。
上のuse client
キーワードが存在するファイルは全てCSRで実行する話をする時に
気づいた方もいますと思いますが、
Next.jsはデフォルトとしてSSRで実行します。
SSRがデフォルトの状況で、どうやってCSRをロードするかを解決しなければなりません。
これに対するNext.jsでのソリューションはHydrationになります。
この、Hydrationをそのまま翻訳すると水分補給になります。
例えば、以下のみたいに簡単なページが存在していると仮定してみましょう。
画像のページの特徴として、下段にカード系スライダーコンポーネントが存在します。
このページは矢印ボタンを押下すると前・次のカードに切り替える機能になります。
1. サーバーからHTMLを取得・表示する(SSR)
最初には、まずサーバーは必要なデータを取り込んでHTMLファイルを作成・送信、
ブラウザーはサーバーから静的ページを取得して先に、レンダリング(SSR)を行います。
このプロセスが完了すると上の画像通り、
onClickが取り込んでいるカード系スライダーコンポーネント以外には、レンダリングが完了している状況だと考えらえます。
該当するルーティングにloading.tsx
があればそのまま表示、
なければ何も表示されないと考えられます。
2. HTML上に、クライアントコンポーネントをレンダリング(Hydration、水分補給)する
次は、本格的にHydrationを実施します。
CSRと同様に、まずはCSRレンダリングに必要なJavaScriptのBundleを外部から取得、
その後、クライアントコンポーネントのレンダリングを行います。
もちろん、現在はデバイスのスペックが非常に良いので
実際、ボタンを押下しても動作しないことはほぼないと思います。
このプロセスまで終了したら、以下のページが表示されると思います。
結論的には、簡単に言うとNext.jsでは、SSRとCSRが共存していると考えても良いかと思います。
ただ、もうご存知の方もいらっしゃると思いますが、
この方法は必然的にレンダリングを2回実行する必要があります。
ReactとNext.js何を選択するべきが
詳細などころまでは議論してはないですが、
ここまででも十分今回の疑問について回答ができるかと思います。
技術的な面だけ見ると、該当するプロダクトがどの程度SEOについて
こだわりがあるかによって決めれると思います。
ただ、実はこの結論は完璧な回答になったとは言えません。
なぜかと言うと、現実的な制約とか問題を議論してなかったためです。
つもり、まずはチームだったらチーム内で、会社だったら会社内で
稼働できるリソースをまず把握しなければなりません。
そのため、実は技術スタックの選択する前に、先行としてチームビルディングが必要です。
このビルディングは新しいチームを作ると言う意味もありますが、
既存のチームの整備と言う意味もあります。
例えば、Reactに慣れているメンバーが多い時、プロダクトをNext.jsに転換を検討しているであれば、
Next.jsはReact基盤のフレームワークのため、そこまでラーニングカブはないかもしれません。
逆に、バニラJavaScriptに慣れているメンバーが多い場合は、
Reactのラーニングカブが追加されるので、その分リスクも高くなります。
ただ、向上心が強くて意欲があるエンジニアがチームで多いであれば、
チーム全体的に成長できる機会にもなるので、肯定的に検討するのも可能かと思います。
もちろん、この判断は各リーダーが決定しなければならないので、リーダーの判断力が大事かと思います。
終わり
結論的には、現実的な制約や課題を考えた上で、技術を選択することが重要だと思います。
この前提を満足しているんであれば、SEOがどの程度重要かによって決めれば良いでしょう。
ただ、Next.jsを選択したとしても、Next.jsに十分に慣れてないチームであれば、
高い生産性をすぐに期待するのは難しいと思います。
そのため、先行として社内リソースを把握してチームビルディングをする必要があります。
なので、実は現実的な問題がないかまずは把握した上で、
技術選択について議論するべきですので元々順番が間違っているではないかと思います。
あと、Apple社のパラダイムシフト件て解析できる通り、
実はエンジニアが思うほど技術は大事ではないかもしれません。
そのため、エンジニアが技術だけ考えてしまうと現実を見逃す方が多いではないでしょうか。
この観点を考えて良いエンジニアとは何かについて改めて考えて見るのも良いかと思います。
記事も長くになったので、今回はこのあたりでこの記事も締めたいと思います。
最後まで読んでくださってありがとうございました!
Discussion