Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions packages/next-server/lib/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<AppContextType<Router>>(App, {
AppTree: this._wrapApp(App),
AppTree,
Component,
router: this,
ctx,
Expand Down
4 changes: 4 additions & 0 deletions packages/next-server/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<R extends NextRouter = NextRouter> = {
Expand Down
11 changes: 9 additions & 2 deletions packages/next-server/server/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,19 +303,26 @@ 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,
res: isStaticPage ? undefined : res,
pathname,
query,
asPath,
AppTree: (props: any) => {
return (
<AppContainer>
<App {...props} Component={Component} router={router} />
</AppContainer>
)
},
}
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)
Expand Down
10 changes: 6 additions & 4 deletions packages/next/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions test/integration/app-tree/pages/_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' ? (
<>
<div dangerouslySetInnerHTML={{ __html: html }} />
<Link href={href}>
Expand All @@ -48,7 +48,7 @@ class MyApp extends App {
</>
) : (
<Container>
<Component {...pageProps} {...{ html }} />
<Component {...pageProps} />
</Container>
)
}
Expand Down
33 changes: 33 additions & 0 deletions test/integration/app-tree/pages/hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { render } from 'react-dom'
import { renderToString } from 'react-dom/server'

const Page = ({ html }) =>
html ? (
<>
<p>saved:</p>
<div dangerouslySetInnerHTML={{ __html: html }} />
</>
) : (
<p>Hello world</p>
)

Page.getInitialProps = async ({ AppTree, pathname, query, asPath }) => {
let html
const toRender = (
<AppTree router={{ pathname, query, asPath }} Component={Page} />
)

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
5 changes: 5 additions & 0 deletions test/integration/app-tree/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down