What is a layout in React?

March 27, 2024 (2 months ago)

Cover image

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 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 traditionnal children 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 from react-router-dom behaves like a Next.js layout with the "use client" directive and the usePathname hook from next/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!

Loved this article?

You will probably also like my Next.js course,

"Blazing Fast Next.js with React Server Components"

RSCs, Server Actions, Partial Prerendering, revalidation... everything's in there to fully grasp Next.js server-centric optimizations.