问题
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