ReactアプリケーションにおけるCSR・SSR・next/linkの挙動の違いについて4つの観点で紹介をします。
CSRはフレームワークを利用していないReactアプリケーション、SSRはNext.jsを利用したReactアプリケーションで実装をします。
next/linkとはNext.jsのルータ機能です。Next.jsのページはPre-renderingがデフォルトですが、next/linkを利用して画面遷移をするとCSRになります。
next/linkを利用したCSRの流れの詳細解説は【Next.js】next/linkの画面遷移(CSR)から画面反映までの流れの図解解説で紹介しています。
素のReactアプリケーションのCSRと、next/linkによるCSRで挙動の違いがあるかを確認するため今回比較対象に加えています。
検証対象をまとめると以下のようになります。
- 素のReactアプリケーションによるCSR
- Next.jsによるSSR
- next/linkによるCSR
reactは17.0.2、nextは11.1.2を利用しています。
目次
検証で利用するサンプルコードについて
今回は『ローカル環境に構築したAPI経由で受け取ったToDoリストを表示するページ』で動作検証をします。CSSについては簡単のため紹介を省略します。
plain react app
import axios from "axios";
import React, { useEffect, useState } from "react";
type TodoItem = {
id: number;
content: string;
completed: boolean;
};
const TodoApp: React.FC = () => {
const [todos, setTodos] = useState([] as TodoItem[]);
useEffect(() => {
const getTodoRequest = async () => {
const response = await axios.get("http://localhost:4000/todos");
const todos = response.data;
return todos;
};
getTodoRequest().then((todos) => setTodos(todos as TodoItem[]));
}, []);
return (
<>
<section>
<h2>Todo List</h2>
<ul>
{todos.map(({ id, content }) => (
<li key={id}>
<a href={`/todos/${id}`}>{content}</a>
</li>
))}
</ul>
</section>
</>
);
};
export default TodoApp;
react app with next.js
import type { NextPage } from "next";
import axios from "axios";
type TodoItem = {
id: number;
content: string;
completed: boolean;
};
type PageProps = {
todos: TodoItem[];
};
const TodoList: NextPage<PageProps> = ({ todos }) => {
return (
<>
<section>
<h2>TodoList</h2>
<ul>
{todos.map(({ id, content }) => (
<li key={id}>
<a href={`/todos/${id}`}>{content}</a>
</li>
))}
</ul>
</section>
</>
);
};
export async function getServerSideProps() {
const response = await axios.get("http://localhost:4000/todos");
const todos = response.data;
return {
props: {
todos,
},
};
}
export default TodoList;
検証方法について
CSRの検証はreact-scripts start
、SSRの検証はnext dev
でReactアプリケーションを起動します。
next/linkの検証は以下のように、<Link>
コンポーネントを経由することで画面にアクセスして確認します。
import type { NextPage } from "next";
import Link from "next/link";
const Home: NextPage = () => {
return (
<>
<Link href={`/todos`}>
<a>TodoList</a>
</Link>
</>
);
};
export default Home;
挙動の比較結果
今回検証した項目は以下の通りです。
- リクエスト・レスポンスの内容
- JavaScriptを無効にしたときの挙動
- ブラウザ上でのみ実行可能な機能の利用可否
- レンダリング時のログ出力の場所
以下ではそれぞれの項目の結果について紹介します。
リクエスト・レスポンスの内容
Chrome DevToolsのNetworkタブを利用してリクエストとレスポンスを確認します。
CSRの場合
JavaScriptの組み込まれたHTMLが返ってきます。
HTMLのPreviewはYou need to enable JavaScript to run this app.
という文字列のみで、実際の画面で表示されているUIは構築されません。
SSRの場合
実際の画面で表示されているUIを構築したHTMLが返ってきます。
このことから、サーバサイドですでにHTMLの作成がされていることがわかります。
next/linkの場合
JavaScriptとJSONが返ってきます。
Next.jsではnext/linkを利用してSSRのページに画面遷移する場合、CSRをする際に必要なPropsがgetServerSideProps()によってJSON形式でクライアントサイドに渡されます。
JavaScriptを無効にしたときの挙動
JavaScriptを無効にする方法はDisable JavaScriptをご覧になってください。
CSRの場合
You need to enable JavaScript to run this app.
という文字列が表示され、UIが全く構築されません。
SSRの場合
Next.js『Pre-rendering and Data Fetching#Pre-rendering』にも記載されている通り、JavaScriptを無効にするとPre-renderingのページにCSSが反映されません。
ただし、本番モード(next build && next start
)で起動した場合は事前にビルドしたCSSを利用できるため、以下のようにJavaScriptを無効にした状態でもCSSが適用されます。
next/linkの場合
JavaScriptを無効にするとJSONとJavaScriptの代わりにHTMLがレスポンスとして返されます。
CSSはレスポンスに含まれていないため、以下のようにCSSが適用されていない画面が表示されます。
ただし、本番モード(next build && next start
)で起動した場合はCSSが適用された画面になります。
つまり今回のサンプルコードにおいては、JavaScriptを無効にしたときのnext/linkの挙動はSSRのそれと同じといえます。
ブラウザ上でのみ実行可能な機能の利用可否
ブラウザ上でのみ実行可能な機能の例は以下の通りです。
- Windowの参照
- Cookieの参照
- localStrageの参照
- sessionStorageの参照
今回は以下のように警告ダイアログ(window.alert()
)をレンダリング時の処理に加えることで利用可否を確認します。
plain react app
const TodoApp: React.FC = () => {
window.alert("Hello world!");
// 略
react app with next.js
const TodoList: NextPage<PageProps> = ({ todos }) => {
window.alert("Hello world!");
// 略
CSRの場合
警告ダイアログが正常に表示されます。
SSRの場合
ReferenceError: window is not defined
というサーバエラーになります。
SSRのページでブラウザの機能を利用する場合は副作用で実行します。
SSRにおけるブラウザ機能の利用方法の詳細解説は【Next.js】SSR/SGでブラウザ機能(Cookieなど)を活用する際の注意点で紹介しています。
next/linkの場合
警告ダイアログが正常に表示されます。
レンダリング時のログ出力の場所
以下のようにレンダリングの過程にconsole.log
を仕込み、レンダリングがどの環境で行われているか確認をします。
plain react app
const TodoApp: React.FC = () => {
console.log('rendering');
// 略
react app with next.js
const TodoList: NextPage<PageProps> = ({ todos }) => {
console.log('rendering');
// 略
CSRの場合
ブラウザのコンソールに2回ログが表示されます。
2回の内訳は『初期Stateでのレンダリング』と『ToDoリストをStateにセットした際のレンダリング』です。
Stateの更新があるとReactアプリケーションは再レンダリングをするため、今回のように2回ログが表示されます。
Reactコンポーネントの再レンダリングに関する詳細解説はReactコンポーネントの再レンダリング発生条件と防止方法で紹介しています。
SSRの場合
next dev
を実行したターミナルと、ブラウザのコンソールに1回ずつログが表示されます。
サーバサイドでレンダリングをしてHTMLをレスポンスとして返した後、クライアントサイドではHydration時にレンダリングが行われます。
HydrationとはHTMLに付随したJavaScriptを利用してイベントリスナを登録することでインタラクティブ(操作可能)なページを生成する過程のことをいいます。
【図解】Next.jsのSSRが画面反映されるまでの具体的な流れで解説したように、Next.jsのSSRの過程ではクライアントサイドでレンダリング結果の比較・検証が行われます。
next/linkの場合
ブラウザのコンソールに1回ログが表示されます。
ターミナルにはログが表示されないため、next/linkのレンダリングはクライアントサイドだけで完結していることがわかります。
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!