diff --git a/packages/next-server/lib/router/router.ts b/packages/next-server/lib/router/router.ts index 9166266ef493e5..5dcb8c2896dd76 100644 --- a/packages/next-server/lib/router/router.ts +++ b/packages/next-server/lib/router/router.ts @@ -416,8 +416,12 @@ export default class Router implements BaseRouter { } return new Promise((resolve, reject) => { - const ctx = { pathname, query, asPath: as } - this.getInitialProps(Component, ctx).then(props => { + // we provide AppTree later so this needs to be `any` + this.getInitialProps(Component, { + pathname, + query, + asPath: as, + } as any).then(props => { routeInfo.props = props this.components[route] = routeInfo resolve(routeInfo) @@ -450,9 +454,12 @@ export default class Router implements BaseRouter { resolve( this.fetchComponent('/_error').then(Component => { const routeInfo: RouteInfo = { Component, err } - const ctx = { err, pathname, query } return new Promise(resolve => { - this.getInitialProps(Component, ctx).then( + this.getInitialProps(Component, { + err, + pathname, + query, + } as any).then( props => { routeInfo.props = props routeInfo.error = err @@ -635,8 +642,10 @@ export default class Router implements BaseRouter { return { error: err.message, status } }) } else { + const AppTree = this._wrapApp(App) + ctx.AppTree = AppTree props = await loadGetInitialProps>(App, { - AppTree: this._wrapApp(App), + AppTree, Component, router: this, ctx, diff --git a/packages/next-server/lib/utils.ts b/packages/next-server/lib/utils.ts index 62854dbabac218..230189ad34de75 100644 --- a/packages/next-server/lib/utils.ts +++ b/packages/next-server/lib/utils.ts @@ -96,6 +96,10 @@ export interface NextPageContext { * `String` of the actual path including query. */ asPath?: string + /** + * `Component` the tree of the App to use if needing to render separately + */ + AppTree: NextComponentType } export type AppContextType = { diff --git a/packages/next-server/server/render.tsx b/packages/next-server/server/render.tsx index 1d7f676410078f..87eaf4fb4f71ec 100644 --- a/packages/next-server/server/render.tsx +++ b/packages/next-server/server/render.tsx @@ -303,6 +303,7 @@ export async function renderToHTML( // @ts-ignore url will always be set const asPath: string = req.url + const router = new ServerRouter(pathname, query, asPath) const ctx = { err, req: isStaticPage ? undefined : req, @@ -310,12 +311,18 @@ export async function renderToHTML( pathname, query, asPath, + AppTree: (props: any) => { + return ( + + + + ) + }, } + let props: any const isDataPrerender = pageConfig.experimentalPrerender === true && req.headers['content-type'] === 'application/json' - const router = new ServerRouter(pathname, query, asPath) - let props: any if (documentMiddlewareEnabled && typeof DocumentMiddleware === 'function') { await DocumentMiddleware(ctx) diff --git a/packages/next/client/index.js b/packages/next/client/index.js index d3d6951d1af967..1fc3264c6c1bea 100644 --- a/packages/next/client/index.js +++ b/packages/next/client/index.js @@ -218,11 +218,12 @@ export async function renderError (props) { // In production we do a normal render with the `ErrorComponent` as component. // If we've gotten here upon initial render, we can use the props from the server. // Otherwise, we need to call `getInitialProps` on `App` before mounting. + const AppTree = wrapApp(App) const appCtx = { - AppTree: wrapApp(App), Component: ErrorComponent, + AppTree, router, - ctx: { err, pathname: page, query, asPath } + ctx: { err, pathname: page, query, asPath, AppTree } } const initProps = props.props @@ -326,11 +327,12 @@ async function doRender ({ App, Component, props, err }) { lastAppProps.Component === ErrorComponent ) { const { pathname, query, asPath } = router + const AppTree = wrapApp(App) const appCtx = { router, - AppTree: wrapApp(App), + AppTree, Component: ErrorComponent, - ctx: { err, pathname, query, asPath } + ctx: { err, pathname, query, asPath, AppTree } } props = await loadGetInitialProps(App, appCtx) } diff --git a/test/integration/app-tree/pages/_app.js b/test/integration/app-tree/pages/_app.js index 7952c779517d3a..4db9656c960fb2 100644 --- a/test/integration/app-tree/pages/_app.js +++ b/test/integration/app-tree/pages/_app.js @@ -39,7 +39,7 @@ class MyApp extends App { const { Component, pageProps, html, router } = this.props const href = router.pathname === '/' ? '/another' : '/' - return html ? ( + return html && router.pathname !== '/hello' ? ( <>
@@ -48,7 +48,7 @@ class MyApp extends App { ) : ( - + ) } diff --git a/test/integration/app-tree/pages/hello.js b/test/integration/app-tree/pages/hello.js new file mode 100644 index 00000000000000..d3423c354a9e68 --- /dev/null +++ b/test/integration/app-tree/pages/hello.js @@ -0,0 +1,33 @@ +import { render } from 'react-dom' +import { renderToString } from 'react-dom/server' + +const Page = ({ html }) => + html ? ( + <> +

saved:

+
+ + ) : ( +

Hello world

+ ) + +Page.getInitialProps = async ({ AppTree, pathname, query, asPath }) => { + let html + const toRender = ( + + ) + + if (typeof window !== 'undefined') { + const el = document.createElement('div') + document.querySelector('body').appendChild(el) + render(toRender, el) + html = el.innerHTML + el.remove() + } else { + html = renderToString(toRender) + } + + return { html } +} + +export default Page diff --git a/test/integration/app-tree/test/index.test.js b/test/integration/app-tree/test/index.test.js index 09b66457fdb564..edadea0d3a15ba 100644 --- a/test/integration/app-tree/test/index.test.js +++ b/test/integration/app-tree/test/index.test.js @@ -45,6 +45,11 @@ const runTests = () => { html = await browser.eval(`document.documentElement.innerHTML`) expect(html).toMatch(/page:.*?\/another/) }) + + it('should pass AppTree to NextPageContext', async () => { + const html = await renderViaHTTP(appPort, '/hello') + expect(html).toMatch(/saved:.*?Hello world/) + }) } describe('Auto Export', () => {