この記事は便宜上next.jsのexampleを例に出しているが、apollo clientでSSRしていればわかる内容になっているはず。
デフォルトのままでは使えない?
next.js公式のtypescript例を参考に実装を進めていると、データの取得とキャッシュ周りで、あまりうれしくない挙動をしていた。
うれしい挙動とは
apolloのキャッシュ機能をうまく使えば、サクサク動く、サーバー&データベースにやさしいスーパーサービスが作れるはず。
しかし、しんどさと複数タブのことを考えると、少なくともブラウザ上で動くサービスに関しては、以下のような挙動がうれしいのではないだろうか?
- SSRしたときにデータを取得し、クライアントは送り付けられたデータをそのまま使う。
- ページ移動→戻ってくるときにはもう一度データを取りにいく。
exampleの挙動とは
with-typescript-graphqlを動かしてみると、データ取得のタイミングは以下の通りになっている。
- SSRしたときにデータを取得し、クライアントは常にそのデータを使う。
これはapollo clientのデフォルトの動きなので、別にexampleが悪いわけではないが、上記のうれしい挙動には程遠いものである。
どうすべきか
まず、クエリを投げる関数に、キャッシュを使わないよう、fetchPolicyオプションを渡す必要がある。
fetchPolicyのデフォルトの値はcache-first
であり、これはキャッシュがなければ(つまり初回)データを取りに行き、以後はキャッシュを使う、という設定だ。
これをcache-and-network
に変えよう。
cache-and-network
は、毎回新しいデータを取りに行き、更にキャッシュも活用してくれるという賢いやつだ。
「俺はキャッシュされるような、そんな安ぽい人間じゃない」と考えるストロングスタイルの方々は、network-only
を使おう。
以下はreactの例である。
qiitaの上で直接書いたものだから多少間違えてるかもしれないが、雰囲気で分かるはずだ。
import { useQuery } from '@apollo/react-hooks';
export default () => {
const {loading, error, data} = useQuery(QUERY, {fetchPolicy: 'cache-and-network'});
}
しかしこの方法ではいちいち指定することになってしまうのだが、当然デフォルト値を差し替えることもできるので、安心して下まで読んで欲しい。
現状確認
現在の挙動はこうなっているはずだ。
- SSRしたときにデータを取得する
- クライアントで表示した時、またデータを取得する。
- ページ移動→戻ってくるときにはもう一度データを取りにいく。
お気づきだろうか? このままだとユーザーがページを開いたときに二回データ取得が発生してしまい、データベースが爆発するのだ。
二回目のデータ取得は全く意味がないので、これを何とかしよう。
因みにこの問題については2017年にissueが建てられているのだが、一度修正されてからまた再発したのかは知らないが、まったく解決される気配はないし、クローズされている。
https://github.com/apollographql/apollo-client/issues/2119
不要なリクエストを削減
new ApolloClient
時に渡せる、ssrForceFetchDelay
オプションを使おう。
このオプションは指定されたミリ秒間、fetchをせずキャッシュを使うというものだ。
これによって、クライアントにページが表示された直後の不要なデータ取得をなくすことができる。
セットする時間は公式のドキュメントに倣って、100
にしておこう。
const client = new ApolloClient({
cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
link,
ssrForceFetchDelay: 100,
});
最終形態
どうすべきかの項で約束していたfetchPolicy
のデフォルト値の差し替えを追加した最終形態がこれだ。
const client = new ApolloClient({
cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
link,
ssrForceFetchDelay: 100,
// これでfetchPolicyのデフォルト値を変えられる
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network'
},
},
});
これであなたの
import { useQuery } from '@apollo/react-hooks';
export default () => {
const {loading, error, data} = useQuery(QUERY);
}
は
- SSRしたときにデータを取得し、クライアントは送り付けられたデータをそのまま使う。
- ページ移動→戻ってくるときにはもう一度データを取りにいく。
になる。