diff --git a/examples/with-next-layouts/layouts/AppLayout.js b/examples/with-next-layouts/layouts/AppLayout.js
new file mode 100644
index 00000000000000..e2d1c32a43827a
--- /dev/null
+++ b/examples/with-next-layouts/layouts/AppLayout.js
@@ -0,0 +1,19 @@
+import React from 'react'
+import delay from '../modules/delay'
+
+export default class AppLayout extends React.Component {
+ static async getInitialProps () {
+ return {
+ delay: await delay(1000)
+ }
+ }
+
+ render () {
+ return (
+
+
App header
+ {this.props.children}
+
+ )
+ }
+}
diff --git a/examples/with-next-layouts/layouts/ContentLayout.js b/examples/with-next-layouts/layouts/ContentLayout.js
new file mode 100644
index 00000000000000..346112c50f5bca
--- /dev/null
+++ b/examples/with-next-layouts/layouts/ContentLayout.js
@@ -0,0 +1,22 @@
+import React from 'react'
+import delay from '../modules/delay'
+import AppLayout from './AppLayout'
+
+export default class ContentLayout extends React.Component {
+ static parentLayout = AppLayout
+ static async getInitialProps () {
+ return {
+ delay: await delay(2000)
+ }
+ }
+
+ render () {
+ return (
+
+
+ {this.props.children}
+
+
+ )
+ }
+}
diff --git a/examples/with-next-layouts/modules/delay.js b/examples/with-next-layouts/modules/delay.js
new file mode 100644
index 00000000000000..27a13789aa79d3
--- /dev/null
+++ b/examples/with-next-layouts/modules/delay.js
@@ -0,0 +1,6 @@
+export default function delay (ms) {
+ return new Promise(resolve => setTimeout(() => {
+ console.log(`sleep ${ms} ms.`)
+ resolve(ms)
+ }, ms))
+}
diff --git a/examples/with-next-layouts/modules/next-layouts.js b/examples/with-next-layouts/modules/next-layouts.js
new file mode 100644
index 00000000000000..0730dce72e642b
--- /dev/null
+++ b/examples/with-next-layouts/modules/next-layouts.js
@@ -0,0 +1,81 @@
+import React from 'react'
+const defaultOptions = {
+ getInitialPropsMode: 'sequential'
+}
+
+export default function applyLayout (PageComponent, options) {
+ options = Object.assign({}, defaultOptions, options)
+
+ if (!isFunction(PageComponent.layout)) {
+ warn('Missing static property "layout" of page component.')
+ return PageComponent
+ }
+
+ if (isFunction(PageComponent.renderPage)) {
+ warn('You should not define static property "renderPage" of page component when the layout is applying')
+ return PageComponent
+ }
+
+ // collect layouts & getInitialProps
+ let layouts = [PageComponent.layout]
+ while (isFunction(layouts[0].parentLayout)) {
+ layouts.unshift(layouts[0].parentLayout)
+ }
+ const layoutGetInitialPropsList = layouts.map(Layout => isFunction(Layout.getInitialProps) ? Layout.getInitialProps : () => ({}))
+ const pageGetInitialProps = isFunction(PageComponent.getInitialProps) ? PageComponent.getInitialProps : () => ({})
+
+ PageComponent.getInitialProps = async(ctx) => {
+ let layoutPropsList = []
+ let pageProps
+
+ if (options.getInitialPropsMode === 'sequential') {
+ for (const layoutGetInitialProps of layoutGetInitialPropsList) {
+ const layoutProps = await layoutGetInitialProps(ctx)
+ layoutPropsList.push(layoutProps)
+ }
+ pageProps = await pageGetInitialProps(ctx)
+ } else if (options.getInitialPropsMode === 'concurrent') {
+ const promises = layoutGetInitialPropsList.map(layoutGetInitialProps => layoutGetInitialProps(ctx))
+ promises.push(pageGetInitialProps(ctx))
+ const promiseResults = await Promise.all(promises)
+ pageProps = promiseResults.pop()
+ layoutPropsList = promiseResults
+ }
+
+ return { layoutPropsList, pageProps }
+ }
+
+ PageComponent.renderPage = ({ Component, props: { layoutPropsList, pageProps }, url }) => {
+ return renderLayout({ Component, layouts, pageProps, layoutPropsList, url })
+ }
+
+ return PageComponent
+}
+
+function renderLayout ({ Component, layouts, pageProps, layoutPropsList, url }) {
+ const Layout = layouts[0]
+ const layoutProps = layoutPropsList[0]
+ return (Layout) ? (
+
+ {renderLayout({
+ Component,
+ layouts: layouts.slice(1, layouts.length),
+ pageProps,
+ layoutPropsList: layoutPropsList.slice(1, layoutPropsList.length),
+ url
+ })}
+
+ ) : (
+
+ )
+}
+
+function isFunction (func) {
+ return typeof func === 'function'
+}
+
+function warn (message) {
+ if (process.env.NODE_ENV !== 'production') {
+ console.error(message)
+ }
+}
diff --git a/examples/with-next-layouts/package.json b/examples/with-next-layouts/package.json
new file mode 100644
index 00000000000000..0fdb3b36f4bff2
--- /dev/null
+++ b/examples/with-next-layouts/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "with-next-layouts",
+ "version": "1.0.0",
+ "scripts": {
+ "dev": "next",
+ "build": "next build",
+ "start": "next start"
+ },
+ "dependencies": {
+ "next": "latest",
+ "react": "^16.0.0",
+ "react-dom": "^16.0.0"
+ },
+ "license": "ISC"
+}
diff --git a/examples/with-next-layouts/pages/about.js b/examples/with-next-layouts/pages/about.js
new file mode 100644
index 00000000000000..bd6af1ed86eef1
--- /dev/null
+++ b/examples/with-next-layouts/pages/about.js
@@ -0,0 +1,23 @@
+import React from 'react'
+import delay from '../modules/delay'
+import applyLayout from '../modules/next-layouts'
+import ContentLayout from '../components/ContentLayout'
+
+class AboutPage extends React.Component {
+ static layout = ContentLayout
+ static async getInitialProps () {
+ return {
+ delay: await delay(3000)
+ }
+ }
+
+ render () {
+ return (
+ about
+ )
+ }
+}
+
+export default applyLayout(AboutPage, {
+ getInitialPropsMode: 'concurrent'
+})
diff --git a/examples/with-next-layouts/pages/index.js b/examples/with-next-layouts/pages/index.js
new file mode 100644
index 00000000000000..b1d7e84a2ef6f0
--- /dev/null
+++ b/examples/with-next-layouts/pages/index.js
@@ -0,0 +1,27 @@
+import React from 'react'
+import Link from 'next/link'
+import delay from '../modules/delay'
+import applyLayout from '../modules/next-layouts'
+import ContentLayout from '../components/ContentLayout'
+
+class IndexPage extends React.Component {
+ static layout = ContentLayout
+ static async getInitialProps () {
+ return {
+ delay: await delay(3000)
+ }
+ }
+
+ render () {
+ return (
+
+ )
+ }
+}
+
+export default applyLayout(IndexPage)
diff --git a/next-layouts.js b/next-layouts.js
new file mode 100644
index 00000000000000..10da704670e37f
--- /dev/null
+++ b/next-layouts.js
@@ -0,0 +1,80 @@
+import React from 'react'
+const defaultOptions = {
+ getInitialPropsMode: 'sequential'
+}
+
+export default function applyLayout (PageComponent, options) {
+ options = Object.assign({}, defaultOptions, options)
+
+ if (!isFunction(PageComponent.layout)) {
+ warn('Missing static property "layout" of page component.')
+ return PageComponent
+ }
+
+ if (isFunction(PageComponent.renderPage)) {
+ warn('You should not define static property "renderPage" of page component when the layout is applying')
+ }
+
+ // collect layouts & getInitialProps
+ let layouts = [PageComponent.layout]
+ while (isFunction(layouts[0].parentLayout)) {
+ layouts.unshift(layouts[0].parentLayout)
+ }
+ const layoutGetInitialPropsList = layouts.map(Layout => isFunction(Layout.getInitialProps) ? Layout.getInitialProps : () => ({}))
+ const pageGetInitialProps = isFunction(PageComponent.getInitialProps) ? PageComponent.getInitialProps : () => ({})
+
+ PageComponent.getInitialProps = async(ctx) => {
+ let layoutPropsList = []
+ let pageProps
+
+ if (options.getInitialPropsMode === 'sequential') {
+ for (const layoutGetInitialProps of layoutGetInitialPropsList) {
+ const layoutProps = await layoutGetInitialProps(ctx)
+ layoutPropsList.push(layoutProps)
+ }
+ pageProps = await pageGetInitialProps(ctx)
+ } else if (options.getInitialPropsMode === 'concurrent') {
+ const promises = layoutGetInitialPropsList.map(layoutGetInitialProps => layoutGetInitialProps(ctx))
+ promises.push(pageGetInitialProps(ctx))
+ const promiseResults = await Promise.all(promises)
+ pageProps = promiseResults.pop()
+ layoutPropsList = promiseResults
+ }
+
+ return { layoutPropsList, pageProps }
+ }
+
+ PageComponent.renderPage = ({ Component, props: { layoutPropsList, pageProps }, url }) => {
+ return renderLayout({ Component, layouts, pageProps, layoutPropsList, url })
+ }
+
+ return PageComponent
+}
+
+function renderLayout ({ Component, layouts, pageProps, layoutPropsList, url }) {
+ const Layout = layouts[0]
+ const layoutProps = layoutPropsList[0]
+ return (Layout) ? (
+
+ {renderLayout({
+ Component,
+ layouts: layouts.slice(1, layouts.length),
+ pageProps,
+ layoutPropsList: layoutPropsList.slice(1, layoutPropsList.length),
+ url
+ })}
+
+ ) : (
+
+ )
+}
+
+function isFunction (func) {
+ return typeof func === 'function'
+}
+
+function warn (message) {
+ if (process.env.NODE_ENV !== 'production') {
+ console.error(message)
+ }
+}