Skip to main content

Layouts & Pages

Until now, we have only worked with a single "page" in our Next.js app: "Home", defined by the "Home" component, located within the index.js file in the "pages" folder. In this section, we explore how to add additional routes (pages) to the application.

Adding a Route

Each "route" in our application / site is defined by the name of the file within the "pages" folder. In this case, we only have one route "/" because there is only one file in the "pages" folder: index.js (not including "_app.js" which is used to initialize pages - discussed later on).

We will be adding another route at "/about". When complete, our application will have two routes:

  • Route: "/" - Renders component from "pages/index.js"
  • Route "/about" - Renders component from "pages/about.js"

Here's how to add the /about route:

  1. In the project's pages folder, add a file called: about.js
Pages folder structure
πŸ“‚ pages
┣ πŸ“‚api
┣ πŸ“‚dashboard
┣ πŸ“‚fonts
┃ _app.js
┣ _document.js
┣ about.js <-- added this file
β”— index.js
  1. In the about.js file, add this code:
/pages/about.js
export default function About() {
return (
<>
<p>About</p>
</>
);
}
caution

The name of the exported component does not need to match the file name, since it is the file name that defines the route. However, the name of the component must be capitalized as usual.

  1. Ihe browser, visit the /about route. The page contents will appear.

Nested Routes

It is sometimes necessary to define routes that are "nested" ie: "/dashboard/preferences". To achieve this, we simply need to recreate the nested route as a nested folder structure within the pages folder. Additionally, if we place an index.js file nested within another folder, Next.js will automatically serve that component as the default route for that folder.

Let's expand our current list of routes to add two "dashboard" routes:

  • Route: "/dashboard" - Renders component from "pages/dashboard/index.js"
  • Route "/dashboard/preferences" - Renders component from "pages/dashboard/preferences.js"
  1. In the pages folder, add a folder called dashboard
πŸ“‚ pages
┣ πŸ“‚ api
┣ πŸ“‚ dashboard <-- create this folder
┣ πŸ“‚ fonts
┣ index.js
β”— ... etc

  1. Within the pages/dashboard folder, add two new components:
  • File: "pages/dashboard/index.js"
  • File: "pages/dashboard/preferences.js"
πŸ“‚ pages
┣ πŸ“‚ api
┣ πŸ“‚ dashboard
┃ ┣ index.js <-- add this file
┃ β”— preferences.js <-- add this file
┣ πŸ“‚ fonts
┣ index.js
β”— ... etc
  1. In each file, add the code for a new endpoint:
  • Code snippet for the dashboard/index.js page:
pages/dashboard/index.js
export default function DashboardHome() {
return (
<>
<p>Dashboard Home</p>
</>
);
}
  • Code for the dashboard/preferences page:
pages/dashboard/preferences.js
export default function DashboardPreferences() {
return (
<>
<p>Dashboard Preferences</p>
</>
);
}
  1. When complete, the application has this updated list of routes:
  • Route: "/" - Renders component from "pages/index.js"
  • Route "/about" - Renders component from "pages/about.js"
  • Route: "/dashboard" - Renders component from "pages/dashboard/index.js"
  • Route "/dashboard/preferences" - Renders component from "pages/dashboard/preferences.js"

Layouts

If we have components that are re-used across multiple pages (ie: a common "navbar" / "footer"), we can place these in a custom "Layout" component, which can then be specified for the whole application. Let's begin by creating a very simple layout without any extra components.

  1. In the project's components folder, add a new file called layout.js:
πŸ“¦ my-app
┣ πŸ“‚ components
┃ β”— layout.js <-- add this file
┣ πŸ“‚pages
┣ πŸ“‚public
┣ πŸ“‚styles
┣ README.md
┣ package-lock.json
β”— ... etc
caution

Since layouts are not "pages", they must not be placed within the "pages" folder - instead, place them in a "components" folder, as we have previously done.

  1. In the components/layout.js file, add this code:
components/layout.js
export default function Layout(props) {
return (
<>
<h1>Pages / Routing in Next.js</h1>
<a href="/">Home</a> | <a href="/about">About</a> | <a href="/dashboard">Dashboard</a> | <a href="/dashboard/preferences">Dashboard Preferences</a>
<hr />
<br />
{props.children}
<br />
</>
);
}

