Although the word layout is generally related to CSS page layout, the reason why I’m posting this is a bit different. Fixing the position of navbar to the top doesn't mean that the navbar wouldn't be unmounted when the user is directed to another page. For SPAs (Single Page Application), it's the expected behaviour that the navbar remains mounted throughout the routing. While it's easy to achieve this on CRA (Create React App), it can be difficult to maintain layout persistence in Gatsby or Next.js, since routing is different due to code-splitting. In this post, we will ensure the persistence of the layout component during page transitions without using any library in Next.js.
First of all, if it isn't already added, we need to add an “_app.js” file under ”/ pages”. In this way, we will be able to interfere with the initializing process of the application, and will be able to contain the page component in the layout component we will be creating soon. In its simplest form, an “_app.js” file looks like this:
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
This is where we'll be adding layout support for our app. Let’s change the “_app.js” file we created under “/ pages” as follows:
import React from "react";
export default function MyApp({ Component, pageProps }) {
const Layout = Component.Layout ? Component.Layout : React.Fragment;
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
Component here is the component that returns for the URL path you are on. For example; if you are on the homepage, the default export from “/pages/index.js” will return here. Therefore, we'll be adding layout components we created directly to the page components. We’ll get back to that later. Let’s create our layout. We can create it under “/layouts/MyLayout.js” to keep the project tidy:
import React, { useState } from "react";
export default function MyLayout({ children }) {
const [counter, setCounter] = useState(0);
return (
<>
<p>
<button onClick={() => setCounter(counter + 1)}>
Clicked {counter} Times
</button>
</p>
{children}
</>
)
}
If we were able to achieve layout persistance with the voodoo we cast in “pages / _app.js”, the number shown by the button with the counter on it shouldn't reset during routing. So, let’s create two sample pages under the “pages” folder to test this. First, “/pages/profile.js”:
import Link from "next/link";
export default function Profile() {
return (
<div>
<p>This is the Profile page.</p>
<p>
<Link href="/account">
<a>Go: Account</a>
</Link>
</p>
</div>
);
}
So, how do we add the layout component we just created to this page component? As I mentioned before; directly…
import Link from "next/link";
import MyLayout from "../layouts/MyLayout";
export default function Profile() {
return (
<div>
<p>This is the Profile page.</p>
<p>
<Link href="/account">
<a>Go: Account</a>
</Link>
</p>
</div>
);
}
Profile.Layout = MyLayout;
Well, this is not even slightly complicated. Let’s continue by adding our second sample page under “/pages/account.js”:
import MyLayout from "../layouts/MyLayout";
export default function Account() {
return (
<div>
<p>This is the Account page.</p>
<p>
<Link href="/profile">
<a>Go: Profile</a>
</Link>
</p>
</div>
);
}
Account.Layout = MyLayout;
All preparations have been completed. Now I'll open the Profile page in the browser for you. And I'll click the button with the counter on it for 4 times. Then I'll go to the Account page, and the result will be:
The counter didn't reset during routing because these two pages share the same layout component. If I go to a different page, MyLayout will be unmounted and the counter will reset.
In this way, you can keep navigation elements like Tab Bars etc. persistent across pages. Or, you can go to the kitchen and bake a strawberry cake, I don’t know, I’m ending this post.
I hope this was useful, you can also follow me on Twitter for future content:
Top comments (17)
Hi,
I wonder why do you bother to import MyLayout in every pages when just import MyLayout in the _app work the same, without this
MyPage.Layout = MyLayout;
?I hav checkd and id works as expected and in a simplier way.
Leo
To have multiple layouts within the app. We wouldn't want all pages to share the same layout.
Oh ! That make sense. Maybe it could be easier with a custom hook then ?
Like :
and the page :
But when we change the layout we still have a reset of the old one
how can I update the title in the head using this layout?
Don't. Use title inside Head from next/head instead.
Hi, do you mean to use the Head tag and title tag inside every page?
Would it be possible to have the Head tag in the layout component and pass the title variable somehow when you are doing Page.Layout = MyLayout?
Yes I meant that.
It is possible but it adds an unnecessary complexity imho.
What if you want to pass a prop to the MyLayout so that you can let your nav component know what page you are on? How do you pass props to MyLayout?
Any idea, how to implement Layout with typescript?
Thanks, I was blowing my mind trying to figure that.
Could you share a bit more context on how you got this working with Typescript?Component.layout is throwing "unsafe assignment of an any value".
This seems to have done the trick for me when not using fat-arrow functions.
Thank you
Hi! thanks for your effort and share us.
What about if I need to decide between two different layouts? Where should I put the logic and how should I export Layout from my page?
You will assign the layout to the page in the page file.
Upon navigating to a page,
_app.js
will render the layout from the page component viaComponent.Layout
as per the authors code.