MWAN MOBILE

×
mwan_logo
blog-banner

Layouts RFC

Miscellaneous 26-May-2022

This RFC outlines the biggest update to Next.js since it was introduced in 2016:

  • Nested Layouts: Build complex applications with nested routes.
  • Designed for Server Components: Optimized for subtree navigation.
  • Improved Data Fetching: Fetch in layouts while avoiding waterfalls.
  • Using React 18 Features: Streaming, Transitions, and Suspense.
  • Client and Server Routing: Server-centric routing with SPA-like behavior.
  • 100% incrementally adoptable: No breaking changes so you can adopt gradually.
  • Advanced Routing Conventions: Offscreen stashing, instant transitions, and more.

The new Next.js router will be built on top of the recently released React 18 features. We plan to introduce defaults and conventions to allow you to easily adopt these new features and take advantage of the benefits they unlock.

Timeline

This RFC will be divided into two parts:

  • Part 1 (This Post): Overview of the new routing system and how it integrates with React Server Components and Data Fetching.
  • Part 2 (Next Post): Advanced routing examples and conventions, and how Next.js will use Suspense behind the scenes for streaming and selective hydration.

Motivation

We’ve been gathering community feedback from GitHub, Discord, Reddit, and our developer survey about the current limitations of routing in Next.js. We’ve found that:

  • The developer experience of creating layouts can be improved. It should be easy to create layouts that can be nested, shared across routes, and have their state preserved on navigation.
  • Many Next.js applications are dashboards or consoles, which would benefit from more advanced routing solutions.

While the current routing system has worked well since the beginning of Next.js, we want to make it easier for developers to build more performant and feature-rich web applications.

As framework maintainers, we also want to build a routing system that is backwards compatible and aligns with the future of React.

Note: Some routing conventions were inspired by the Relay-based router at Meta, where some features of Server Components were originally developed, as well as client-side routers like React Router and Ember.js. The layout.js file convention was inspired by the work done in SvelteKit. We’d also like to thank Cassidy for opening an earlier RFC on layouts.

Terminology

This RFC introduces new routing conventions and syntax. The terminology is based on React and standard web platform terms. Throughout the RFC, you’ll see these terms linked back to their definitions below.

  • Tree: A convention for visualizing a hierarchical structure. For example, a component tree with parent and children components, a folder structure, etc.
  • Subtree Part of the tree, starting at the root (first) and ending at the leaf (last).
  • URL Path: Part of the URL that comes after the domain.
  • URL Segment: Part of the URL path delimited by slashes.

How Routing Currently Works

Today, Next.js uses the file system to map individual folders and files in the Pages directory to routes accessible through URLs. Each Page file exports a React Component and has an associated Route based on its file name. For example:

Next.js also supports Dynamic Routes (including catch all variations) with the [param].js[...param].js and [[...param]].js conventions.

Introducing the App Folder

To ensure these new improvements can be incrementally adopted and avoid breaking changes, we are proposing a new directory called app.

The app directory will work alongside the pages directory. For backwards compatibility, the behavior of the pages directory will remain the same and continue to be supported. You can incrementally move parts of your application to the new app directory to take advantage of the new features.

Defining Routes

You can use the folder hierarchy inside app to define routes. A route is a single path of nested folders, following the hierarchy from the root folder down to the final leaf folder.

For example, you can add a new /dashboard/settings route by nesting two new folders in the app directory.

Note:

  • With this system, you’ll use folders to define routes, and files to define UI (with new file conventions such as layout.jspage.js, and in the second part of the RFC loading.js).
  • This allows you to colocate your own project files (UI components, test files, stories, etc) inside the app directory. This is currently not possible in pages.

Route Segments

Each folder in the subtree represents a route segment. Each route segment is mapped to a corresponding segment in a URL path.

For example, the /dashboard/settings route is composed of 3 segments:

  • The / root segment
  • The dashboard segment
  • The settings segment

Note: The name route segment was chosen to match the existing terminology around URL paths.

Layouts & Creating UI

New file convention: layout.js

So far, we have used folders to define the routes of our application. But empty folders do not do anything by themselves. Let’s discuss how you can define the UI that will render for these routes using new file conventions.