You will notice that this looks exactly like any other custom component that we have created before, except instead of rendering specific data passed to props (or within the "state" of the component), it renders "props.children". This is essentially a placeholder that renders the children of the component at a specific point in the layout. When using "props.children", we will be including the component using the form "<Layout></Layout>" instead of the "self-closing" notation (ie: <Layout />) that we have used so far.

  1. To include this layout in all our pages, we must open the aforementioned "_app.js" file located within the "pages" folder and "wrap" the "<Component {...pageProps} />" with our new layout:
pages/_app.js
import Layout from '@/components/layout';
import '@/styles/globals.css';

export default function App({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
  1. Navigate to the website. You will see a common navigation bar with links to all our newly created routes.
info

It is also possible to configure layouts on a page-by-page basis. See the official documentation for more information.

Client-Side Page Transitions

When browsing the site, you may have noticed that each route takes a moment to load, despite the fact there is little difference between the UI / DOM for each route. This is because the entirety of the app (ie: all .js files) is downloaded each time a new route is accessed - this can be confirmed by viewing the network activity in the dev tools. Fortunately, Next.js provides a mechanism to transition between pages without reloading every .js file for the application. This is known as "client-side routing" / "client-side route transitions".

Client-side routing can be achieved by replacing anchor tags (<a>...</a>) with custom "Link" Components (<Link>...</Link>) containing the "href" attribute. Let's test this by refactoring our Layout to use "Link":

components/layout.js
import Link from 'next/link';

export default function Layout(props) {
return (
<>
<h1>Pages / Routing in Next.js</h1>
<Link href="/">Home</Link> | <Link href="/about">About</Link> | <Link href="/dashboard">Dashboard</Link> | <Link href="/dashboard/preferences">Dashboard Preferences</Link>
<hr />
<br />
{props.children}
<br />
</>
);
}

You will find that the page transitions are much faster and that only the relevant .js is loaded when each route is first accessed. Once that route is accessed for a second time, nothing is download from the server - loading the required .js files on demand is known as "Lazy Loading".

It is also important to note that the "Link" component accepts the following props (from the official documentation):

  • href - The path or URL to navigate to. This is the only required prop

  • as - Optional decorator for the path that will be shown in the browser URL bar.

  • legacyBehavior - Changes behavior so that child must be <a>. Defaults to false.

    NOTE: This is important to use if the child is a custom component that wraps the <a> tag

  • passHref - Forces Link to send the href property to its child. Defaults to false

  • prefetch - Prefetch the page in the background. Defaults to true. Any <Link /> that is in the viewport (initially or through scroll) will be preloaded. Prefetch can be disabled by passing prefetch={false}. When prefetch is set to false, prefetching will still occur on hover. Pages using Static Generation will preload JSON files with the data for faster page transitions. Prefetching is only enabled in production.

  • replace - Replace the current history state instead of adding a new url into the stack. Defaults to false

  • scroll - Scroll to the top of the page after a navigation. Defaults to true

  • shallow - Update the path of the current page without rerunning getStaticProps, getServerSideProps or getInitialProps. Defaults to false

  • locale - The active locale is automatically prepended. locale allows for providing a different locale. When false href has to include the locale as the default behavior is disabled.

useRouter Hook

If we wish to achieve the same effect from within our component logic (such as in an "onClick" event handler), we must make use of the "useRouter" hook from "next/router":

import { useRouter } from 'next/router';

This hook will be used to obtain a "router" object from within our component by invoking "useRouter()":

const router = useRouter();

The router object itself has many useful properties, such as:

  • pathname: The current route - the path of the page in /pages.

  • query: The query string parsed to an object, including dynamic route parameters. It will be an empty object during prerendering if the page doesn't have data fetching requirements. Defaults to {}

However, at the moment we are most interested in the "push" method to transition to a new route:

router.push('/'); // navigate to the home route "/"

This method accepts three arguments, ie:

  • url: The URL to navigate to (either as a string or urlObject)

  • as: Optional decorator for the path that will be shown in the browser URL bar.

  • options: Optional object with the following configuration options:

    • scroll: Optional boolean, controls scrolling to the top of the page after navigation. Defaults to true

    • shallow: Update the path of the current page without rerunning getStaticProps, getServerSideProps or getInitialProps. Defaults to false

    • locale: Optional string, indicates locale of the new page