パフォーマンス無関心で作ったサイトのパフォーマンスチューニング番外編 Nextjs AppRouter(色々試す)
はじめに
こんにちわ!こんばんわ!ご観覧ありがとうございます。
定期的に雑色な記事を投稿します。(タイトルは記事のメインの内容にしてますが)
基本的には抱えている問題を改善し、改善方法を記事にしてアップしようと考えています。
お題と経緯
NextjsのPageRouterからAppRouterに乗り換えてみてパフォーマンスを測ってみたいし、使ってもみたいなってことで、
今回はFirebaseのhostingでAppRouterがどこまで機能するかを、パフォーマンス向上の一環として試そうと思います!
まずはAppRouterでサンプルのAppを作成しつつ、備忘録で説明を書いていこうと思います。
今回の内容
画像の圧縮方法を記録していきたいと思います!
使用した技術
Category | Technology Stack |
---|---|
Frontend | Nextjs,React,Typescript |
Backend | Typescript,Node,CloudFunction |
Database | Firestore |
Design | XD |
前回の内容
前回記事
パフォーマンス無関心で作ったサイトのパフォーマンスチューニング番外編 Nextjs AppRouter(fetch) - zare926のブログ
NextjsのAppRouterでSGやSSR、ISRのfetchについてやってみました。
色々試す!
今回は色々試す回にします。
まずはクライアントコンポーネントから動的なfetchをしてみます。
FetchComponentとButtonのコンポーネントを作成
FetchComponent.tsx
"use client"; import React, { useState } from "react"; import { Button } from "./Button"; const FetchComponent = () => { const [user, setUser] = useState<{ id: number; name: string }>(); const testFunc = async () => { const number = Math.floor(Math.random() * (10 - 1 + 1)) + 1; const response = await fetch( `https://jsonplaceholder.typicode.com/users/${number}` ); const user: { id: number; name: string } = await response.json(); return user; }; const fetchUser = async () => { const response = await testFunc(); setUser(response); }; return ( <div> <Button text="fetch button" onClick={fetchUser} /> <p> クライアントコンポーネントからfetchした値 : {user && <span>{user.name}</span>} </p> </div> ); }; export default FetchComponent;
Button.tsx
"use client"; import React from "react"; type Props = { text: string; onClick?: () => void; }; export const Button = (props: Props) => { const { text, onClick } = props; return ( <button className="bg-gray-200 hover:bg-gray-300 active:bg-gray-400 border border-gray-400 p-2 transition-all duration-300 ease-in-out" onClick={() => { onClick && onClick(); }} > {text} </button> ); };
import { FetchComponent } from "@/components/FetchComponent"; import { Squares } from "@/components/Squares"; import Link from "next/link"; export default async function Home() { const response = await fetch(`http://localhost:3000/sample-date-get`, { next: { revalidate: 5 }, }); const data = await response.json(); return ( <div className="p-8 relative"> <h1 className="text-gradient-blue-green" data-content="TopPage"> TopPage </h1> <Link href="/sample" className="text-sky-600"> Sampleページへ </Link> <p>{data.data}</p> <FetchComponent /> <Squares /> </div> ); }
json placeholderでuserの名前を引っ張ってきてますが、うまく動いていますね。
ちなみにクライアントサイドでのfetch関数にISRの設定をしてもcacheはされなかったです。
次にサーバーコンポーネントからfetchをしてcacheの設定をしてみます。
ChildrenServerComponent.tsx
import React from "react"; export const ChildrenServerComponent = async () => { const response = await fetch(`http://localhost:3000/sample-date-get`, { next: { revalidate: 20 }, }); const data = await response.json(); return ( <div> <p> サーバーサイドコンポーネントからfetchした値 :<span>{data.data}</span> </p> </div> ); };
page.tsx
import { ChildrenServerComponent } from "@/components/ChildrenServerComponent"; // import { FetchComponent } from "@/components/FetchComponent"; import { Squares } from "@/components/Squares"; import Link from "next/link"; export default async function Home() { const response = await fetch(`http://localhost:3000/sample-date-get`, { next: { revalidate: 5 }, }); const data = await response.json(); return ( <div className="p-8 relative"> <h1 className="text-gradient-blue-green" data-content="TopPage"> TopPage </h1> <Link href="/sample" className="text-sky-600"> Sampleページへ </Link> <p>{data.data}</p> <ChildrenServerComponent /> <Squares /> </div> ); }
しっかりとサーバーでfetchされ表示されました。
親のサーバーコンポーネントではrevalidateを5秒に設定し、子のサーバーコンポーネントでは20秒にしましたが、同じAPIだったからなのか、親の5秒に引っ張られました。
逆に親の5秒をコメントアウトしたところ、親を含めて20秒の設定になりました。
APIを分けた場合、それぞれの設定が生かされました。
てとこですかね。
最後にクライアントコンポーネントの子にサーバーコンポーネントを設置してみます。
FetchComponent.tsx
"use client"; import React, { useState } from "react"; import { Button } from "./Button"; import { ChildrenServerComponent } from "./ChildrenServerComponent"; export const FetchComponent = () => { const [user, setUser] = useState<{ id: number; name: string }>(); const testFunc = async () => { const number = Math.floor(Math.random() * (10 - 1 + 1)) + 1; const response = await fetch( `https://jsonplaceholder.typicode.com/users/${number}` ); const user: { id: number; name: string } = await response.json(); return user; }; const fetchUser = async () => { const response = await testFunc(); setUser(response); }; return ( <div> <Button text="fetch button" onClick={fetchUser} /> <p> クライアントコンポーネントからfetchした値 : {user && <span>{user.name}</span>} </p> <ChildrenServerComponent /> </div> ); };
page.tsx
import { FetchComponent } from "@/components/FetchComponent"; import { Squares } from "@/components/Squares"; import Link from "next/link"; export default async function Home() { const response = await fetch(`http://localhost:3000/sample-date-get`, { next: { revalidate: 5 }, }); const data = await response.json(); return ( <div className="p-8 relative"> <h1 className="text-gradient-blue-green" data-content="TopPage"> TopPage </h1> <Link href="/sample" className="text-sky-600"> Sampleページへ </Link> <p>{data.data}</p> <FetchComponent /> <Squares /> </div> ); }
SC<CS<SC(async/await有)という設定ですが、結論これはダメでした。
クライアントコンポーネントは、コンポーネント単位でasync/awaitを使えないので、子のコンポーネントがサーバーサイドでasync/awaitを使用しているとエラーが出ます。
サーバーコンポーネントがasync/awaitを使っていなければ、今回の様な親子関係も可能です。
ちなみにSC<SC<CSは問題なく動きます。
この辺りは慣れが必要そうですね!
最後に
色々と把握できた回で満足でした!
ちなみに今回出たエラーを翻訳すると
エラー:async/awaitはクライアント・コンポーネントではまだサポートされていません。このエラーは、本来サーバ用に書かれたモジュールに誤って `'use client'` を追加してしまった場合によく起こります。
なんですが、将来的に対応する様になるんですかね。
そうするとかなり自由度があるようなないような。。。
動的なアプリを作る場合は結構考えものです。
ここまでみていただいてありがとうございました!!!