LoginSignup
160
88

More than 1 year has passed since last update.

6歳娘「パパ、SGしてみたらサイトが爆速になったよ!」

Last updated at Posted at 2022-06-26

とある休日

ワイ「あー、今日はヒマやな〜」
ワイ「ヒマやから、メールチェックでもしよか」
ワイ「パソコンでメールを受信して・・・っと」
ワイ「わ〜、また迷惑メールたくさん来とるわ〜」

娘「パパ、迷惑メールって何?」

ワイ「そのまんまや」
ワイ「迷惑なメールことや」

娘「迷惑な・・・メール・・・」
娘「・・・あっ!」
娘「もしかして、パパみたいな男の人のこと!?」

ワイ「そうそう、もう存在そのものが迷惑な男性やから・・・コラッ!」
ワイ「誰が迷惑maleやねん」

娘「そんなことよりパパ」

ワイ「なんや、娘ちゃん」

ここから本題

娘「この前、私が日記を書くための個人ブログのサイトを作ったんだけどね」
娘「Next.jsを使って、SPAとして作ったの」

ワイ「ほうほう、シングルページアプリケーションやな」

娘「うん、それでね」
娘「CSRしてたところを、SGするように変えたら」
娘「サイトにアクセスした時のロード時間が、だいぶ速くなったの!」

ワイ「CSR・・・SG・・・?」
ワイ「娘ちゃん、一体なんの話をしてんのや?」

娘「えっとね」

CSRとは

娘「CSRっていうのはね」

  1. ブラウザ上から、JavaScriptを用いてAPIを叩く
  2. APIから取得したデータを、JavaScriptを用いて画面上に表示する

娘「↑こういうやつだよ!」

ワイ「ふーん、SPAでよくある普通の方法やな」
ワイ「SWRとかを使って、下記のようにやるんやろ?」

ブログ記事の詳細ページの例

データ取得

/pages/articles/[id].tsx
  // URLクエリから id を取得
  const articleId = router.query.id
  // APIからブログ記事を1件取得
  const { data: article } = useSWR(`/api/articles/${articleId}`, fetcher)

データ表示

/pages/articles/[id].tsx
  // APIからデータが読み込み終わるまでは「ロード中...」と表示される。
  return (
    <section>
      {/* 記事のタイトル */}
      <h1>{article ? article.title : "ロード中..."}</h1>
      {/* 記事本文 */}
      <div>{article ? article.body : "ロード中..."}</div>
    </section>
  )

ワイ「↑これをCSRって呼ぶんやな」
ワイ「知らんかったわ」

娘「ブラウザ・・・つまりクライアント側で情報を描画するから」
娘「クライアントサイド・レンダリング」
娘「だからCSRだね」

ワイ「なるほどなー」
ワイ「レンダリングって、描画って意味やもんな」

娘「うん」
娘「CSRの場合はね」
娘「サーバからHTMLを取得した時点では」

【CSR】サーバから返ってきたHTML(抜粋)
    <main>
      <section>
        <h1>ロード中...</h1>
        <div>ロード中...</div>
      </section>
    </main>

娘「↑こんな感じなの」

ワイ「ほー」
ワイ「この後、JavaScriptによって」
ワイ「記事のデータをAPIから取得してきて」
ワイ「ロード中...の部分が書き換わるわけやな」

娘「そうなの」

ワイ「つまり、一連の流れで言うと・・・」

  1. ブラウザのアドレスバーにブログ記事のURLを打ち込む
  2. 閲覧者さんのパソコンに、HTMLの内容が届く
    (この段階では、記事のタイトルや本文などは表示されていない)
  3. JavaScriptが読み込まれ、API通信が起こる
  4. APIから記事のデータが届く
  5. JavaScriptによって、画面上に記事のタイトルや本文が描画される

ワイ「↑こんな感じか」
ワイ「まさにクライアントサイドでレンダリングしとるわけやな」

娘「うん」

ワイ「ほな、SGってのはどんな感じなん?」

SGとは

娘「SGの場合はね」
娘「事前にHTMLの内容を静的生成しておくの」
娘「サイトをサーバにデプロイする前1にね」

ワイ「へぇ〜」

娘「静的生成は、英語で言うとStatic Generation
娘「だからSGっていうんだよ」

ワイ「ふーん」
ワイ「そのSGとやらをすると」
ワイ「ブラウザからサイトにアクセスした時のロード時間が速くなるん?」

娘「そうだよ」
娘「SGの場合は」
娘「サーバからHTMLを取得した時点で」

【SG】サーバから返ってきたHTML(抜粋)
    <main>
      <section>
        <h1>初投稿</h1>
        <div>これは初めての記事です。</div>
      </section>
    </main>

