Closed6

フロントエンドのレンダリングについて(Nuxt.js/Next.js)

ebi_yuebi_yu

フロントエンドの実装方式

ここから画像などを引用

静的サイト

  • HTML/CSS/JavaScriptのみで作成されたページ

CSR(Client Side Rendering)

  • アクセスしてきたユーザのブラウザ上(Client)でHTMLを動的に生成する方式。

SPA (Single Page Application)

  • CSRを使うことで、1つのページのみでサイト(Application)を表現する方式
  • サーバから受け取ったデータによって1つのページのDOM要素を更新していくことで、1つのページのみで サイトを表現することができる

SSR(Server Side Rendering)

  • サーバ上で動的にHTMLを生成し、アクセスしてきたユーザのブラウザに返す方式。
  • SSRより表示速度は速いが、サーバ負荷がかかる。

SSG (Static Site Generation)

  • 開発時に全てのページを静的なHTMLとして生成しておく方法
  • 静的サイトなので速度は速いが、ビルドするのに時間がかかる。
  • 静的サイトなので、動的にHTMLの変更をすることができない。

ISR(Incremental Static Regeneration)

  • 基本はSSGで生成された静的ページ
  • 有効期限を超えたら非同期で静的ページの再生成をSSRで行う。

ebi_yuebi_yu

Nuxt.js

  • ハイブリットレンダリングでページごとにレンダリングモードを指定できる。
  • ユニバーサルレンダリングは初回はSSR、ページ描画後の動的な処理についてはCSRで行う?
  • ISRについてはNetlify または Vercelのプラットフォームでのみサポートされているらしい。

ブラウザーがユニバーサル (サーバー側 + クライアント側) レンダリングを有効にして URL を要求すると、サーバーは完全にレンダリングされた HTML ページをブラウザーに返します。
動的なインターフェイスやページ遷移など、クライアント側のレンダリング方法の利点を失わないように、HTML ドキュメントがダウンロードされると、クライアント (ブラウザー) はバックグラウンドでサーバー上で実行される JavaScript コードを読み込みます。ブラウザーはそれを再度解釈し (したがってユニバーサル レンダリング)、Vue.js がドキュメントを制御して対話性を可能にします。

https://nuxt.com/docs/guide/concepts/rendering#edge-side-rendering

ハイブリットレンダリングの設定

  • routeRulesで個別のページのレンダリングモードを設定する
  • prerender: trueにするとSSGができるようになる。

nuxt.config.ts

export default defineNuxtConfig({
  routeRules: {
    "/csr": { ssr: false },
    "/ssr": { ssr: true },
    "/ssg": { ssr: true, prerender: true },
  },
  build: {
    transpile: ["vuetify"],
  },
  modules: ["@nuxtjs/tailwindcss"],
  css: ["~/assets/css/global.css"],
});

CSR

  • nuxt.config.tsrouteRulesssr : falseにする。
  • 通信で送られてくるhtmlドキュメントは空
  • クライアント側でDOM要素が動的に生成されている。
<template>
  <div>
    <h1>Client Side Rendering Example</h1>
    <div>
      <p>Id: {{ data?.id }}</p>
      <p>Title: {{ data?.title }}</p>
      <p>UserId: {{ data?.userId }}</p>
    </div>
  </div>
</template>

<script setup lang="ts">
const { data } = (await useAsyncData("run_useAsyncData", () => {
  return $fetch("https://jsonplaceholder.typicode.com/todos/1");
})) as { data: Ref<Record<string, string>> };
</script>

ユニバーサルレンダリング

  • 全てのページの要素があらかじめ、サーバから渡されている
  • クライアント側でのイベントをフックして動的に生成したdom要素は含まれていない。
    • onMountedで動的に生成されたdom要素は含まれない。
<template>
  <div>
    <h1>Client Side Rendering Example</h1>
    <div>
      <p>Id: {{ data?.id }}</p>
      <p>Title: {{ data?.title }}</p>
      <p>UserId: {{ data?.userId }}</p>
      <p>{{ csr }}</p>
    </div>
  </div>
</template>

<script setup lang="ts">
const { data } = (await useAsyncData("run_useAsyncData", () => {
  return $fetch("https://jsonplaceholder.typicode.com/todos/1");
})) as { data: Ref<Record<string, string>> };
const csr = ref("");

onMounted(() => {
  csr.value = "client rendering data";
});
</script>

SSG

  • nuxt.config.tsrouteRulesprerender : trueにする。
  • ビルド時にページが事前生成される。

コード例

https://github.com/ebi-yu/playground/tree/main/frontend/nuxt

ebi_yuebi_yu

Next.js

  • app-routerとpage-routerでレンダリングの実装の仕方がだいぶ異なる
    • 今回はapp-routerで試している。
  • app-routerでは基本的にSSRで動き、CSRをしたいときは明示的にuse clientと宣言を入れる必要がある。

CSR

  • use client宣言がされているかつ、useStateの使用がされている場合にCSRになる。
  • Nuxt.jsのCSRと違い、クライアントで動的に生成されるところ以外のhtmlドキュメントはサーバから返ってくる。(通信で送られてくるhtmlが空じゃない)
"use client";
import { useState, useEffect } from "react";

interface Todo {
  userId: number;
  id: number;
  title: string;
}

const CSRendering = () => {
  const [data, setData] = useState<Todo | null>(null);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    const response = await fetch(
      "https://jsonplaceholder.typicode.com/todos/1"
    );
    const data: Todo = await response.json();
    setData(data);
  };

  return (
    <div>
      <h1>Client Side Rendering Example</h1>
      <div>
        <p>Id: {data?.id}</p>
        <p>Title: {data?.title}</p>
        <p>UserId: {data?.userId}</p>
      </div>
    </div>
  );
};

export default CSRendering;

SSR/SSG/ISR

  • fetchメソッドでno-storeを設定すると、SSRになる。
  • force-cacheにするとSSGになる。
  • revalidateを設定するとISRになる。
interface SsrTodo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

export default async function SsrPage() {
  const todo: SsrTodo = await getData();

  return (
    <ul>
      <li> Id : {todo.id}</li>
      <li> userId : {todo.userId}</li>
      <li> title : {todo.title}</li>
      <li> completed : {todo.completed.toString()}</li>
    </ul>
  );
}

async function getData() {
  // SSR
  const res = await fetch("https://jsonplaceholder.typicode.com/todos/1", {
    cache: "no-store",
  });
  // SSG
  const res = await fetch("https://jsonplaceholder.typicode.com/todos/1", {
    cache: "force-cache",
  });
  return res.json();
}
  // ISR
  const res = await fetch("https://jsonplaceholder.typicode.com/todos/1", {
    next: { revalidate: 60 },
  });

コード例

https://github.com/ebi-yu/playground/tree/main/frontend/next

ebi_yuebi_yu

感想

  • Next.jsとNuxt.jsを比べてみると、レンダリングだけでも違うことが多い
  • 個人的にはNext.jsの方が一歩リードしている印象を受けた
このスクラップは1ヶ月前にクローズされました