Cache-busting page-data.json files in Gatsby

烈酒焚心 提交于 2020-12-12 07:37:32

问题


I have a gatsby generated website on which I have replaced the contents of the homepage.

Unfortunately the previous version was serving up /page-data/index/page-data.json with the incorrect cache-control headers, resulting in /page-data/index/page-data.json being cached on client browsers (and stale data being shown unless force-refreshed). I have also discovered that page-data.json files are not hashed (see https://github.com/gatsbyjs/gatsby/issues/15080).

I've updated the cache-control headers so that versions from now on will not be cached but this does not help with clients that have the cached version now.

What can I do to force clients to request the latest version of this file?


回答1:


I got there in the end... This is in my gatsby-node.js

const hash = md5(`${new Date().getTime()}`)

const addPageDataVersion = async file => {
  const stats = await util.promisify(fs.stat)(file)
  if (stats.isFile()) {
    console.log(`Adding version to page-data.json in ${file}..`)
    let content = await util.promisify(fs.readFile)(file, 'utf8')
    const result = content.replace(
      /page-data.json(\?v=[a-f0-9]{32})?/g,
      `page-data.json?v=${hash}`
    )
    await util.promisify(fs.writeFile)(file, result, 'utf8')
  }
}

exports.onPostBootstrap = async () => {
  const loader = path.join(__dirname, 'node_modules/gatsby/cache-dir/loader.js')
  await addPageDataVersion(loader)
}

exports.onPostBuild = async () => {
  const publicPath = path.join(__dirname, 'public')
  const htmlAndJSFiles = glob.sync(`${publicPath}/**/*.{html,js}`)
  for (let file of htmlAndJSFiles) {
    await addPageDataVersion(file)
  }
}



回答2:


Check out this tutorial, this is the solution I've been using.

https://examsworld.co.in/programming/javascript/how-to-cache-bust-a-react-app/

It's basically a wrapper component that checks to see if the browser's cached version matches the build's version number in package.json. If it doesn't, it clears the cache and reloads the page.

This is how I'm using it.

gatsby-browser.js

    export const wrapRootElement = ({ element }) => (
     <CacheBuster>
        {({ loading, isLatestVersion, refreshCacheAndReload }) => {
          if (loading) return null
          if (!loading && !isLatestVersion) {
            // You can decide how and when you want to force reload
            refreshCacheAndReload()
          }
          return <AppProvider>{element}</AppProvider>
        }}
      </CacheBuster>
    )

CacheBuster.js

import React from 'react'
import packageJson from '../../package.json'

global.appVersion = packageJson.version

// version from response - first param, local version second param
const semverGreaterThan = (versionA, versionB) => {
  const versionsA = versionA.split(/\./g)

  const versionsB = versionB.split(/\./g)
  while (versionsA.length || versionsB.length) {
    const a = Number(versionsA.shift())

    const b = Number(versionsB.shift())
    // eslint-disable-next-line no-continue
    if (a === b) continue
    // eslint-disable-next-line no-restricted-globals
    return a > b || isNaN(b)
  }
  return false
}

class CacheBuster extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      loading: true,
      isLatestVersion: false,
      refreshCacheAndReload: () => {
        console.info('Clearing cache and hard reloading...')
        if (caches) {
          // Service worker cache should be cleared with caches.delete()
          caches.keys().then(function(names) {
            for (const name of names) caches.delete(name)
          })
        }

        // delete browser cache and hard reload
        window.location.reload(true)
      },
    }
  }

  componentDidMount() {
    fetch('/meta.json')
      .then(response => response.json())
      .then(meta => {
        const latestVersion = meta.version
        const currentVersion = global.appVersion

        const shouldForceRefresh = semverGreaterThan(
          latestVersion,
          currentVersion
        )
        if (shouldForceRefresh) {
          console.info(
            `We have a new version - ${latestVersion}. Should force refresh`
          )
          this.setState({ loading: false, isLatestVersion: false })
        } else {
          console.info(
            `You already have the latest version - ${latestVersion}. No cache refresh needed.`
          )
          this.setState({ loading: false, isLatestVersion: true })
        }
      })
  }

  render() {
    const { loading, isLatestVersion, refreshCacheAndReload } = this.state
    const { children } = this.props
    return children({ loading, isLatestVersion, refreshCacheAndReload })
  }
}

export default CacheBuster

generate-build-version.js

const fs = require('fs')
const packageJson = require('./package.json')

const appVersion = packageJson.version

const jsonData = {
  version: appVersion,
}

const jsonContent = JSON.stringify(jsonData)

fs.writeFile('./static/meta.json', jsonContent, 'utf8', function(err) {
  if (err) {
    console.log('An error occured while writing JSON Object to meta.json')
    return console.log(err)
  }

  console.log('meta.json file has been saved with latest version number')
})

and in your package.json add these scripts

 "generate-build-version": "node generate-build-version",
 "prebuild": "npm run generate-build-version"




回答3:


Outside of going to each client browser individually and clearing their cache there isn't any other means of invalidating all of your client's caches. If your webpage is behind a CDN you can control, you may be able to force invalidation at the CDN-level so new clients will always be routed to the up to date webpage even if the CDN had a pre-existing, outdated copy cached.



来源:https://stackoverflow.com/questions/58223669/cache-busting-page-data-json-files-in-gatsby

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!