娘「↑こんな風に」
娘「記事の内容もちゃんとHTML内に書かれてるの」
娘「だから、記事の内容が即座に画面に表示されるの」

ワイ「そうか、記事の内容も含めて事前に生成されたHTMLを」
ワイ「ダウンロードしてくるだけって感じなんやな」

娘「そう」
娘「だから、SGしておく方が爆速なの」

ワイ「なるほどなー」
ワイ「SGが良さそうなのは分かったけど、具体的にどうやってSGするん?」

娘「えっとね」

getStaticPropsgetStaticPathsを使う

娘「Next.jsを使っている場合、SGをするには」
娘「getStaticPropsgetStaticPathsを使うの」

ワイ「ほえ〜、なんか聞いたことあるな」
ワイ「ページのtsxファイルに」
ワイ「getStaticPropsgetStaticPathsっていう関数を書いて」
ワイ「exportしてやればええんやっけ?」

娘「そう」
娘「さっきの例だと・・・」

getStaticProps

/pages/articles/[id].tsx
type StaticProps = { article: Article }

export const getStaticProps: GetStaticProps<StaticProps, { id: string }> = async (context) => {
  // 記事詳細APIからデータを取得
  const id = context.params!.id
  const article = await fetch(`https://api.example.com/articles/${id}`).then<Article>(res => res.json())

  return {
    props: {
      article,
    },
  }
}

娘「↑こんな感じでgetStaticPropsを書いてやればいいの」

ワイ「へー」
ワイ「APIを叩いて、ブログ記事の詳細情報を取得する感じか」

娘「うん」

ワイ「こう書いておくことで」
ワイ「デプロイ前の、ビルドの段階で」
ワイ「APIから記事データを取得して」
ワイ「HTMLの内容を埋めておいてくれるってわけか」

娘「そうだよ」
娘「ブログ記事が5件ある場合なら」

  • /articles/1.html
  • /articles/2.html
  • /articles/3.html
  • /articles/4.html
  • /articles/5.html

娘「↑こう、5つのファイルをビルド時に生成してくれるよ」

ワイ「なるほどな〜」
ワイ「でも・・・」

記事が5件あって、5つのファイルを生成しなければならない

ワイ「↑この事は、どうやってNext.jsに伝えるんや?」

娘「いい質問だね」
娘「そのためにgetStaticPathsを書くの」

  • /articles/1
  • /articles/2
  • /articles/3
  • /articles/4
  • /articles/5

娘「↑この5つのパスがあるってことをNext.jsに伝えるために」

/pages/articles/[id].tsx
const paths = [
  {
    params: {
      id: "1",
    },
  },
  {
    params: {
      id: "2",
    },
  },
  {
    params: {
      id: "3",
    },
  },
  {
    params: {
      id: "4",
    },
  },
  {
    params: {
      id: "5",
    },
  },
]

娘「↑こういう、pathsっていう配列を作ってあげないといけないの」

ワイ「ほうほう」
ワイ「pathsに入れておいた分だけ」
ワイ「HTMLファイルが生成されるってことかいな?」

娘「そうだよ」
娘「今回の例だとgetStaticPaths関数に書く内容は・・・」

getStaticPaths

/pages/articles/[id].tsx
export const getStaticPaths: GetStaticPaths = async () => {
  // 記事一覧APIからデータを取得
  const articles = await fetch("https://api.example.com/articles").then<Article[]>(res => res.json())

  const paths = articles.map((article) => ({
    params: {
      id: String(article.id)
    },
  }))
  return { paths }
}

娘「↑こんな感じだね」

ワイ「ほー」
ワイ「記事一覧のデータをAPIから取得して」
ワイ「その一覧データを元に、pathsの配列を作ってやるんやね」

娘「そう」
娘「そうしておくと、全記事ぶんのHTMLファイルが静的生成されるの」

ワイ「なるほどなー」

  1. ビルド時に、APIからデータを取得する
  2. そのデータをもとに、記事の内容が書かれたHTMLを生成する
    (全記事ぶん生成する)

ワイ「↑これをビルドの時にやっておくから」
ワイ「閲覧者さんがブログ記事にアクセスしてくれた時には」
ワイ「爆速でHTMLをご提供することができるんやな」

娘「そうだね」
娘「閲覧者さんは、API通信を待たずに」
娘「すぐにブログ記事の内容を読むことができるね」

ワイ「なるほどなー」
ワイ「ワイもSGしてみたくなってきたな」

SGが向いていないケースもある

ワイ「今度、Twitterみたいなサイトを自作するんやけど」
ワイ「そのサイトにもSGを導入してみよかな!」
ワイ「そうすれば、閲覧者さんが爆速でタイムラインの情報を見れるやろ?」

娘「うーん、無理そうじゃない?」

ワイ「へ、なんで・・・?」

娘「例えば、タイムラインのURLが」

