23
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

nextjsで新たなbuildをデプロイした際、既に古いページをキャッシュしていたクライアントへ強制的にページ更新させる方法

Last updated at Posted at 2020-10-15

問題

nextjsはSPAなのでページ間移動でHTTPのロードが発生しない。
これによって古いページをキャッシュしていたクライアントは、新たなバージョンがサーバーにデプロイされている状態でも、明示的にページリロードをしてもらわない限りは古いページに居残ってしまう。

解決方法

  1. generateBuildIdを使う(gitのcommit IDを自動で付与するpluginが便利)
  2. 下のようにHTMLソースからbuildIdを読み出す
  3. buildId配下のパスが存在すれば新たなbuildはデプロイされていない
  4. buildId配下のパスが存在しなければ新たなbuildと考えられる
  5. ページを移動した後にこのチェックを行い、リロードが必要な場合はdocument.location.reload()でcacheを無視して読み込む
  6. こうすることによってクライアント側は意識することなく、ページを移動した際に新たなバージョンであれば自動的にページ更新をして最新の状態に保てる。

事前設定

next.config.js
// https://github.com/nexdrew/next-build-id
const nextBuildId = require("next-build-id")

module.exports = {
  generateBuildId: () => nextBuildId({ dir: __dirname })
}

HTMLソース

view-source_ubuntu_local_6850.png

変更点

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
23
13
0

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
23
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?