💭

reactチュートリアル4(Pre-rendering and Data Fetching)

2024/05/08に公開

NextJSの学習をする。
以下のページを翻訳し記録する。
https://nextjs.org/learn-pages-router/basics/data-fetching

  1. reactチュートリアル1(create a nextjs app)
  2. reactチュートリアル2(Navigate Between Pages)
  3. reactチュートリアル3(Assets, Metadata, and CSS)
  4. reactチュートリアル4(Pre-rendering and Data Fetching)
  5. reactチュートリアル5 (Dynamic Routes)
  6. reactチュートリアル6(API Routes)
  7. reactチュートリアル7(Deploying Your Next.js App)

このレッスンで何を学ぶか

このレッスンでは以下について学びます。

Pre-rendering

data fetchingについて話をする前に、NextjsのPre-renderingの最も重要な概念について話をしましょう。

初期設定でNext.jsは全てのページを事前レンダリングします。つまり、Next.jsはクライアントサイドのJavascriptで全てを実行する代わりに、各ページのHTMLを事前に生成するといううことです。事前レンダリングはより良いパフォーマンスとSEOに繋がります。

生成されたそれぞれのHTMLはページに必要な最低限のJavaScriptコードと関連付けされます。ページがブラウザによってロードされると、JavaScriptコードが実行されページを完全にインタラクティブにします。(これはhydrationと呼ばれます)

事前レンダリングが起きていることを確認する

次の手順で事前レンダリングが実行さていることを確認することができます。

  1. ブラウザのJavaScriptを無効化します。(Chromeの場合はこの様に実施します)
  2. このページにアクセスします(このチュートリアルの最終成果物)

JavaScriptなしにレンダリングされることが確認できます。これはNext.jsがappを静的なHTMLに事前レンダリングしているからで、JavaScriptを実行することなくapp UIを見ることができます。

注意 上記手順はlocalhostで試すことができますが、JavaScriptを無効にしているとCSSは読みこまれません。

appが素のReact.jsでNext.jsではない場合、事前レンダリングはありません。ですので、JavaScriptを無効にするとappを見ることはできません。例えば。

  • JavaScriptを有効化してこのページを確認してください。これは素のReact.jsアプリで、Create React Appで作られたものです。
  • JavaScriptを無効化して再度同じページにアクセスしてください。
  • アプリはもう見られません。そのかわりに"You need to enable JavaScript to run this app."というメッセージが表示されます。これはアプリがもはや事前連打リンクによる静的なHTML生成を実施しないからです。

まとめ: Pre-rendering vs No Pre-rendering

簡単なまとめ画像です。
alt text
alt text
次は2つの事前レンダリングの形について話をしましょう。

事前レンダリングの2つの形

Next.jsは2つの事前レンダリングの形を持ちます。Static GenerationServer-side Renderingです。HTMLがいつ生成されるかが違いになります。

  • Static Generationはビルド時にHTMLを生成する方法です。事前レンダリングされたHTMLはリクエストの際に再利用されます。
  • Server-side RenderingはリクエストのたびにHTMLを生成する事前レンダリングです。

Static Generation

alt text

Server-side Rendering

alt text

開発モードでは(npm run devやyarn devしているとき)ページは全てのリクエストで事前レンダリングされています。これは静的生成にも適用され、開発が用意となります。製品deployの場合は静的サイト生成は毎回は実施されず、一度だけビルド時に実施されます。

ページごとの基本

Next.jsはページごとにどちらの事前レンダリングの形態を取るかを開発者が選ぶことができるのは重要なポイントです。ほとんどのページでStatic Generationを使い、その他でServer-side Renderingを使うことでハイブリッドNext.jsアプリを作ることができます。

alt text

いつStatic GenerationServer-side Renderingを使うべきか

データが有ってもなくてもできる限りStatic Generationを使うことをおすすめします。なぜならページはビルドされCDNによってサーブされるからです。これは毎回のリクエストでサーバサイドレンダリングをするよりとても高速です。

多くのページでStatic Generationを使うことができます。

  • マーケティングページ
  • ブログ投稿
  • 電子商取引のための製品リスト
  • ヘルプドキュメントページ

