Skip to content

Commit

Permalink
Implement isFallback Router Property (#10539)
Browse files Browse the repository at this point in the history
This adds a `isFallback` property to detect if the page is being rendered in "fallback" mode or normal mode.
Accessed via the `useRouter()` hook.

---

Closes #10527
  • Loading branch information
Timer committed Feb 15, 2020
1 parent ae9b13e commit 6d5c487
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 7 deletions.
1 change: 1 addition & 0 deletions packages/next/client/index.js
Expand Up @@ -206,6 +206,7 @@ export default async ({ webpackHMR: passedWebpackHMR } = {}) => {
Component,
wrapApp,
err: initialErr,
isFallback,
subscription: ({ Component, props, err }, App) => {
render({ App, Component, props, err })
},
Expand Down
9 changes: 8 additions & 1 deletion packages/next/client/router.ts
Expand Up @@ -29,7 +29,14 @@ const singletonRouter: SingletonRouterBase = {
}

// Create public properties and methods of the router in the singletonRouter
const urlPropertyFields = ['pathname', 'route', 'query', 'asPath', 'components']
const urlPropertyFields = [
'pathname',
'route',
'query',
'asPath',
'components',
'isFallback',
]
const routerEvents = [
'routeChangeStart',
'beforeHistoryChange',
Expand Down
8 changes: 8 additions & 0 deletions packages/next/next-server/lib/router/router.ts
Expand Up @@ -49,6 +49,7 @@ export type NextRouter = BaseRouter &
| 'prefetch'
| 'beforePopState'
| 'events'
| 'isFallback'
>

type RouteInfo = {
Expand Down Expand Up @@ -122,6 +123,7 @@ export default class Router implements BaseRouter {
events: MittEmitter
_wrapApp: (App: ComponentType) => any
isSsr: boolean
isFallback: boolean

static events: MittEmitter = mitt()

Expand All @@ -137,6 +139,7 @@ export default class Router implements BaseRouter {
Component,
err,
subscription,
isFallback,
}: {
subscription: Subscription
initialProps: any
Expand All @@ -145,6 +148,7 @@ export default class Router implements BaseRouter {
App: ComponentType
wrapApp: (App: ComponentType) => any
err?: Error
isFallback: boolean
}
) {
// represents the current component key
Expand Down Expand Up @@ -180,6 +184,8 @@ export default class Router implements BaseRouter {
// back from external site
this.isSsr = true

this.isFallback = isFallback

if (typeof window !== 'undefined') {
// in order for `e.state` to work on the `onpopstate` event
// we have to register the initial route upon initialization
Expand Down Expand Up @@ -580,6 +586,8 @@ export default class Router implements BaseRouter {
as: string,
data: RouteInfo
): void {
this.isFallback = false

this.route = route
this.pathname = pathname
this.query = query
Expand Down
14 changes: 12 additions & 2 deletions packages/next/next-server/server/render.tsx
Expand Up @@ -46,14 +46,22 @@ class ServerRouter implements NextRouter {
query: ParsedUrlQuery
asPath: string
events: any
isFallback: boolean
// TODO: Remove in the next major version, as this would mean the user is adding event listeners in server-side `render` method
static events: MittEmitter = mitt()

constructor(pathname: string, query: ParsedUrlQuery, as: string) {
constructor(
pathname: string,
query: ParsedUrlQuery,
as: string,
{ isFallback }: { isFallback: boolean }
) {
this.route = pathname.replace(/\/$/, '') || '/'
this.pathname = pathname
this.query = query
this.asPath = as

this.isFallback = isFallback
}
push(): any {
noRouter()
Expand Down Expand Up @@ -394,7 +402,9 @@ export async function renderToHTML(

// @ts-ignore url will always be set
const asPath: string = req.url
const router = new ServerRouter(pathname, query, asPath)
const router = new ServerRouter(pathname, query, asPath, {
isFallback: isFallback,
})
const ctx = {
err,
req: isAutoExport ? undefined : req,
Expand Down
14 changes: 13 additions & 1 deletion test/integration/prerender/pages/catchall/[...slug].js
@@ -1,4 +1,10 @@
import { useRouter } from 'next/router'

export async function unstable_getStaticProps({ params: { slug } }) {
if (slug[0] === 'delayby3s') {
await new Promise(resolve => setTimeout(resolve, 3000))
}

return {
props: {
slug,
Expand All @@ -18,4 +24,10 @@ export async function unstable_getStaticPaths() {
}
}

export default ({ slug }) => <p id="catchall">Hi {slug?.join('/')}</p>
export default ({ slug }) => {
const { isFallback } = useRouter()
if (isFallback) {
return <p id="catchall">fallback</p>
}
return <p id="catchall">Hi {slug.join('/')}</p>
}
17 changes: 14 additions & 3 deletions test/integration/prerender/test/index.test.js
Expand Up @@ -386,9 +386,20 @@ const runTests = (dev = false) => {
}
// Production will render fallback for a "lazy" route
else {
const browser = await webdriver(appPort, '/catchall/notreturnedinpaths')
const text = await browser.elementByCss('#catchall').text()
expect(text).toMatch(/Hi.*?notreturnedinpaths/)
const html = await renderViaHTTP(appPort, '/catchall/notreturnedinpaths')
const $ = cheerio.load(html)
expect($('#catchall').text()).toBe('fallback')

// hydration
const browser = await webdriver(appPort, '/catchall/delayby3s')

const text1 = await browser.elementByCss('#catchall').text()
expect(text1).toBe('fallback')

await new Promise(resolve => setTimeout(resolve, 4000))

const text2 = await browser.elementByCss('#catchall').text()
expect(text2).toMatch(/Hi.*?delayby3s/)
}
})

Expand Down

0 comments on commit 6d5c487

Please sign in to comment.