とある休日
ワイ「あー、今日はヒマやな〜」
ワイ「ヒマやから、メールチェックでもしよか」
ワイ「パソコンでメールを受信して・・・っと」
ワイ「わ〜、また迷惑メールたくさん来とるわ〜」
娘「パパ、迷惑メールって何?」
ワイ「そのまんまや」
ワイ「迷惑なメールのことや」
娘「迷惑な・・・メール・・・」
娘「・・・あっ!」
娘「もしかして、パパみたいな男の人のこと!?」
ワイ「そうそう、もう存在そのものが迷惑な男性やから・・・コラッ!」
ワイ「誰が迷惑maleやねん」
娘「そんなことよりパパ」
ワイ「なんや、娘ちゃん」
ここから本題
娘「この前、私が日記を書くための個人ブログのサイトを作ったんだけどね」
娘「Next.jsを使って、SPAとして作ったの」
ワイ「ほうほう、シングルページアプリケーションやな」
娘「うん、それでね」
娘「CSRしてたところを、SGするように変えたら」
娘「サイトにアクセスした時のロード時間が、だいぶ速くなったの!」
ワイ「CSR・・・SG・・・?」
ワイ「娘ちゃん、一体なんの話をしてんのや?」
娘「えっとね」
CSRとは
娘「CSRっていうのはね」
- ブラウザ上から、JavaScriptを用いてAPIを叩く
- APIから取得したデータを、JavaScriptを用いて画面上に表示する
娘「↑こういうやつだよ!」
ワイ「ふーん、SPAでよくある普通の方法やな」
ワイ「SWR
とかを使って、下記のようにやるんやろ?」
ブログ記事の詳細ページの例
データ取得
// URLクエリから id を取得
const articleId = router.query.id
// APIからブログ記事を1件取得
const { data: article } = useSWR(`/api/articles/${articleId}`, fetcher)
データ表示
// APIからデータが読み込み終わるまでは「ロード中...」と表示される。
return (
<section>
{/* 記事のタイトル */}
<h1>{article ? article.title : "ロード中..."}</h1>
{/* 記事本文 */}
<div>{article ? article.body : "ロード中..."}</div>
</section>
)
ワイ「↑これをCSRって呼ぶんやな」
ワイ「知らんかったわ」
娘「ブラウザ・・・つまりクライアント側で情報を描画するから」
娘「クライアントサイド・レンダリング」
娘「だからCSRだね」
ワイ「なるほどなー」
ワイ「レンダリングって、描画って意味やもんな」
娘「うん」
娘「CSRの場合はね」
娘「サーバからHTMLを取得した時点では」
<main>
<section>
<h1>ロード中...</h1>
<div>ロード中...</div>
</section>
</main>
娘「↑こんな感じなの」
ワイ「ほー」
ワイ「この後、JavaScriptによって」
ワイ「記事のデータをAPIから取得してきて」
ワイ「ロード中...
の部分が書き換わるわけやな」
娘「そうなの」
ワイ「つまり、一連の流れで言うと・・・」
- ブラウザのアドレスバーにブログ記事のURLを打ち込む
- 閲覧者さんのパソコンに、HTMLの内容が届く
(この段階では、記事のタイトルや本文などは表示されていない) - JavaScriptが読み込まれ、API通信が起こる
- APIから記事のデータが届く
- JavaScriptによって、画面上に記事のタイトルや本文が描画される
ワイ「↑こんな感じか」
ワイ「まさにクライアントサイドでレンダリングしとるわけやな」
娘「うん」
ワイ「ほな、SGってのはどんな感じなん?」
SGとは
娘「SGの場合はね」
娘「事前にHTMLの内容を静的生成しておくの」
娘「サイトをサーバにデプロイする前1にね」
ワイ「へぇ〜」
娘「静的生成は、英語で言うとStatic Generation」
娘「だからSGっていうんだよ」
ワイ「ふーん」
ワイ「そのSGとやらをすると」
ワイ「ブラウザからサイトにアクセスした時のロード時間が速くなるん?」
娘「そうだよ」
娘「SGの場合は」
娘「サーバからHTMLを取得した時点で」
<main>
<section>
<h1>初投稿</h1>
<div>これは初めての記事です。</div>
</section>
</main>
娘「↑こんな風に」
娘「記事の内容もちゃんとHTML内に書かれてるの」
娘「だから、記事の内容が即座に画面に表示されるの」
ワイ「そうか、記事の内容も含めて事前に生成されたHTMLを」
ワイ「ダウンロードしてくるだけって感じなんやな」
娘「そう」
娘「だから、SGしておく方が爆速なの」
ワイ「なるほどなー」
ワイ「SGが良さそうなのは分かったけど、具体的にどうやってSGするん?」
娘「えっとね」
getStaticProps
とgetStaticPaths
を使う
娘「Next.jsを使っている場合、SGをするには」
娘「getStaticProps
とgetStaticPaths
を使うの」
ワイ「ほえ〜、なんか聞いたことあるな」
ワイ「ページのtsxファイルに」
ワイ「getStaticProps
とgetStaticPaths
っていう関数を書いて」
ワイ「export
してやればええんやっけ?」
娘「そう」
娘「さっきの例だと・・・」
getStaticProps
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に伝えるために」
const paths = [
{
params: {
id: "1",
},
},
{
params: {
id: "2",
},
},
{
params: {
id: "3",
},
},
{
params: {
id: "4",
},
},
{
params: {
id: "5",
},
},
]
娘「↑こういう、paths
っていう配列を作ってあげないといけないの」
ワイ「ほうほう」
ワイ「paths
に入れておいた分だけ」
ワイ「HTMLファイルが生成されるってことかいな?」
娘「そうだよ」
娘「今回の例だとgetStaticPaths
関数に書く内容は・・・」
getStaticPaths
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ファイルが静的生成されるの」
ワイ「なるほどなー」
- ビルド時に、APIからデータを取得する
- そのデータをもとに、記事の内容が書かれた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の内容が変わるため
- Twitterのタイムラインとか
- 例
SGのやり方(Next.jsの場合)
- ページのtsxファイルに
getStaticPaths
やgetStaticProps
という関数を書いてexport
する2
ワイ「↑こんな感じやな!」
娘「そうだね!」
娘「ほかにも、Next.jsを使っていると」
- ページ単位で「SGする or SGしない」を選択できる
- SGするページでも「一部だけCSR」ということもできる
- 例
- 記事の内容は、SGしておくことで爆速表示
- コメントの内容は、CSRすることで最新のものを反映
- 例
娘「↑こんなことも、割と簡単にできるよ」
ワイ「へ〜、SGとCSRを組み合わせることもできるんや」
娘「そうだよ〜」
OGPの表示にも有利
娘「SGは、OGPの表示にも有利だよ」
娘「HTMLを生成するときに、meta
タグのOGP情報も全部書いておけば」
娘「SPAでよくあるOGP問題も回避できるじゃん?」
ワイ「そうなんや」
ワイ「そういえば、SPAでOGP画像をいい感じに表示するには」
ワイ「SSRをしないと、上手く行かなかったりするもんな」
娘「そうだね」
娘「その辺りのことは、パパが素性を隠して書いたZennの記事を読み返してみるといいかもね」
娘「とにかく、事前に静的生成ができるケースなら」
娘「できるだけSGしておいた方がいいと思う!」
娘「Next.jsの公式ドキュメントにも」
Static Generation (Recommended)
娘「↑こう推奨
って書いてあるんだ」
ワイ「ほぇ〜、勉強になるわ」
その日の夜
ワイ「あー、今日も娘ちゃんのおかげで」
ワイ「またひとつ賢くなったで」
ワイ「もう少しでワイもチーフ・エンジニアになれるんちゃうか?」
ワイ「へへっ」
娘「パパ、すご〜い!」
娘「シーフ・エンジニアになるの?」
ワイ「いやシーフ3ちゃうわ」
ワイ「誰が給料泥棒エンジニアやねん」
〜おしまい〜
おまけ:誤解しそうなポイント
- 全ページを静的生成しても、MPA(マルチページ・アプリケーション)になるわけではありません
- 初回ロードした後のページ遷移は、SPA的な動作になります