If you already are a React developer, you probably know what a layout is.
Or do you?
The devil is in the details and it turns out that "layout" is one of the most ambiguous term of modern JavaScript web development.
Let's discover not one, not two, but four (!) definitions of what a layout is in React.
1) From the component standpoint
Let's start with an abstract definition, which will be the most reliable:
In React, a layout is a component that wraps multiple pages.
That's it.
At its core, a layout is a React component like any other. What makes it so special is its relation with the router used in your application, which depends on your framework or tech stack.
Similarly, pages that are just top-level React components, rendered depending on the current URL.
A layout in React is a component that wraps the page content.
To rephrase this definition in routing terms:
- a layout exists on multiple routes
- a page is associated with one route
- other components have no special relationship with the router
The lifecycle of layouts, and also pages, is controlled by the router when the user navigates between different routes.
Whether this navigation is a client-side navigation (SPA-style), or a server-side navigation (classical anchor tags) is relevant to understand how the layout behave. We specifically talk about client-side navigation in this article, using
<Link />
components rather than raw<a>
tags.
That's why we end up with slightly different interpretations of what a layout is, depending on the framework/library you use.
Let's dive three of them: React Router, Next.js, and Astro.
2) React Router: the stateful layouts
In React Router, during client-side navigation, layouts:
- do not remount
- rerenderer if you use the
useLocation
hook
Since React Router is one of the oldest and most popular router, this is how most React developer will interpret layouts.
This definition can be rephrased as such: layouts are components that preserve their state (do not remount) during client-side navigation.
Before hooks, the layout would rerender on every navigation.
Nowadays, it will rerender only if you actually use a reactive hook that rely on the current URL, like useLocation
. This makes them more efficient.
If you have a global application state in a React Context, you'd want to define the context provider within a layout.
In terms of implementation, a React Router layout takes no props,
and renders an <Outlet />
which correspond to its children.
I am not React Router expert so I can't tell why React Router prefers an
Outlet
component to the traditionnalchildren
prop: reach me out on Twitter if you have more info!
Here is how our layout could look like:
import { useState } from "react";
import { Outlet, Link, useLocation } from "react-router-dom";
// It's just a React component
export default function Root() {
// The random value will stay the same during client-side navigation
const [randomState] = useState(Math.random())
// We can read the URL easily
let location = useLocation();
// Will rerender during navigation, because of the useLocation hook
console.log("Rendering Root")
return (
<>
<div id="sidebar">
<h1>React Router Contacts</h1>
<div>Current pathname: {location.pathname}</div>
<div>Current state: {randomState}</div>
<div id="detail">
<Outlet />
</div>
We then setup the router to use this Root
component:
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
children: [
{
path: "contacts/:contactId",
element: <Contact />,
},
],
},
]);
Technically, we should differentiate "nested routes" from true "layout routes" documented here.
A nested route is associated with a URL segment, like /account
, and its component will act as a layout for nested pages like /account/profile
and /account/update-password
.
A layout route is not associated with a URL segment, it just provides the React component that wraps multiple pages.
But they are both akin to layouts, in the sense that they can wrap pages.
3) Next.js App Router, the super static layouts
In Next.js, with the App Router and during client-side navigation, layouts:
- do not remount
- do not rerender
Keep in mind that as a default, Next layouts are React Server Components, they render only once on the server and not client-side.
This is a very static interpretation of layouts, highly specific to Next.js with React Server Components.
In particular, the fact that they don't rerender on client-side navigation explains why you can't access the URL in layouts that are React Server Components (the default behaviour).
This limitation feels a bit harsh and is heavily debated on Next.js GitHub, however note that you can still use a client component for the layout or parts of it and rely on the usePathname hook.
A React Router layout with the
useLocation
hook fromreact-router-dom
behaves like a Next.js layout with the"use client"
directive and theusePathname
hook fromnext/navigation
.
Also, this non-rerendering approach makes layouts a terrible place to run authentication checks for paid or private content. The check won't run again during client-side navigation, and you can completely bypass a layout to access the content of a page as explained in my previous article about paywalls in Next.js.
In terms of implementation, a layout is defined using a special layout.ts
file.
The component receives a children
property which corresponds to the current page,
as well as the current route parameters for dynamic routes.
// app/layout.ts
// The root layout for this blog,
// which renders the html and body tags
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html
lang="en"
className={cx(
"text-black bg-white dark:text-[#fefefe] dark:bg-[#19191a]",
GeistSans.variable,
GeistMono.variable
)}
>
<body>
<main>
{/** Navbar can be a client-component and access the URL */*/}
<Navbar />
{children}
...
Pages inherit the static or dynamic nature of their parent layout. My hope is that in the future Partial Prerendering (PPR) may allow mixing static layouts and dynamic pages.
4) Astro, the visual layout
Astro is not exactly a React framework, but given that it's compatible with many UI libraries including React, I'll count as one for the purpose of this article.
In Astro, during client-side navigation, layouts:
- remount
- rerender
Astro is sticking to a purely visual definition of a layout, or "mise en page" as their awesome French translation states. A layout is a component that you use as the top-level component of multiple pages, it has no special behaviour when it comes to user navigation.
---
// src/pages/some-page.astro
// You need to import the layout component in every page
// it's just a regular React component really
import MySiteLayout from '../layouts/MySiteLayout.astro';
---
<MySiteLayout title="Home Page">
<p>My page content, wrapped in a layout!</p>
</MySiteLayout>
In Next.js, this would be called a template, using template.js
as the filename rather than layout.js
.
So yeah, nothing fancy here, in Astro layouts are just normal React components, that you happen to use in multiple pages.
They won't preserve state during client-side navigation, if your layout happens to use a client directive.
In Next.js older Pages Router, layouts were behaving similarly. We could hack our way to get state persistence, but it was quite convoluted.
Conclusion: lay out your layout right
Layout is a very common term in the JavaScript ecosystem, and yet it's very ambiguous.
When talking about layouts in React, a few points should be clarified:
- Is the layout a React Server Component, or a traditionnal client component?
- Does it remount or not during client-side navigation? If it remounts, it won't preserve state across pages.
- When does it rerender: on every navigation, only if you use reactive hooks to read the current URL, or only if you reload the page?
If you are not sure, you can always observe the layout behaviour of your favourite framework by setting up some state in a layout and rendering a random value.
Thanks for reading this article, I hope you now have a clearer view of what "layout" means in React!