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

Remove special error script handling (#3849)

* Remove special error script handling.
As a result of that, we can't detect 500 errors and buildIdMismatch via client side.

* Fix failing test cases.

* Refactor the code base.

* Remove Router.onAppUpdated
This commit is contained in:
Arunoda Susiripala 2018-02-21 23:11:25 +05:30 committed by GitHub
parent 832425e67e
commit a32b22bb2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 60 additions and 72 deletions

View file

@ -0,0 +1,25 @@
# Router.onAppUpdated is removed
Due to [this bug fix](https://github.com/zeit/next.js/pull/3849), we had to remove the `Router.onAppUpdated` hook. But the default functionality of this feature is still in effect.
We use this hook to detect a new app deployment when switching pages and act accordingly. Although there are many things you can do in this hook, it's often used to navigate the page via the server as shown below:
```js
Router.onAppUpdated = function(nextRoute) {
location.href = nextRoute
}
```
In this hook, you can't wait for a network request or a promise to get resolved. And you can't block the page navigation. So, the things you can do is limited.
One real use of this hook is to persist your application state to local-storage before the page navigation. For that, you can use the [`window.onbeforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload) hook instead.
This is code for that:
```js
window.onbeforeunload = function(e) {
// Get the application state (usually from a store like Redux)
const appState = {}
localStorage.setItem('app-state', JSON.stringify(appState));
};
```

View file

@ -76,6 +76,7 @@ export default class PageLoader {
script.src = url
script.onerror = () => {
const error = new Error(`Error when loading route: ${route}`)
error.code = 'PAGE_LOAD_ERROR'
this.pageRegisterEvents.emit(route, { error })
}

View file

@ -1,5 +1,6 @@
/* global window */
import _Router from './router'
import { execOnce } from '../utils'
const SingletonRouter = {
router: null, // holds the actual router instance
@ -53,6 +54,18 @@ routerEvents.forEach((event) => {
})
})
const warnAboutRouterOnAppUpdated = execOnce(() => {
console.warn(`Router.onAppUpdated is removed - visit https://err.sh/next.js/no-on-app-updated-hook for more information.`)
})
Object.defineProperty(SingletonRouter, 'onAppUpdated', {
get () { return null },
set () {
warnAboutRouterOnAppUpdated()
return null
}
})
function throwIfNoRouter () {
if (!SingletonRouter.router) {
const message = 'No router instance found.\n' +
@ -85,15 +98,6 @@ 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 {
console.warn(`An app update detected. Loading the SSR version of "${nextRoute}"`)
window.location.href = nextRoute
}
}
export function _rewriteUrlForNextExport (url) {
const [, hash] = url.split('#')
url = url.replace(/#.*/, '')

View file

@ -5,7 +5,7 @@ import EventEmitter from '../EventEmitter'
import shallowEquals from '../shallow-equals'
import PQueue from '../p-queue'
import { loadGetInitialProps, getURL, warn, execOnce } from '../utils'
import { _notifyBuildIdMismatch, _rewriteUrlForNextExport } from './'
import { _rewriteUrlForNextExport } from './'
export default class Router {
constructor (pathname, query, as, { pageLoader, Component, ErrorComponent, err } = {}) {
@ -212,20 +212,12 @@ export default class Router {
this.components[route] = routeInfo
} catch (err) {
if (err.buildIdMismatched) {
// Now we need to reload the page or do the action asked by the user
_notifyBuildIdMismatch(as)
// We also need to cancel this current route change.
// We do it like this.
err.cancelled = true
return { error: err }
}
if (err.code === 'PAGE_LOAD_ERROR') {
// If we can't load the page it could be one of following reasons
// 1. Page doesn't exists
// 2. Page does exist in a different zone
// 3. Internal error while loading the page
if (err.statusCode === 404) {
// If there's 404 error for the page, it could be due to two reasons.
// 1. Page is not exists
// 2. Page is exists in a different zone
// We are not sure whether this is actual 404 or exists in a different zone.
// So, doing a hard reload is the proper way to deal with this.
window.location.href = as

View file

@ -106,8 +106,7 @@
"webpack-dev-middleware": "1.12.0",
"webpack-hot-middleware": "2.21.0",
"webpack-sources": "1.1.0",
"write-file-webpack-plugin": "4.2.0",
"xss-filters": "1.2.7"
"write-file-webpack-plugin": "4.2.0"
},
"devDependencies": {
"@taskr/babel": "1.1.0",

View file

@ -491,7 +491,6 @@ Here's a list of supported events:
- `onRouteChangeComplete(url)` - Fires when a route changed completely
- `onRouteChangeError(err, url)` - Fires when there's an error when changing routes
- `onBeforeHistoryChange(url)` - Fires just before changing the browser's history
- `onAppUpdated(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`.
@ -519,17 +518,6 @@ 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
}
```
##### Shallow Routing
<p><details>

View file

@ -196,9 +196,7 @@ export default class Server {
'/_next/:buildId/page/_error.js': async (req, res, params) => {
if (!this.handleBuildId(params.buildId, res)) {
const error = new Error('INVALID_BUILD_ID')
const customFields = { buildIdMismatched: true }
return await renderScriptError(req, res, '/_error', error, customFields, this.renderOpts)
return await renderScriptError(req, res, '/_error', error)
}
const p = join(this.dir, `${this.dist}/bundles/pages/_error.js`)
@ -211,22 +209,19 @@ export default class Server {
if (!this.handleBuildId(params.buildId, res)) {
const error = new Error('INVALID_BUILD_ID')
const customFields = { buildIdMismatched: true }
return await renderScriptError(req, res, page, error, customFields, this.renderOpts)
return await renderScriptError(req, res, page, error)
}
if (this.dev) {
try {
await this.hotReloader.ensurePage(page)
} catch (error) {
return await renderScriptError(req, res, page, error, {}, this.renderOpts)
return await renderScriptError(req, res, page, error)
}
const compilationErr = await this.getCompilationError()
if (compilationErr) {
const customFields = { statusCode: 500 }
return await renderScriptError(req, res, page, compilationErr, customFields, this.renderOpts)
return await renderScriptError(req, res, page, compilationErr)
}
}
@ -235,7 +230,7 @@ export default class Server {
// [production] If the page is not exists, we need to send a proper Next.js style 404
// Otherwise, it'll affect the multi-zones feature.
if (!(await fsAsync.exists(p))) {
return await renderScriptError(req, res, page, { code: 'ENOENT' }, {}, this.renderOpts)
return await renderScriptError(req, res, page, { code: 'ENOENT' })
}
await this.serveStatic(req, res, p)

View file

@ -12,7 +12,8 @@ import Head, { defaultHead } from '../lib/head'
import App from '../lib/app'
import ErrorDebug from '../lib/error-debug'
import { flushChunks } from '../lib/dynamic'
import xssFilters from 'xss-filters'
const logger = console
export async function render (req, res, pathname, query, opts) {
const html = await renderToHTML(req, res, pathname, query, opts)
@ -120,36 +121,19 @@ async function doRender (req, res, pathname, query, {
return '<!DOCTYPE html>' + renderToStaticMarkup(doc)
}
export async function renderScriptError (req, res, page, error, customFields, { dev }) {
export async function renderScriptError (req, res, page, error) {
// Asks CDNs and others to not to cache the errored page
res.setHeader('Cache-Control', 'no-store, must-revalidate')
// prevent XSS attacks by filtering the page before printing it.
page = xssFilters.uriInSingleQuotedAttr(page)
res.setHeader('Content-Type', 'text/javascript')
if (error.code === 'ENOENT') {
res.end(`
window.__NEXT_REGISTER_PAGE('${page}', function() {
var error = new Error('Page does not exist: ${page}')
error.statusCode = 404
return { error: error }
})
`)
res.statusCode = 404
res.end('404 - Not Found')
return
}
const errorJson = {
...serializeError(dev, error),
...customFields
}
res.end(`
window.__NEXT_REGISTER_PAGE('${page}', function() {
var error = ${JSON.stringify(errorJson)}
return { error: error }
})
`)
logger.error(error.stack)
res.statusCode = 500
res.end('500 - Internal Error')
}
export function sendHTML (req, res, html, method, { dev }) {

View file

@ -127,7 +127,7 @@ describe('Production Usage', () => {
// Let the browser to prefetch the page and error it on the console.
await waitFor(3000)
const browserLogs = await browser.log('browser')
expect(browserLogs[0].message).toMatch(/Page does not exist: \/no-such-page/)
expect(browserLogs[0].message).toMatch(/\/no-such-page.js - Failed to load resource/)
// When we go to the 404 page, it'll do a hard reload.
// So, it's possible for the front proxy to load a page from another zone.