自分にこう問いかけましょう。"ユーザリクエストの前に事前レンダリングできるだろうか?"答えがyesの場合はStatic Generationを使うべきです。

一方で、ユーザリクエストの前にページを生成できない場合はStatic Generationは良い選択肢ではありません。データが高頻度で更新され、ページコンテンツが毎回のリクエストで変わるかもしれません。

その場合はServer-side Renderingを使うことができます。これは比較すると低速ですが、毎回最新のページが表示されます。あるいは、事前レンダリングをスキップして頻繁に更新されるデータを作るためにクライアントサイドでJavaScriptを使ってもよいです。

静的サイト生成に集中します

本レッスンではStatic Generationに集中し、データがある場合ない場合にStatic Generationに関して話をします。

データがある場合、ない場合の静的サイト生成

Static Generationはデータの有無によらず実施できます。

これまで作ってきた全てのページは外部データを取得する必要はありませんでした。これらのページは製品用にアプリがビルドされる時に自動的に静的サイトが生成されました。

alt text

しかしながら、外部データを取得するまでHTMLがレンダリングできないかもしれません。ファイルシステムにアクセスするかもしれないし、外部APIを叩くかもしれません、はたまたデータベースに問い合わせするかもしれません。Next.jsはデータを伴う静的サイト生成に対応しています。

alt text

getStaticPropsを使ったデータを伴う静的サイト生成

これはどの様に動作するのでしょうか?Next.jsではページコンポーネントをエクスポートしたとき、getStaticPropsと呼ばれる非同期関数を同時にエクスポートできます。そうすると

  • getStaticPropsが製品deployのビルド時に走ります
  • 関数では外部データを取得し、ページに引数として送ることができます。
export default function Home(props) {...}

export async function getStaticProps(){
    // Get external data from the file system, API, DB, etc.
    const data =...

    // The value of the `props` key will be
    // passed to the `Home` component
    return {
        props:...
    }
}

本質的にはgetStaticPropsは "このページは依存しているデータが有るぜ、だからこのページをビルド時に事前レンダリングする時はそのデータを先に解決するようにしろよ"とNext.jsに伝えることをあなたに許可するということです。

** 注意 ** 開発モードでは全てのリクエストでgetStaticPropsが走ることに注意。

getStaticPropsを使ってみよう

実施することで学びは簡単になります。次のページではgetStaticPropsを使ってブログを実装します。

シンプルなブログアーキテクチャを作る

今回のブルグ投稿はアプリケーションのディレクトリにローカルマークダウンファイルを保存し、外部データソースから取得しません。つまり、ファイルシステムを読む必要があります。

本セクションでは、ファイルシステムからマークダウンデータを読むブログを作るステップを見ていきます。

マークダウンファイルを作る

まずルートフォルダにpostsという名前のトップレベルディレクトリを作ります。これはpages/postsとは別のものです。postsの中にpre-rendering.mdとssg-ssr.mdという2つのファイルを作ります。

では次のcodeをposts/pre-rendering.mdにコピーしましょう。

---
title: 'Two Forms of Pre-rendering'
date: '2020-01-01'
---

Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.

- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.

Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.

次に posts/ssg-ssr.mdに以下をコピーしましょう。

---
title: 'When to Use Static Generation v.s. Server-side Rendering'
date: '2020-01-02'
---

We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.

You can use Static Generation for many types of pages, including:

- Marketing pages
- Blog posts
- E-commerce product listings
- Help and documentation

You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.

On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.

In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.

それぞれのマークダウンは上部にtitleと日時を含むメタデータがあることに気づいたかもしれません。これはYAML Front Matterと呼ばれgray-matterと呼ばれるライブラリを使うことでパースできます。

Installing gray-matter

最初にgray-matterをインストールしましょう。これでマークダウンファイル毎のメタデータのパースができます。

ファイルシステムを読み込むutility関数を作る

次にファイルシステムからデータをパースできるutility関数を作ります。これで以下を実施したいです。

  • それぞれのマークダウンファイルをパースしてタイトルと日時を取得します。これは投稿のURLのIDとして使います。
  • 日時順にインデックスページにデータを並べます

