問題
nextjsはSPAなのでページ間移動でHTTPのロードが発生しない。
これによって古いページをキャッシュしていたクライアントは、新たなバージョンがサーバーにデプロイされている状態でも、明示的にページリロードをしてもらわない限りは古いページに居残ってしまう。
解決方法
- generateBuildIdを使う(gitのcommit IDを自動で付与するpluginが便利)
- 下のようにHTMLソースからbuildIdを読み出す
- buildId配下のパスが存在すれば新たなbuildはデプロイされていない
- buildId配下のパスが存在しなければ新たなbuildと考えられる
- ページを移動した後にこのチェックを行い、リロードが必要な場合は
document.location.reload()
でcacheを無視して読み込む - こうすることによってクライアント側は意識することなく、ページを移動した際に新たなバージョンであれば自動的にページ更新をして最新の状態に保てる。
事前設定
next.config.js
// https://github.com/nexdrew/next-build-id
const nextBuildId = require("next-build-id")
module.exports = {
generateBuildId: () => nextBuildId({ dir: __dirname })
}
HTMLソース
変更点
basePathが設定されていることを考慮して
yarn add proper-url-join
src/utils/useBuildId.ts
import { useRouter } from "next/router"
import urlJoin from "proper-url-join"
import React from "react"
const useBuildId = () => {
const { basePath } = useRouter()
const shouldReload = React.useCallback((): boolean => {
if (process.env.NODE_ENV != "production") {
return false
}
const buildId = JSON.parse(
document.querySelector("#__NEXT_DATA__").textContent
).buildId
const request = new XMLHttpRequest()
request.open(
"HEAD",
urlJoin(basePath, `/_next/static/${buildId}/_buildManifest.js`),
false
)
request.setRequestHeader("Pragma", "no-cache")
request.setRequestHeader("Cache-Control", "no-cache")
request.setRequestHeader(
"If-Modified-Since",
"Thu, 01 Jun 1970 00:00:00 GMT"
)
request.send(null)
return request.status === 404
}, [])
return {
shouldReload,
}
}
export default useBuildId
pages/_app.tsx
import type { AppProps } from "next/app"
import { useRouter } from "next/router"
import React from "react"
import { useBuildId } from "src/utils"
function MyApp({ Component, pageProps }: AppProps) {
const { shouldReload } = useBuildId()
const router = useRouter()
React.useEffect(() => {
const handleRouteChange = (url: string) => {
if (shouldReload()) {
document.location.reload()
}
}
router.events.on("routeChangeComplete", handleRouteChange)
return () => {
router.events.off("routeChangeComplete", handleRouteChange)
}
}, [shouldReload])
return <Component {...pageProps} />
}
export default MyApp