A blog about React and related technologies

Nested dynamic layouts in Next.js apps

How to implement nested and dynamic layouts is a question that comes up often in the Next.js community. In this post, we'll explore how to create a Next.js app with one global layout and multiple (dynamic) sub-layouts for different sections of the website.

I prepared a simple example:

If you click around the website, you will notice that Blog and Docs sections have different sidebars and if you scroll down and browse through different pages in the sidebar, your scroll position will be persisted across pages. This is achieved by pages sharing the same (sub)layout.

Defining main layout

Next.js uses the App component the initialize pages. Each page on the website is basically a "child" component of the App component, which makes it a perfect place to define layouts.

We can override this default App component by creating a custom MyApp component in the pages/_app.js file.

Here's an example of a simple override:

// pages/_app.js
import React from 'react';
import App from 'next/app';

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;

    return <Component {...pageProps} />;
  }
}

export default MyApp;

The <Component {...pageProps} /> part represents individual pages, which means we can wrap it with a layout component:

// pages/_app.js
import React from 'react';
import App from 'next/app';
import MainLayout from '../components/layouts/main';

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;

    return (
      <MainLayout>
        <Component {...pageProps} />
      </MainLayout>
    );
  }
}

export default MyApp;

In this example, I created a <MainLayout> component that will be used on all pages of the site. A global layout component like this is used to add elements that need to be loaded on every page (like header and footer), and we can also use it to set global css styles:

// components/layouts/main
import Header from '../header';

const MainLayout = ({ children }) => (
  <div className="main-container">
    <Header />

    <div className="content-wrapper">{children}</div>

    <style jsx global>{`
      *,
      *::before,
      *::after {
        box-sizing: border-box;
      }
    `}</style>
  </div>
);

export default MainLayout;

Dynamic sub-layouts

The example website I created has a couple of regular pages and many pages in the docs and blog sections.

I created 3 sub-layout components:

  • components/layouts/docs - used by individual pages in the docs section
  • components/layouts/blog - used by individual pages in the blog section
  • components/layouts/default - used by all other pages

To assign a layout to each individual page, I found that attaching a static Layout property is the most flexible solution.

Here is an example of one of the docs pages:

// pages/docs/page-1.js
import DocsLayout from '../../components/layouts/docs';

const DocsPage = () => (
  <div className="docs">
    <h1>Page 1</h1>

    <p>...</p>
  </div>
);

DocsPage.Layout = DocsLayout;

export default DocsPage;

On top of the file I'm importing the DocsLayout component and I attached it to the page using DocsPage.Layout = DocsLayout;.

The same approach is used for blog pages, I used BlogPage.Layout = BlogLayout;.

How will Next.js know to use this layout? We need to update the pages/_app.js file. Since <Component /> in _app.js represents individual pages, we can get the layout using Component.Layout.

I also created a DefaultLayout component to use it for all pages that don't have their own layout specified.

Here's the final version of the pages/_app.js file:

// pages/_app.js
import React from 'react';
import App from 'next/app';
import MainLayout from '../components/layouts/main';
import DefaultLayout from '../components/layouts/default';

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;
    const Layout = Component.Layout || DefaultLayout;

    return (
      <MainLayout>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </MainLayout>
    );
  }
}

export default MyApp;

Now the pages are wrapped with the dynamic <Layout> component, which is also wrapped with the global <MainLayout> component.

You can download the complete source code on GitHub.