ルートディレクトリにlibという名前のトップレベルディレクトリを作ります。libの中にposts.jsというファイルを作り、以下のコードをコピーします。

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const postsDirectory = path.join(process.cwd(), 'posts');

export function getSortedPostsData() {
  // Get file names under /posts
  const fileNames = fs.readdirSync(postsDirectory);
  const allPostsData = fileNames.map((fileName) => {
    // Remove ".md" from file name to get id
    const id = fileName.replace(/\.md$/, '');

    // Read markdown file as string
    const fullPath = path.join(postsDirectory, fileName);
    const fileContents = fs.readFileSync(fullPath, 'utf8');

    // Use gray-matter to parse the post metadata section
    const matterResult = matter(fileContents);

    // Combine the data with the id
    return {
      id,
      ...matterResult.data,
    };
  });
  // Sort posts by date
  return allPostsData.sort((a, b) => {
    if (a.date < b.date) {
      return 1;
    } else {
      return -1;
    }
  });
}

注意:
Next.jsを学ぶために上記のコードが何をしているかを理解する必要はありません。関数はブログを動くようにするためのものです。もし勉強したい場合は

  • fs は Node.jsモジュールで、ファイルシステムからファイルを読めるようにしてくれます
  • path は Node.jsモジュールでファイルパスを操作するものです
  • matter は マークダウンファイルのメタデータをパースするライブラリです
  • Next.js ではlibフォルダーはpagesフォルダーの様な定められた名前はありませんので、どのようにでも名付けられます。libかutilsを使うのが昔ながらの監修です。

ブログデータを取得する

これでブログデータはパースされました。これをインデックスページ(pages/index.js)に追加する必要があります。Next.jsのgetStaticProps と呼ばれるdata fetching method を使うことで実現できます。次のセクションではgetStaticPropsの実装方法について学びます。

index-page

getStaticPropsを実装する

Next.jsは静的生成とサーバーサイドレンダリングの2つの事前レンダリング方法があります。HTMLページをいつ作るかという点に違いがあります。

  • 静的生成はビルド時にHTMLを生成する方法で、リクエストのたびに事前レンダリングHTMLは再利用されます。
  • サーバーサイドレンダリングはリクエストのたびにHTMLを生成する事前レンダリング手法です

ページごとにどちらの方法を選択するか選ぶことができます。多くのページに静的生成を使い、サーバーサイドレンダリングをその他に使うハイブリッドNext.jsを作ることができます。

静的生成を使う(getStaticProps)

getSortedPostsDataをインポートすることで、pages/index.jsのgetStaticPropsから呼ぶことができます。

pages/index.jsを開き、エクスポートされたHomeコンポーネントの上にいかのcodeを追加しましょう。

import { getSortedPostsData } from '../lib/posts';

export async function getStaticProps() {
  const allPostsData = getSortedPostsData();
  return {
    props: {
      allPostsData,
    },
  };
}

getStaticPropsのpropsオブジェクトからallPostsDataを返すことで、ブログの投稿がホームコンポーネントに渡されます。以下のようにしてブログ投稿にアクセスできます。

export default function Home ({allPostsData})(...)

ブログ投稿を表示していきましょう。自己紹介の下に別のセクションタグを追加するためにホームコンポーネントを更新しましょう。
引数を () から ({allPostsData}) に変えるのを忘れないようにしましょう。

export default function Home({ allPostsData }) {
  return (
    <Layout home>
      {/* Keep the existing code here */}

      {/* Add this <section> tag below the existing <section> tag */}
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              {title}
              <br />
              {id}
              <br />
              {date}
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  );
}

http://localhost:3000/にアクセスすると以下のブログデータが見られるはずです。
blog.png

おめでとうございます!外部データ(ファイルシステムから)を取得し、このデータを使ったインデックスページを事前レンダリングできました。

index-page.png

次はgetStaticPropsを使ったコツについてお話しましょう。

getStaticPropsの詳細

いくつかgetStaticPropsについて知っておくべき大事なことがあります。

外部APIあるいはデータベース問い合わせによるデータ取得

