Creating persistent sublayouts in Next.js

In next.js we use _app.js for layouts which should not be rerendered on page transitions. In this post we will see how we can extend this approach to persistent sublayouts on sections of our website.

Layouts in _app.js

In next.js we use the _app.js file to create layouts which do not need to be rerendered between page transitions.

_app.js

export default (props) => (
  <div>
    <div>Header</div>
    {props.children}
  </div>
)

This will include our header in all of our pages, without having to need to rerender the header when we move from one page to another.

For most cases this is all we need, but sometimes we face situations where we have subsections of our sites with additional layout parts, that should not be rerendered.

Sublayouts - an example

Imagine we create an account section for our page. Our existing pages would now be:

/pages/index.js
/pages/account/index.js
/pages/account/profile.js
/pages/account/subscription.js

And we want to add a persistent side-bar to all our account pages:

Header
------------------------------------------
Account        |
* Profile      |
* Subscription |    Page Content
               |
               |

How can we achieve this? We only have one

_app.js
file and we certainly wouldn't want to show the sidebar on
/index.js
. Luckily there is still a way to make use of
_app.js
and to create a sidebar which is kept between page transitions.

Creating Sublayouts

Let's first create the sublayout:

/components/AccountLayout.js

import React, { useEffect } from 'react'
import Link from 'next/link'

const AccountLayout = (props) => {
  return (
    <div style={{ display: 'flex', flexDirection: 'row' }}>
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
        <Link href="/account">
          <a>Account</a>
        </Link>
        <ul>
          <li>
            <Link href="/account/profile">
              <a>Profile</a>
            </Link>
          </li>
          <li>
            <Link href="/account/subscription">
              <a>Subscription</a>
            </Link>
          </li>
        </ul>
      </div>
      <div style={{ flex: 3 }}>{props.children}</div>
    </div>
  )
}
export default AccountLayout

On components with a sublayout we can set the layout as a property:

/pages/account/index.js

import React from 'react'
import AccountLayout from '../../componenets/AccountLayout'

const Account = (props) => {
  return <div>AccountPage</div>
}
export default Account

Account.Layout = AccountLayout

(We also need to set the

.Layout
property in
/pages/account/profile.js
and
/pages/account/subscription.js
.)

Now we are able to check in

_app.js
if the component we are rendering contains a layout. If it does, we wrap it with its Layout:

import Header from '../componenets/Header'

function MyApp({ Component, pageProps }) {
  const wrappedComponent = Component.Layout ? (
    <Component.Layout>
      <Component {...pageProps} />
    </Component.Layout>
  ) : (
    <Component {...pageProps} />
  )
  return (
    <div>
      <Header />
      {wrappedComponent}
    </div>
  )
}

export default MyApp

If we now transition between two pages with the same Layout, the Layout is not unmounted and mounted again.