1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Handle BUILD_ID mismatch error (#1224)

* Reload the page if the buildIds are mismatch.

* Reload the browser with main.js and commons.js buildId mismatch.

* Implement proper reloading with an API to persist the state.

* Add some tests for force reload.

* Change _reload to _forceReload.

* Add a section about reload hooks to the README.

* Allow to add a hook to handle BUILD_ID mismatch.

* Remove readme docs.

* Do not show a custom error to the user.

* Cancel the routing when there's a BUILD_ID mismatch.

* Fix a typo.

* Passing route to SingletonRouter.onBuildIdMismatch

* Handle buildId mismatch automatically.
This commit is contained in:
Arunoda Susiripala 2017-02-21 05:18:17 +05:30 committed by Guillermo Rauch
parent 20c7d98efe
commit 0bd250f4aa
5 changed files with 65 additions and 15 deletions

View file

@ -1,3 +1,4 @@
/* global window, location */
import _Router from './router'
const SingletonRouter = {
@ -75,3 +76,11 @@ export const createRouter = function (...args) {
// Export the actual Router class, which is usually used inside the server
export const Router = _Router
export function _notifyBuildIdMismatch (nextRoute) {
if (SingletonRouter.onAppUpdated) {
SingletonRouter.onAppUpdated(nextRoute)
} else {
location.href = nextRoute
}
}

View file

@ -6,6 +6,7 @@ import evalScript from '../eval-script'
import shallowEquals from '../shallow-equals'
import PQueue from '../p-queue'
import { loadGetInitialProps, getLocationOrigin } from '../utils'
import { _notifyBuildIdMismatch } from './'
// Add "fetch" polyfill for older browsers
if (typeof window !== 'undefined') {
@ -75,7 +76,7 @@ export default class Router extends EventEmitter {
data,
props,
error
} = await this.getRouteInfo(route, pathname, query)
} = await this.getRouteInfo(route, pathname, query, as)
if (error && error.cancelled) {
this.emit('routeChangeError', error, as)
@ -116,7 +117,7 @@ export default class Router extends EventEmitter {
data,
props,
error
} = await this.getRouteInfo(route, pathname, query)
} = await this.getRouteInfo(route, pathname, query, url)
if (error && error.cancelled) {
this.emit('routeChangeError', error, url)
@ -162,7 +163,7 @@ export default class Router extends EventEmitter {
this.emit('routeChangeStart', as)
const {
data, props, error
} = await this.getRouteInfo(route, pathname, query)
} = await this.getRouteInfo(route, pathname, query, as)
if (error && error.cancelled) {
this.emit('routeChangeError', error, as)
@ -189,11 +190,16 @@ export default class Router extends EventEmitter {
}
}
async getRouteInfo (route, pathname, query) {
async getRouteInfo (route, pathname, query, as) {
const routeInfo = {}
try {
const { Component, err, jsonPageRes } = routeInfo.data = await this.fetchComponent(route)
routeInfo.data = await this.fetchComponent(route, as)
if (!routeInfo.data) {
return null
}
const { Component, err, jsonPageRes } = routeInfo.data
const ctx = { err, pathname, query, jsonPageRes }
routeInfo.props = await this.getInitialProps(Component, ctx)
} catch (err) {
@ -229,7 +235,7 @@ export default class Router extends EventEmitter {
return this.prefetchQueue.add(() => this.fetchRoute(route))
}
async fetchComponent (route) {
async fetchComponent (route, as) {
let data = this.components[route]
if (data) return data
@ -240,6 +246,15 @@ export default class Router extends EventEmitter {
const jsonPageRes = await this.fetchRoute(route)
const jsonData = await jsonPageRes.json()
if (jsonData.buildIdMismatch) {
_notifyBuildIdMismatch(as)
const error = Error('Abort due to BUILD_ID mismatch')
error.cancelled = true
throw error
}
const newData = {
...loadComponent(jsonData),
jsonPageRes

View file

@ -310,6 +310,7 @@ Here's a list of supported events:
- `routeChangeStart(url)` - Fires when a route starts to change
- `routeChangeComplete(url)` - Fires when a route changed completely
- `routeChangeError(err, url)` - Fires when there's an error when changing routes
- `appUpdated(nextRoute)` - Fires when switching pages and there's a new version of the app
> Here `url` is the URL shown in the browser. If you call `Router.push(url, as)` (or similar), then the value of `url` will be `as`.
@ -337,6 +338,17 @@ Router.onRouteChangeError = (err, url) => {
}
```
If you change a route while in between a new deployment, we can't navigate the app via client side. We need to do a full browser navigation. We do it automatically for you.
But you can customize that via `Route.onAppUpdated` event like this:
```js
Router.onAppUpdated = (nextUrl) => {
// persist the local state
location.href = nextUrl
}
```
### Prefetching Pages
<p><details>

View file

@ -81,19 +81,30 @@ export default class Server {
},
'/_next/:buildId/main.js': async (req, res, params) => {
this.handleBuildId(params.buildId, res)
if (!this.handleBuildId(params.buildId, res)) {
throwBuildIdMismatchError()
}
const p = join(this.dir, '.next/main.js')
await this.serveStatic(req, res, p)
},
'/_next/:buildId/commons.js': async (req, res, params) => {
this.handleBuildId(params.buildId, res)
if (!this.handleBuildId(params.buildId, res)) {
throwBuildIdMismatchError()
}
const p = join(this.dir, '.next/commons.js')
await this.serveStatic(req, res, p)
},
'/_next/:buildId/pages/:path*': async (req, res, params) => {
this.handleBuildId(params.buildId, res)
if (!this.handleBuildId(params.buildId, res)) {
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ buildIdMismatch: true }))
return
}
const paths = params.path || ['index']
const pathname = `/${paths.join('/')}`
await this.renderJSON(req, res, pathname)
@ -277,14 +288,13 @@ export default class Server {
}
handleBuildId (buildId, res) {
if (this.dev) return
if (this.dev) return true
if (buildId !== this.renderOpts.buildId) {
const errorMessage = 'Build id mismatch!' +
'Seems like the server and the client version of files are not the same.'
throw new Error(errorMessage)
return false
}
res.setHeader('Cache-Control', 'max-age=365000000, immutable')
return true
}
getCompilationError (page) {
@ -298,3 +308,7 @@ export default class Server {
if (p) return errors.get(p)[0]
}
}
function throwBuildIdMismatchError () {
throw new Error('BUILD_ID Mismatched!')
}

View file

@ -1,6 +1,6 @@
/* global describe, test, expect */
export default function ({ app }) {
export default function (context) {
describe('Misc', () => {
test('finishes response', async () => {
const res = {
@ -9,7 +9,7 @@ export default function ({ app }) {
this.finished = true
}
}
const html = await app.renderToHTML({}, res, '/finish-response', {})
const html = await context.app.renderToHTML({}, res, '/finish-response', {})
expect(html).toBeFalsy()
})
})