Adding a wrapper component to your Next.js app
By Flavio Copes
Learn how to add a wrapper layout component in Next.js to share a nav and sidebar across pages, comparing a higher order component with a props approach.
To share a layout across pages in Next.js, you create a wrapper component that renders the common parts, and each page fills in its own content. There are 2 ways to build it: a Higher Order Component, or a plain component that takes the page content as a prop.
Here’s the situation. All the pages on your site look more or less the same. There’s a chrome, a common base layer, and you just want to change what’s inside.
There’s a nav bar, a sidebar, and then the actual content. You don’t want to repeat the nav and sidebar in every page file.
The Higher Order Component approach
One way is using a Higher Order Component, by creating a components/Layout.js component:
export default Page => {
return () => (
<div>
<nav>
<ul>....</ul>
</nav>
<main>
<Page />
</main>
</div>
)
}
It’s a function that takes a page component and returns a new component wrapping it in the layout markup.
In there we can import separate components for heading and/or sidebar, and we can also add all the CSS we need.
And you use it in every page like this:
import withLayout from '../components/Layout.js'
const Page = () => <p>Here's a page!</p>
export default withLayout(Page)
Why this breaks getInitialProps
I found this works only for simple cases, where you don’t need to call getInitialProps() on a page.
Why?
Because Next.js only calls getInitialProps() on the component the page exports as default. Here, that’s the wrapper returned by withLayout(), not your Page. So Page.getInitialProps() is never called, and your data fetching silently stops working.
The symptom is confusing: no error, the page renders, but the props are empty. You can work around it by copying getInitialProps onto the wrapped component, but that’s one more thing to remember on every page.
The props approach
To avoid unnecessarily complicating our codebase, the alternative approach is to use props:
export default props => (
<div>
<nav>
<ul>....</ul>
</nav>
<main>
{props.content}
</main>
</div>
)
and in our pages now we use it like this:
import Layout from '../components/Layout.js'
const Page = () => (
<Layout content={(
<p>Here's a page!</p>
)} />
)
Now the page itself is still the default export, so Next.js finds getInitialProps() where it expects it:
import Layout from '../components/Layout.js'
const Page = () => (
<Layout content={(
<p>Here's a page!</p>
)} />
)
Page.getInitialProps = ({ query }) => {
//...
}
The only downside is having to write the component JSX inside the content prop. If that bothers you, pass it as props.children instead and render {props.children} in the layout. The mechanics are identical, the JSX reads a bit more naturally.
Related posts about next: