There are two main types of custom layouts you’ll find in many applications: Single Shared Layout and Per-Page Layout
The Single Shared Layout is a layout that you want every page of your application to share. This is so that components like your NavBar
and Footer
don’t have to be defined on every page that you build. We will accomplish this type of layout by creating a Layout component that will wrap our application in _app.js
. Because _app.js
is the root of the application and renders every page, our layout component will also get rendered on every page.
The Per-Page Layout lets you be a lot more fine grain about what pages receive what layout. If you have a big website and need to apply different layouts to different pages (auth, dashboard, settings, etc.) you will want to consider using this pattern. To achieve this pattern, we will define a getLayout
function on the pages we want to receive a custom layout and in _app.js
use that function to wrap the page in the layout with a default of rendering the page as is if the function is not defined.
Lazar Nikolov: [0:00] React's composable nature allows us to create reusable components, and layouts are exactly that. In lesson number seven, we learned what's the app component and how to override it. If we had a custom layout and wanted to use it, the App component is where we should start.
[0:17] In Next.js, there are two ways that you can define a custom layout. You can define a single shared layout, or you can do something more complex and build a per-page layout system. In this lesson, we're going to cover both of them.
[0:30] Let's start with the single shared layout. A single shared layout in Next.js is a custom layout that's used by every page in our app. For example, let's say that our app is simple and every page has a navbar and a footer.
[0:43] Let's open up exercise 10. Let's open the Source folder components layout, and open the index.tsx file. Before we jump into the index.tsx file, we can see that we have a footer and navbar files at the same level.
[0:58] If we open those files first, we can see that we have some sort of an initial layout. To hook everything up, let's go to the index.tsx file, and check it out first. As you can see, the layout component is a standard React component.
[1:14] In this case, we have the children-defined in the props. The children will be the page itself. What we need to do now in order to display the navbar and the footer is first import them.
[1:26] We will do import navbar from navbar, and also import footer from footer, and then go down into the layout component, expand the empty React fragment. First, let's add the navbar.
[1:46] Then, we can add the children or the page itself. Lastly, we can add the footer. This is how we can define a layout. In order to use this layout or to apply it to every page, we need to go to the app component, pages_app.tsx.
[2:06] As we mentioned earlier, in lesson number seven, the app component renders every page in our app. In order to use the new layout, we need to import it first. We're going to say import layout from. We're going to jump one level up, source, components, and then layout.
[2:25] After we have it imported, we can wrap the component with our layout. Let's say, layout right here and put the closing tag below the component. There's no need to pay attention to the Chakra provider for now, because we'll learn more about it in the next lesson.
[2:42] Let's save this and execute npm run dev to see it in action. If we refresh our page, there we go. We can see that the title is here. There's a login button. This is our home page content, and then the footer contains three columns of links.
[3:00] Let's confirm. If you go to the index page, which is the home page, we can see that we only have the heading. This is our home page content.
[3:10] Everything else is defined by that layout component that we learned about. Since the layout component is being used in the app component, it is reused when changing pages. The component state will be preserved. That's about it for the single shared layouts.
[3:29] Let's take a look how we can define per-page layouts. For example, if we have a much bigger website that has multiple layouts, for example, authentication, dashboard, settings, etc., we can define a getLayout property to our pages that will receive the page in the props and wrap it in the layout that we want.
[3:49] Since we're going to be returning a function, we can have complex nested layouts if we wanted to. Let's take a look how we can do that. First, let's get rid of the layout component, the single shared layout that we just did.
[4:03] There we go. Now, we're ready to take a look at the per-page layout. For this to work, we are going to open the home page. As we mentioned, we can define a getLayout function on the home component itself, that will take the page in its props and wrap that page with a certain layout.
[4:19] To define the getLayout method on the home component itself, we can attach it by doing home.getLayout=, and then the arrow function. The point with this method is to actually call it from within the app component.
[4:36] Since we have the component from the props in the app component, which represents our page, we can pass that page in the props of the getLayout. We can do page, and then that will be of type React element, and make sure to import it.
[4:54] The getLayout method can be considered as a component itself. For example, in this case for the home page, we want to use the layout that we previously did. We're going to import it and inside, pass the page from the props. That's it.
[5:09] Right now, if we refresh, we won't see any changes because we need to utilize or to call the getLayout method. Let's go to the app component and do some changes. As we mentioned earlier, the component that we get from the app props is the actual home page. Right now, that component contains the getLayout method that we just defined.
[5:30] In order to obtain it, let's create a new constant called getLayout, which will equal to component.getLayout in case it exists or as a fall back, we can create a simple inline method that takes the page in the props and returns the page without making any changes.
[5:50] Now, in order to use the getLayout method, we need to use it in place of the component. We'll open curly brackets and write getLayout, and because it needs a page, we can paste our component. There we go, we can immediately see the changes.
[6:05] We have the heading, we have the button, and the footer with all of the links inside, but there are some errors. If we hover on the getLayout, TypeScript will complain that the property getLayout does not exist on the type Next component type.
[6:18] If this was JavaScript, we won't have any problems. In order to make TypeScript happy, we need to do some changes. Because we're using the AppProps, the component type that is defined from the AppProps does not include the getLayout method, because that is our own custom method.
[6:36] The solution would be to create our own AppProps type and override the component type, so let's do that. We can create a type above the App component, and let's name it AppPropsWithLayout.
[6:50] This will inherit or extend the AppProps that we already have from the Next /F package, but then we will override the component type.
[7:02] Since our new getLayout method is part of the component type, we need to create yet another type that will include our new method inside. Let's call that one NextPageWithLayout and define it above the AppPropsWithLayout.
[7:17] Let's see. Type NextPageWithLayout that will inhibit from NextPage, and then add our getLayout method. As we defined it, it will receive a page, which was of type React element, and it will return a React node.
[7:41] Now, if we were to replace the AppProps with our AppPropsWithLayout, all of the errors are gone. If we hover on the component, we can see that it extends the NextPageWithLayout. If we trigger the intelliSense, we can see that our getLayout method is now being discovered.
[8:00] Since we defined it as an optional, we need to provide the fallback. This is how we can build a simple per-page layout mechanism, where we can define which layout will be used for each page specifically.
[8:16] One thing to note that the custom layouts are not considered as pages, so the only way to fetch data is on the client's side. We can't use Next.js' data fetching methods.
[8:28] Let's recap. We learned that there were two ways to achieve custom layouts in Next.js. There was the single shared layout, which we defined in the source components layout file.
[8:40] This was a simple React component that accepted children in its props. In order to use it, we wrapped our component in the layout itself. That's how all of our pages will have that layout.
[8:52] We also learned about the per-page layout mechanism that will allow us to specify which layout will be used for each page. To achieve that, we defined a getLayout method for the page itself. In this case, it was the home page that accepted the page and its arguments, and rendered that page in the specific layout that we want.
[9:14] In this case, we used the previous layout. If this was an authentication screen, then we would define a new register layout or login layout or authentication layout, and we would have used that one instead.
[9:25] To hook up everything and make it work, we obtained the getLayout method from the component itself, which was the page. We also provided a fallback in case a certain page does not define its own layout, and then we use that method to render the component that we get from the props.
[9:42] Then, we went the extra mile to make TypeScript aware of our new method, and we created the AppPropsWithLayout and the NextPageWithLayout types, so that our component that we get from the props will be aware of the getLayout method that we just defined.