# Next.jsのData Fetching
Next.jsのウェブサイトのData Fetching というページに沿ってやっていきます。(開いた瞬間ページ凄い長くてそっ閉じしようと思いましたが…笑)
# getStaticProps
getStaticPropsをasyncでコールするとNext.jsがビルドしてHTMLを吐き出す時によしなにやってくれますよっていうヤツ。
export async function getStaticProps(context) {
return {
props: {}, // propsとしてpageコンポーネントに渡される
}
}
この context
パラメーターは以下のようなキーを含むオブジェクト
params
はDynamic Routesで使う時のアレで、例えば [id].js だったら、{ id: … } っていう形になるのだそうですが、その辺はまたDynamic Routingのドキュメントで詳しく取り扱いますとのことですが、getStaticPathsと一緒に使う感じになりますやねpreview
はプレビューモードの時はtrueでそうじゃない時はundefinedだそうですが、こちらも詳細はPreviewのドキュメントで、とのことpreviewData
はsetPreviewData
でセットされたデータを含むらしいですが、こちらも詳細は(rylocale
はアクティブなロケール(if enabled)locales
はサポートされた全てのロケール(if enabled)defaultLocale
はデフォルトのロケール(if enabled)
previewとlocale周りはよく分かりませんでしたが、とりあえずは今のところは動的なルーティングのIDがparamsに入ってくる、くらいのことを知ってりゃいいのかな。
で、getStaticProps
は以下を返す、と。
props
はrequiredなオブジェクトでpageコンポートによって受け取られるけど、これはシリアライズ可能なオブジェクトrevalidate
はオプションだけど、何秒後にページをre-generationするかっていう設定らしい。Incremental Static Regenerationっていう章で詳しく語られるとのことnotFound
もオプション。404を返すかどうか的な。以下の例だと、fetchしにいったけどデータが無かった場合はnotFound: trueになって、それがreturnされてページコンポーネントに渡る。
export async function getStaticProps(context) {
const res = await fetch(`https://.../data`)
const data = await res.json()
if (!data) {
return {
notFound: true,
}
}
return {
props: { data }, // will be passed to the page component as props
}
}
そして、notFoundは fallback: false な場合は必要なく、getStaticPahtsで返ってきたパスだけpre-renderされますよ、とのこと。
redirect
は{destination: string, permanent: boolean} で設定できるオプション。そしてpermanentの代わりにstatusCodeというプロパティも使うことができます、と。↓はgetStaticPropsでデータが無かった場合にトップページに302なリダイレクトをする例。
export async function getStaticProps(context) {
const res = await fetch(`https://...`)
const data = await res.json()
if (!data) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: { data }, // will be passed to the page component as props
}
}
そして、何やらいろいろ注意書きがあるのだけど、ビルド時のリダイレクトは許可されていないけどnext.config.jsっていうファイルでなんか出来るらしいとか、getStaticPropsの中でインポートされたやつはクライアント側ではバンドルされないとか、fetch()はgetStaticPropsの中で呼ぶのでなくてAPI用のやつをインポートすべきだとかいろいろあるみたいですが、先に進みます。
# Simpleなサンプル
// posts はビルド時に実行される getStaticProps() で渡される
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// ビルド時にサーバー側で実行されるもの
// クライアント側では実行されないのでデータベースへの直接のクエリとかもOK
export async function getStaticProps() {
// ここで外部APIを呼ぶ。データ取得用のライブラリも使っていこう的な
const res = await fetch('https://.../posts')
const posts = await res.json()
// { props: { posts } }をリターンすることでBlogコンポーネントはpostsをビルド時に受け取る
return {
props: {
posts,
},
}
}
export default Blog
# いつgetStaticProps使うの?
これは今までの繰り返しな感じなので省略
# TypeScriptではGetStaticPropが使える
nextからGetStaticPropsをインポートする感じで。 (っていうか当たり前のように出てくるけど、自分みたいにコールバック地獄してた頃に現場から足を洗ったオジサンからすると、Promiseとかasync/awaitはどこかで本とか読んで勉強されるとよろしいかと思います…)
import { GetStaticProps } from 'next'
export const getStaticProps: GetStaticProps = async (context) => {
// ...
}
なんか型推論が〜とかってのもあるのね。InferGetStaticPropsType<typeof getStaticProps>
ってやつ。
import { InferGetStaticPropsType } from 'next'
type Post = {
author: string
content: string
}
export const getStaticProps = async () => {
const res = await fetch('https://.../posts')
const posts: Post[] = await res.json()
return {
props: {
posts,
},
}
}
function Blog({ posts }: InferGetStaticPropsType<typeof getStaticProps>) {
// postsをPostの配列に。
}
export default Blog
# Incremental Static Regeneration
Next.js 9.5以降の機能らしいですが。
『static content can also be dynamic』って何言っとんねん?(笑)みたいな感じではありますが、一定間隔でHTMLを改めて作り直すことができるっていう機能。確かに物凄い勢いでアクセスされるサービスで、ゆうても10秒に一回とかHTML作り直すのでよければそれの方がコスト効率イイっていうのは結構ありそう。なんだろうな、、リアルタイムなゲームの大戦みたいのは無理だけど、インターネットショッピングにおける在庫の反映とかだったらOKじゃね?みたいな割り切り的な話。
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// serverlessなファンクションとして再実行が有効で新しいリクエストが来た場合
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// ここで再生成する間隔を秒単位で指定
revalidate: 1, // In seconds
}
}
export default Blog
そして、これは fallback: true とパーフェクトにマッチする、と。記事が後から追加されても、オンデマンドでいけるからね。
これなら、、
- スパイクがきても安定して早くコンテンツを配信できる
- 例えばバックグラウンドのre-generationに失敗しても古いページはそのままいるわけで、オフラインになることがない
- データベースとかバックエンドにグワっとアクセスが集中することがないので安心
# ファイルを読むのは process.cwd() を使う
getStaticPropsの中でファイルにアクセスできるけど、その場合フルパスを指定しないといけない。Next.jsのコンパイルは今いるディレクトリとは違うところにファイルを生成するので __dirname とか使えない。その代わりに process.cwd() っていうのが。
↓これ、あー、そーっすかーって感じですが、ここでも、全部ファイル読み込み処理が終わってからっていうので await Promise.all(posts)
な感じで。
import { promises as fs } from 'fs'
import path from 'path'
// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>
<h3>{post.filename}</h3>
<p>{post.content}</p>
</li>
))}
</ul>
)
}
export async function getStaticProps() {
const postsDirectory = path.join(process.cwd(), 'posts')
const filenames = await fs.readdir(postsDirectory)
const posts = filenames.map(async (filename) => {
const filePath = path.join(postsDirectory, filename)
const fileContents = await fs.readFile(filePath, 'utf8')
// ここでマークダウンからHTMLに変換するとか、
// ファイルから取得したデータをホゲホゲする
return {
filename,
content: fileContents,
}
})
// で、Blogコンポーネントに渡すためのreturnなんだけど、
return {
props: {
posts: await Promise.all(posts),
},
}
}
export default Blog
# Technical details
技術的な詳細っていうことなのですが、そんなに特質すべきアレでもなさそうな感じですが、レンダリングはHTMLだけじゃなくてgetStaticPropsの結果をJSONファイルとして生成することもできるのだそうです。で、そのJSONはクライアント側で next/link とか next/router とかで使われるらしいのですが、まだあんまり分かってないですが便利そうな予感はしますねw
開発モード(next dev)だと毎回リクエスト毎にgetStaticPropsが呼ばれるそうです。
あとはPreviewモードに関しては、その名の通りというか、公開をする前にドラフトをプレビューするみたいな時にビルド時ではなくリクエスト毎にページをレンダリングしたいとかっていうので使えるそうです。
# getStaticPaths
ビルド時にHTMLを生成する用のリストを〜っていうヤツ。
export async function getStaticPaths() {
return {
paths: [
{ params: { ... } } // See the "paths" section below
],
fallback: true or false // See the "fallback" section below
};
}
# paths
は必須
例えば以下のように returnすると、
return {
paths: [
{ params: { id: '1' } },
{ params: { id: '2' } }
],
fallback: ...
}
pages/posts/[id].js から posts/1 と posts/2 が生成される感じ。
- ページが pages/posts/[postId]/[commentId] だとすると、
params
はpostId
とcommentId
を保持する必要がある - URLにslugがある場合は['foo', 'bar']って形にすると /foo/bar という感じにできる
# fallback
も必須
fallback: falseだと、getStaticPathsで返されないパスは404になる。 例えば pages/posts/[id].js が↓こんな感じ
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// まだHTMLが生成されていない場合はgetStaticPropsが終わるまでこれが表示される
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
// ビルド時に呼ばれる
export async function getStaticPaths() {
return {
// `/posts/1` と `/posts/2` だけビルド時に生成
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
// 例えば `/posts/3` が追加されるようにするための設定
fallback: true,
}
}
// こちらもビルド時に呼ばれる
export async function getStaticProps({ params }) {
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
return {
props: { post },
revalidate: 1,
}
}
export default Post
で、どんなに時に fallback: true すると便利かというと、、大量に商材を扱うeコマースサイトとか。なぜかというと事前に全商品ビルドとかするとエラいことになってしまうため。
とは言え、fallback: trueがコンテンツを更新するようなre-generateをするわけではないので、それ系の話はIncremental Static Regenerationを見てね、と。
で、fallbackがblokingだと、404にはならずHTMLのgenerateが終わってからページ全体が表示されるような感じになるっぽい。あんまり使う機会ないかもなぁ。また更新に関してはここでもIncremental Static Regenerationを、と。
# TypeScriptのGetStaticPaths
こちらもnextからimportして↓のような感じになるのだそうです。
import { GetStaticPaths } from 'next'
export const getStaticPaths: GetStaticPaths = async () => {
// ...
}
GetStaticPropsと同様でdevの時はリクエストごとに〜ってところ。
# getServerSideProps
(Server-side Rendering)
なんというか、Server-sideでリクエストごとに〜っていうだけで、あんまり変わらんのかな、と。(ザザっと流し読みしただけだけど…)
# クライアントからのデータの取得
ページでコンテンツが頻繁に更新されるとして、Static GenerationしておいてOKなところもあるような、一部分だけリアリタイムにデータを都度取りに行きたい時。
# SWR
いきなり3文字英語なんやねんって感じだけどw データを取得(fetch)するための仕掛けとしてのReact hookということで。
↓こんな感じにするとイイよってことですが、詳しくは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>
}