This RFC outlines the biggest update to Next.js since it was introduced in 2016:
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.
This RFC will be divided into two parts:
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:
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.
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.
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.
getStaticProps
, getServerSideProps
) which can be used at the page (route) level. These methods are used to determine if a page should be Statically Generated (getStaticProps
) or Server-Side Rendered (getServerSideProps
). In addition, you can use Incremental Static Regeneration (ISR) to create or update static pages after a site is built.getServerSideProps
).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.
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:
layout.js
, page.js
, and in the second part of the RFC loading.js
).app
directory. This is currently not possible in pages
.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:
/
root segmentdashboard
segmentsettings
segmentNote: The name route segment was chosen to match the existing terminology around URL paths.
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.
A 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:
You can nest two or more layouts together to form nested layouts.
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:
_app.js
) and custom Document (_document.js
) since it applies to all routes.<html>
and <body>
tags).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
.
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/*
.
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:
/
root segmentdashboard
segmentNote:
page.js
file should default export a React component.page.(js|jsx|ts|tsx)
exactly. If you do not export a Page component, Next.js will throw an error.Recap:
page.js
.layout.js
.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>
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.
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
.
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 (getStaticProps
, getServerSideProps
) 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).
children
propIn 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.
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.
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}
</>
);
}
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.
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.
The combination of Server-Side Routing, React Server Components, Suspense and Streaming have a few implications for data fetching and rendering in Next.js:
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.
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.
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:
<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.<iframe />
.Leave comments and join the conversation on GitHub Discussions.
Source: NEXT.js