lib/posts.jsではファイルシステムからデータを取得するgetSortedDataを実装しました。しかし他のソースからデータを取得することができます。外部APIなどからです。

export async function getSortedPostsData(){
    // Instead of the file syste,
    // fetch post data from an external API endpoint
    const res = await fetch('...')
    return res.json()
}

**注意:**Next.jsはクライアントでもサーバーでもfetch()を代替します。importする必要はありません。

データベースに直接問い合わせることもできます。

import someDatabaseSDK from 'someDatabaseSDK'

const databaseClient = someDatabaseSDK.createClient(...)

export async function getSortedPostsData() {
  // Instead of the file system,
  // fetch post data from a database
  return databaseClient.query('SELECT posts...')
}

これはgetStaticPropsがサーバーサイドでだけ動くから可能となります。クライアントでは動作しません。ブラウザ向けのJS良いとバンドルに含まれることもありません。つまり、ブラウザに送ることなく直接データベース問い合わせなどのcodeを書くことができるということです。

開発 vs 製品

  • 開発(npm run dev or yarn dev)ではgetStaticPropsは毎回のリクエストで実行されます。
  • 製品ではgetStaticPropsはビルド時だけ呼び出されます。しかし、getStaticPropsによって返却されるfallback keyを使うことで動作を強化することができます。

ビルド時だけ動作するので、HTTPヘッダーのパラメータのようにリクエスト時にだけ使えるようになるデータを使うことはできません。

ページでだけ許可

getStaticPropspageからだけ許可されます。pageファイル外に出すことはできません。この制限の理由の一つとして、ページがレンダリングされる前に全てのデータが揃っている必要がある事があります。

リクエスト時にデータ取得をする必要性があったら?

静的生成はビルド時に一度だけ実行されるのでユーザのリクエスト時に毎回など頻繁に更新されるデータには適しません。このような場合、Server-side Renderingを使うことができます。次はサーバーサイドレンダリングについて学びましょう。

リクエス時のデータ取得

ビルド児ではなく、リクエスト時にデータを湯得したい場合はサーバーサイドレンダリングを使うことができます。

alt text

サーバーサイドレンダリングを使場合はgetStaticPropsを使うのではなくgetServerSidePropsを使います。

getServerSidePropsを使う

getServerSidePropsを使う最初のcodeは以下になります。これはブログのサンプルには必要ないので実装しません。

export async function getServerSideProps(context) {
  return {
    props: {
      // props for your component
    },
  };
}

getServerSidePropsはリクエスト時に呼ばれるので、パラメータ(context)はリクエストに関するパラメータを持ちます。

リクエスト時に取得する必要があるデータを事前レンダリングするときだけgetServerSidePropsを使うべきです。

サーバーの応答時間getStaticPropsより遅いです。サーバーはリクエストのたびに結果を計算しないといけないからです。そして結果は特別な設定なしではCDNにキャッシュされません。

クライアントサイドレンダリング

事前レンダリングが必要ない場合はクライアントサイドレンダリングを選択することもできます。

  • 外部データを必要としない静的生成パーツ
  • ページがロードされた時はJavaScriptを使って残りのパーツを生成します。
    alt text

このやり方はユーザダッシュボードページにあっています。なぜならダッシュボードはプライベートでユーザに特化したページです。SEOは関連せず、ページは事前レンダリングされる必要はあありません。データはリクエストのたびに更新されます。

SWR

Next.jsではデータを取得するSWRというライブラリも持っています。クライアントサイドでデータを取得する時に使うことを強くおすすめします。キャッシュやデータ再検証、フォーカストラッキングなどに使われます。ここでは詳細には触れませんが、以下のサンプルのように利用します。

import useSWR from 'swr';

function Profile() {
  const { data, error } = useSWR('/api/user', fetch);

  if (error) return <div>failed to load</div>;
  if (!data) return <div>loading...</div>;
  return <div>hello {data.name}!</div>;
}

詳細はSWR documentation参照。

以上です!

次はdynamic routesを使ってブログポスト毎のページを作ります。

最後にもう一度、ぜひgetStaticPropsgetServerSidePropsData Fetchingについて詳しく調べて見てください。

Discussion