https://example.com/home

娘「↑こういうURLだったとしてさ」
娘「ログインしているユーザーさんによって」
娘「タイムラインに表示されるツイートは違うでしょ?」

ワイ「せやで」
ワイ「そのユーザーさんがフォローしてる人達のツイートが」
ワイ「タイムラインに一覧表示されるわけやから」
ワイ「同じURLでも、ページの内容は人それぞれ違うで」

娘「そうだよね」
娘「じゃあ・・・」

サーバ「HTMLファイルは事前に用意してあるから」
サーバ「アクセスが来たら、生成済みのHTMLを返すだけ!」

娘「↑こんなことできないじゃん」

ワイ「あ、そっか・・・」
ワイ「誰がアクセスしてきたかによって」
ワイ「違った内容のHTMLを返さなアカンわけやもんな」

娘「そうそう」

ワイ「じゃあ、こんな場合はどうや?」
ワイ「今度、会員制のショッピングサイトを作る予定なんやけ」

娘「(食い気味に)無理だね」

ワイ「そうなんか・・・」

娘「だって、会員制サイトっていうことは」
娘「ログインしてないと、商品のページとか見れないんでしょ?」

ワイ「せや」

娘「じゃあ」

サーバ「ログイン済みの人がアクセスしてきた場合には」
サーバ「商品の内容が書かれたHTMLを返すで!」
サーバ「ログインしてない人がアクセスしてきた場合には」
サーバ「ログインボタンが表示されたHTMLを返すで!」

娘「↑こういう処理が必要でしょ?」

ワイ「そうか・・・」
ワイ「ってことは」

サーバ「アクセスが来たら、生成済みのHTMLを返すだけ!」

ワイ「↑こうはできないわけやな・・・」

まとめ

SGとは

  • Static Generation → 「静的生成」の略

SGするメリット

  • ユーザーがサイトに訪れた際に、爆速でHTMLを提供できる
    • ビルド時にAPIと通信して、事前にHTMLの中身を作成しておくため

SGが向かないケース

  • アクセスが来るまで、返すべきHTMLの内容が確定しないページ
      • Twitterのタイムラインとか
        • 閲覧するユーザーによって、HTMLの内容が変わるため
      • ログインが必要なサイト
        • ログインしているかどうかによって、HTMLの内容が変わるため

SGのやり方(Next.jsの場合)

  • ページのtsxファイルにgetStaticPathsgetStaticPropsという関数を書いてexportする2

ワイ「↑こんな感じやな!」

娘「そうだね!」
娘「ほかにも、Next.jsを使っていると」

  • ページ単位で「SGする or SGしない」を選択できる
  • SGするページでも「一部だけCSR」ということもできる
      • 記事の内容は、SGしておくことで爆速表示
      • コメントの内容は、CSRすることで最新のものを反映

娘「↑こんなことも、割と簡単にできるよ」

ワイ「へ〜、SGCSRを組み合わせることもできるんや」

娘「そうだよ〜」

OGPの表示にも有利

娘「SGは、OGPの表示にも有利だよ」
娘「HTMLを生成するときに、metaタグのOGP情報も全部書いておけば」
娘「SPAでよくあるOGP問題も回避できるじゃん?」

ワイ「そうなんや」
ワイ「そういえば、SPAでOGP画像をいい感じに表示するには」
ワイ「SSRをしないと、上手く行かなかったりするもんな」

娘「そうだね」
娘「その辺りのことは、パパが素性を隠して書いたZennの記事を読み返してみるといいかもね」
娘「とにかく、事前に静的生成ができるケースなら」
娘「できるだけSGしておいた方がいいと思う!」
娘「Next.jsの公式ドキュメントにも」

Static Generation (Recommended)

娘「↑こう推奨って書いてあるんだ」

ワイ「ほぇ〜、勉強になるわ」

その日の夜

ワイ「あー、今日も娘ちゃんのおかげで」
ワイ「またひとつ賢くなったで」
ワイ「もう少しでワイもチーフ・エンジニアになれるんちゃうか?」
ワイ「へへっ」

娘「パパ、すご〜い!」
娘「シーフ・エンジニアになるの?」

ワイ「いやシーフ3ちゃうわ」
ワイ「誰が給料泥棒エンジニアやねん」

〜おしまい〜

おまけ:誤解しそうなポイント

  • 全ページを静的生成しても、MPA(マルチページ・アプリケーション)になるわけではありません
    • 初回ロードした後のページ遷移は、SPA的な動作になります

続編はこちらやで!

  1. 静的生成後、自動的にサーバにデプロイすることが多いと思うので、実際にはほぼ同時に行われます。

  2. ページコンポーネント側で、それをpropsとして受け取る必要があります。

  3. 「泥棒」「盗人」を意味する言葉

160
88
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
160
88