layout is UI that is shared between route segments in a subtree. Layouts do not affect URL paths and do not re-render (React state is preserved) when a user navigates between segments that share the same layout.

A layout can be defined by default exporting a React component from a layout.js file. The component should accept a children prop which will be populated with the segments the layout is wrapping.

There are 2 types of layouts:

  • Root layout: Applies to all routes
  • Regular layout: Applies to specific route segments

You can nest two or more layouts together to form nested layouts.

Root layout

You can create a root layout that will apply to all routes of your application by adding a layout.js file inside the app folder.

Note:

  • The root layout replaces the need for a custom App (_app.js) and custom Document (_document.js) since it applies to all routes.
  • You’ll be able to use the root layout to customize the initial document shell (e.g. <html> and <body> tags).
  • You’ll be able to use data fetching methods inside the root layout (and other layouts).

Regular layouts

You can also create a layout that only applies to a part of your application by adding a layout.js file inside a specific folder.

For example, you can create a layout.js file inside the dashboard folder which will only apply to the route segments inside dashboard.

Nesting layouts

Layouts are nested by default.

For example, if we were to combine the two layouts above. The root layout (app/layout.js) would be applied to the dashboard layout, which would also apply to all route segments inside dashboard/*.

Pages

New file convention: page.js

A page is UI that is unique to a route segment and required for a route to be valid. You can create a page by adding a page.js file inside a folder.

For example, to create pages for the /dashboard/* routes, you can add a page.js file inside each folder. When a user visits /dashboard/settings, Next.js will render the page.js file for the settings folder wrapped in any layouts that exist further up the subtree.

You can create a page.js file directly inside the dashboard folder to match the /dashboard route. The dashboard layout will also apply to this page:

This route is composed of 2 segments:

  • The / root segment
  • The dashboard segment

Note:

  • For a route to be valid, it needs to have a page in its leaf segment. If it doesn’t, the route will 404.
  • page.js file should default export a React component.
  • The name needs to be page.(js|jsx|ts|tsx) exactly. If you do not export a Page component, Next.js will throw an error.

Layout and Page Behavior

Recap:

  • Page components are the default export of page.js.
  • Layout components are the default export of layout.js.
  • Layout components must accept a children prop.

When a layout component is rendered, the children prop will be populated with a child layout component (if it exists further down the subtree) or a page component.

It may be easier to visualize it as a layout tree where the parent layout will pick the nearest child layout until it reaches a page.

Basic Example:

// Root layout (app/layout.js)
// - Applies to all routes
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Header />
        {children}
        <Footer />
      </body>
    </html>
  )
}

// Regular layout (app/dashboard/layout.js)
// - Applies to route segments in app/dashboard/*
export default function DashboardLayout({ children }) {
  return (
    <>
      <DashboardSidebar />
      {children}
    </>
  )
}

// Page Component (app/dashboard/analytics/page.js)
// - The UI for the `app/dashboard/analytics` segment
// - Matches the `acme.com/dashboard/analytics` URL path
export default function AnalyticsPage() {
  return (
    <main>...</main>
  )
}

The above combination of layouts and pages would render the following component hierarchy:

<RootLayout>
  <Header />
  <DashboardLayout>
    <DahboardSidebar />
    <AnalyticsPage>
      <main>...</main>
    </AnalyticsPage>
  </DashboardLayout>
  <Footer />
</RootLayout>

React Server Components

Note: If you’re not familiar with React Server Components, we recommend reading the React Server Component RFC before reading this section.

With this RFC, you can start using React features and incrementally adopt React Server Components into your Next.js application.

The internals of the new routing system leverage recently released React features such as Streaming, Suspense, and Transitions. These are the building blocks for React Server Components.

Server Components are the new default

One of the biggest changes between the pages and app directories is that, by default, files inside app will be rendered on the server as React Server Components.

This will allow you to automatically adopt React Server Components when incrementally migrating your application from pages to app.

Rendering Environments and Component Types

Note: React introduces new component (module) types: Server, Client, and Shared Components. To learn more about these new types, we recommend reading the Capabilities & Constraints of Server and Client Components and Server Module Conventions RFC.

You’ll have granular control of what components will be in the client-side JavaScript bundle using the new React conventions. There is an ongoing discussion on what exactly the convention will be for defining Client Components and Server Components. We will follow the resolution of this discussion.

For now, it’s worth noting that app allows components (layouts and pages) in a route to be rendered on the server, on the client, or both.

This is different from the pages directory in Next.js, where by default, pages are statically generated unless they have data fetching requirements. In pages, you have the flexibility to decide when (build time or runtime) and where (server-side, client-side, or a combination) a page is rendered by using Next.js data fetching methods (getStaticPropsgetServerSideProps) or fetching the data from the client-side

However, in the app folder, the rendering environment is decoupled from the data fetching method and set at the component level. You will still need to respect the constraints of Client and Server components (e.g. you will not be able to use the getServerSideProps method inside a page or layout that is a client component).

Interleaving Client and Server Components using the children prop

In React, there’s a restriction around importing Server Components inside Client Components because Server Components might have server-only code that should only run on the server (e.g. database or filesystem utilities).

For example, this pattern would not work:

import ServerComponent from './ServerComponent.js';

export default function ClientComponent() {
  return (
    <>
      <ServerComponent />
    </>
  );
}

However, a Server Component can be passed as a child of a Client Component if both are wrapped in another Server Component. For example, you can pass the ServerComponent to the ClientComponent as a child in another Server Component.

// ClientComponent.js
export default function ClientComponent({ children }) {
  return (
    <>
      <h1>Client Component</h1>
      {children}
    </>
  );
}

// ServerComponent.js
export default function ServerComponent() {
  return (
    <>
      <h1>Server Component</h1>
    </>
  );
}

// page.js
// It's possible to import Client and Server components inside Server Components
// because this component is rendered on the server
import ClientComponent from "./ClientComponent.js";
import ServerComponent from "./ServerComponent.js";

export default function ServerComponentPage() {
  return (
    <>
      <ClientComponent>
        <ServerComponent />
      </ClientComponent>
    </>
  );
}

With this pattern, React will know it needs to render ServerComponent on the server before sending the result (which doesn’t contain any server-only code) to the client. From the Client Component’s perspective, its child will be already rendered.

The new router leverages this to allow rendering layouts as client components while the nested layout or page might be a server component.

For example, you can have a Server Component page and a Client Component layout wrapping it:

// The Dashboard Layout is a Client Component
// app/dashboard/layout.js
export default function ClientLayout({ children }) {
  // Can use useState / useEffect here
  return (
    <>
      <h1>Layout</h1>
      {children}
    </>
  );
}

// The Page is a Server Component that will be passed to Dashboard Layout
// app/dashboard/settings/page.js
export default function ServerPage() {
  return (
    <>
      <h1>Page</h1>
    </>
  );
}

Note: This style of composition is an important pattern for rendering Server Components inside Client Components. It sets the precedence of one pattern to learn, and is one of the reasons why we’ve decided to use the children prop.

Data fetching

It’s possible to use Next.js data fetching methods inside layout.js files. Since layouts can be nested, this also means it is possible to fetch data in multiple segments of a route. This is different from the pages directory, where data fetching methods were limited to the page-level.

Data fetching in Layouts

You can fetch data in a layout.js file by using the Next.js data fetching methods getStaticProps or getServerSideProps.

For example, a blog layout could use getStaticProps to fetch categories from a CMS, which can be used to populate a sidebar component:

// app/blog/layout.js
export async function getStaticProps() {
  const categories = await getCategoriesFromCMS();

  return {
    props: { categories },
  };
}

export default function BlogLayout({ categories, children }) {
  return (
    <>
      <BlogSidebar categories={categories} />
      {children}
    </>
  );
}

Multiple data fetching methods in a route

You can also fetch data in multiple segments of a route. For example, a layout that fetches data can also wrap a page that fetches its own data.

Using the blog example above, a single post page can use getStaticProps and getStaticPaths to fetch post data from a CMS:

// app/blog/[slug]/page.js
export async function getStaticPaths() {
  const posts = await getPostSlugsFromCMS();

  return {
    paths: posts.map((post) => ({
      params: { slug: post.slug },
    })),
  };
}

export async function getStaticProps({ params }) {
  const post = await getPostFromCMS(params.slug);

  return {
    props: { post },
  };
}

export default function BlogPostPage({ post }) {
  return <Post post={post} />;
}

Since both app/blog/layout.js and app/blog/[slug]/page.js use getStaticProps, Next.js will statically generate the whole /blog/[slug] route as React Server Components at build time. React Server Components result in less client-side JavaScript and faster hydration.

Statically generated routes improve this further, as the client navigation reuses the cache (server components data) and doesn’t recompute work, leading to less CPU time because you’re rendering a snapshot of the Server Components.

Behavior and priority

Next.js Data Fetching Methods (getServerSideProps and getStaticProps) can only be used in Server Components in the app folder. Different data fetching methods in segments across a single route affect each other.

Using getServerSideProps in one segment will affect getStaticProps in other segments. Since a request already has to go to a server for the getServerSideProps segment, the server will also render any getStaticProps segments. It will reuse the props fetched at build time so the data will still be static, the rendering happens on-demand on every request with the props generated during next build.

Using getStaticProps with revalidate (ISR) in one segment will affect getStaticProps with revalidate in other segments. If there are two revalidate periods in one route, the shorter revalidation will take precedence.

Note: In the future, this may be optimized to allow for full data fetching granularity in a route.

Data fetching and rendering with React Server Components

The combination of Server-Side Routing, React Server Components, Suspense and Streaming have a few implications for data fetching and rendering in Next.js:

Parallel fetching and rendering

Next.js will eagerly initiate data fetches in parallel to minimize waterfalls. In combination with Suspense, React can also start rendering Server Components immediately, before the requests have completed, and can slot in the result as the requests resolve.

For example, if data fetching was sequential, each nested segment in the route couldn’t start fetching data until the previous segment was completed. If rendering was dependent on data fetching, each segment could only render after data fetching was complete.

With parallel fetching, however, each segment can eagerly start data fetching at the same time. With Suspense, rendering also starts immediately, even if the data is not completely loaded. If the data is read before it’s available, Suspense will be triggered.

Partial fetching and rendering

When navigating between sibling route segments, Next.js will only fetch and render from that segment down. It will not need to re-fetch or re-render anything above. This means in a page that shares a layout, the layout will be preserved when a user navigates between sibling pages, and Next.js will only fetch and render from that segment down.

This is especially useful for React Server Components, as otherwise each navigation would cause the full page to re-render on the server instead of rendering only the changed part of the page on the server. This reduces the amount of data transfered and execution time, leading to improved performance.

For example, if the user navigates between the /analytics and the /settings pages, React will re-render the page segments but preserve the layouts:

Note: It will be possible to force a re-fetch of data higher up the tree. We are still discussing the details of how this will look and will update the RFC.

Conclusion

We’re excited about the future of layouts, routing, and data fetching in Next.js. In the next part of the RFC, we’ll discuss:

  • Instant Loading States: With server-side routing, rendering and data fetching happens on the server before navigation completes. This makes it important to show loading UI so the application doesn’t feel unresponsive. We propose framework level support for instant loading states – inline and global loading indicators and skeletons.
  • Offscreen Stashing with Instant Back/Forward: React is planning to add a new <Offscreen /> component that stores a React tree and its state without rendering it to the screen. Leveraging this component, it should be possible to stash routes that have been visited and pre-render routes before they are visited. Navigation backwards and forwards to these routes should be instant and restore any previously stored state.
  • Parallel Routes: If you have two or more tab bars on a page, it should be possible to have two or more parallel nested layouts that can be navigated independently, conceptually similar to an <iframe />.
  • Intercepting Routes: Sometimes it’s useful to be able to intercept a route from within another page. The URL would normally lead to a different part of the UI but not when it’s visited within this context. For example, a tweet that can be expanded and presented inline or a modal photo viewer instead of a standalone gallery.
  • Streaming and selective hydration: We will share more details about how a server centric routing, React Server Components, Suspense and streaming combine to enable new routing paradigms and help performance by reducing what is sent to the client and breaking down work into smaller chunks.

Leave comments and join the conversation on GitHub Discussions.

Source: NEXT.js