From 085b2f806afd4d16e6f4754cce62e88d7bd47518 Mon Sep 17 00:00:00 2001 From: George Pantazis Date: Sat, 31 Mar 2018 07:21:51 -0700 Subject: [PATCH] Add Router method to execute custom logic before popstate events (#3956) * Add router method to inject code before popstate events * Default _beforePopState, return true * Fix link in README * Re-order `if` statements per feedback --- lib/router/index.js | 2 +- lib/router/router.js | 11 +++++++++++ readme.md | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/router/index.js b/lib/router/index.js index 665cde6d..94586c78 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -15,8 +15,8 @@ const SingletonRouter = { // Create public properties and methods of the router in the SingletonRouter const propertyFields = ['components', 'pathname', 'route', 'query', 'asPath'] -const coreMethodFields = ['push', 'replace', 'reload', 'back', 'prefetch'] const routerEvents = ['routeChangeStart', 'beforeHistoryChange', 'routeChangeComplete', 'routeChangeError'] +const coreMethodFields = ['push', 'replace', 'reload', 'back', 'prefetch', 'beforePopState'] propertyFields.forEach((field) => { // Here we need to use Object.defineProperty because, we need to return diff --git a/lib/router/router.js b/lib/router/router.js index 11c33603..ce3415c2 100644 --- a/lib/router/router.js +++ b/lib/router/router.js @@ -40,6 +40,7 @@ export default class Router { this.subscriptions = new Set() this.componentLoadCancel = null this.onPopState = this.onPopState.bind(this) + this._beforePopState = () => true if (typeof window !== 'undefined') { // in order for `e.state` to work on the `onpopstate` event @@ -66,6 +67,12 @@ export default class Router { return } + // If the downstream application returns falsy, return. + // They will then be responsible for handling the event. + if (!this._beforePopState(e.state)) { + return + } + const { url, as, options } = e.state this.replace(url, as, options) } @@ -265,6 +272,10 @@ export default class Router { this.notify(data) } + beforePopState (cb) { + this._beforePopState = cb + } + onlyAHashChange (as) { if (!this.asPath) return false const [ oldUrlNoHash, oldHash ] = this.asPath.split('#') diff --git a/readme.md b/readme.md index dc89540b..8d5a278b 100644 --- a/readme.md +++ b/readme.md @@ -458,6 +458,31 @@ export default () => ``` +#### Intercepting `popstate` + +In some cases (for example, if using a [custom router](#custom-server-and-routing)), you may wish +to listen to `popstate` and react before the router acts on it. +For example, you could use this to manipulate the request, or force an SSR refresh. + +```jsx +import Router from 'next/router' + +Router.beforePopState(({ url, as, options }) => { + // I only want to allow these two routes! + if (as !== "/" || as !== "/other") { + // Have SSR render bad routes as a 404. + window.location.href = as + return false + } + + return true +}); +``` + +If you return a falsy value from `beforePopState`, `Router` will not handle `popstate`; +you'll be responsible for handling it, in that case. +See [Disabling File-System Routing](#disabling-file-system-routing). + Above `Router` object comes with the following API: - `route` - `String` of the current route @@ -466,6 +491,7 @@ Above `Router` object comes with the following API: - `asPath` - `String` of the actual path (including the query) shows in the browser - `push(url, as=url)` - performs a `pushState` call with the given url - `replace(url, as=url)` - performs a `replaceState` call with the given url +- `beforePopState(cb=function)` - intercept popstate before router processes the event. The second `as` parameter for `push` and `replace` is an optional _decoration_ of the URL. Useful if you configured custom routes on the server. @@ -753,6 +779,13 @@ module.exports = { } ``` +Note that `useFileSystemPublicRoutes` simply disables filename routes from SSR; client-side routing +may still access those paths. If using this option, you should guard against navigation to routes +you do not want programmatically. + +You may also wish to configure the client-side Router to disallow client-side redirects to filename +routes; please refer to [Intercepting `popstate`](#intercepting-popstate). + #### Dynamic assetPrefix Sometimes we need to set the `assetPrefix` dynamically. This is useful when changing the `assetPrefix` based on incoming requests.