はじめまして、可茂IT塾インターン生のhirotoです。
本記事では、Next.js(app router)で初心者がハマりやすい「データフェッチ」のやり方と対処法について説明します。実際、僕もハマってしまったのでみなさんも気をつけてください。
特に以下に該当する方:
記事の内容:
client componentでは、
「サイトをクリック」→「サーバー側でHTMLの構築」→「クライアント側へHTMLとJSを送信」→「クライアント側でデータフェッチ」→「HTMLにデータを反映させ終了」
という流れです。
server componentでは、
「サイトをクリック」→「サーバー側でHTMLの構築とデータフェッチ」→「クライアント側へHTML・JS(データフェッチ以外の処理)を送信」→「クライアント側でHTMLを表示させて終了」
という流れです。
server componentの場合、HTMLの構築とデータフェッチを並列で行う方法が主流でありので、この方法を前提としています。
client componentでデータフェッチをする場合、クライアント側でJSを動かす必要があります。そのため、2つの問題が出てきます。
1つ目は、JSバンドルサイズが肥大化することです。
JSバンドルサイズとは、JavaScriptのコードを1つにまとめたファイルのことです。
このファイルが大きくなるのに比例して、クライアントの処理とサーバーからクライアントへの転送量も増えます。
そのため、無駄なJavaScriptコードや転送量が発生し、パフォーマンスの低下につながります。
2つ目は、クライアント側のネットワークやデバイスの処理能力に応じて動作速度が異なることです。クライアント側の環境を使用するとサーバー側の負担は軽減しますが、しかし、クライアントのデバイスの性能が低かったり、ネットワークが悪いとサイトやアプリが開かないといった問題につながります。
こういった点からクライアント側でJSを動かすのは、なるべく最低限にした方が良いと考えられます。
SEO対策として表示速度・クローラーへの対策があります。
クライアント側でJSを動かすとその2つに対して悪い影響を与えてしまいます。
理由は以下の2点です。
上記2つは、クライアント側でJSのデータフェッチを行い、その完了を待つのが原因です。
server componentでも初期動作はかかりますが、「前提」で話した通りHTMLの構築とデータフェッチを並列で行えるため、基本的にクライアント側より高速です。
また、HTMLの完成ができないのは同じですが、server componentの方が表示速度が早いので、SEO的に悪くなる可能性が低いです。
理由は、下記のサイトをご覧ください
SuspenseとStreaming|Next.jsの考え方
'use client'
import { fetchData } from './action'
import { useState, useEffect } from 'react';
export default function Page() {
const [sampleData, setSampleData] = useState();
useEffect(() => {
(async () => {
const data = await fetchData();
setSampleData(data);
})();
}, []);
return <p>{sampleData}</p>;
}
NGパターンは、client componentでデータフェッチを行っています。
問題は、useEffectを使って、初めの1回しか行わないデータフェッチをclient componentで行っている点です。
これをserver componentで行うと以下のようになります。
import { fetchDataFromDB } from '@/models/DB';
import type { Data } from '@/types/types';
type DataProps = {
data: Data[];
};
export default function Page() {
const datas = await fetchDataFromDB();
return (
<>
<Component data={data} />
</>
);
}
function Component({datas}: DataProps) {
return <p>{data}</p>;
}
NGコードと比べると、データフェッチが1行で行えています(関数を呼び出しているので、正確には1行ではありませんが)。
もう1点注目したいのが、Componentを他の関数にして、呼び出していることです。
この関数は、他のファイルで実装し、関数を呼び出すことで実装することも可能です。
このようにすることで、サーバーサイド側の処理とクライアント側の処理が明確になりやすいです。これを「Container/Presentationalパターン」と呼ぶらしいです。
詳しく知りたい方は以下を参照してください。
Container/Presentationalパターン|Next.jsの考え方
ただし、データフェッチをコンポ―ネント側で行いたい場合があります。
以下は、コンポーネント側でデータフェッチを行う例です。
// react公式のコードより(https://ja.react.dev/reference/react/useActionState)
import { useActionState } from 'react';
import { fetchBookFromDB } from '@/models/DB';
async function fetchBookName(previousState, formData) {
const id = formData.get('id');
const bookName = fetchBookFromDB(id) as string
return bookName ;
}
function StatefulForm({}) {
const [bookName, formAction] = useActionState(fetchBookName, "");
return (
<form action={formAction}>
<label htmlFor="id">
Fetch Book Name...
<input type="text" id="id" name="id" />
</label>
<button type="submit">Submit</button>
</form>
<p>Book Name:{booknName}</p>
)
}
ユーザーの入力やその他の操作によってデータフェッチの値が変わるときは、server componentと側ではなく、client component側で行う方と部分的に再レンダリングを行いやすいメリットがあります。
使用例としては、検索機能です。ユーザーが入力した値に応じて表示するデータを変更する場合などに使用します。
Next.jsの仕組みがPage RouterからApp Routerに変更されたことで、ネットの情報が混在しています。特に初めの頃はその見分けがつきづらいので、とりあえず公式ドキュメントを読もう!
可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More