Kapeel Kokane Coder by day, content creator by night, learner at heart!

Working with the app directory in Next.js 13

9 min read 2657

Next js 13 New App Directory

Editor’s note: This article was last updated 16 March 2023 to include additional information about nested layouts in Next.js 13. 

Next.js is well known for its file system-based routing, however, through its app directory, Next.js v13 has changed the manner in which many tasks were previously performed.

While still supporting the same file system-based routing, which uses the pages directory, the new app directory introduces the concepts of layouts, error components, and loading components while also leveraging React’s server components for building the UI. In this article, we’ll explore these new features by building a simple app. You can follow along with the source code on GitHub. Let’s get started!

Jump ahead: 

New features in Next.js 13

Before we start working on our project, we’ll review the new features and concepts introduced in Next.js 13.

pages directory vs. app directory

If you’ve worked with previous versions of Next.js, you might already be familiar with the pages directory. Any file created inside of the pages directory would act as a route in the UI. For example, pages/home.jsx would take care of the /home route:

Next js Page Directory Example

The new app directory works alongside the pages directory to support incremental adoption and provide other new features like server-side rendering and static-site generation.

Routing with the app directory

Just like files inside of the pages directory, routing with the app directory is controlled via the folders inside of it. The UI for a particular route is defined by a page.jsx file inside of the folder.

Therefore, a folder structure that looks like app/profile/settings/page.jsx will take care of rendering the /profile/settings route:

Next js App Directory Routes Example

loading.tsx file

loading.tsx is an optional file that you can create within any directory inside of the app folder. It automatically wraps the page inside of a React suspense boundary. The component will be shown immediately on the first load as well as when you’re navigating between the sibling routes.

error.tsx file

error.tsx is an optional file that isolates the error to the smallest possible subsection of the app. Creating the error.tsx file automatically wraps the page inside of a React error boundary. Whenever any error occurs inside the folder where this file is placed, the component will be replaced with the contents of this component.

layout.tsx file

You can use the layout.tsx file to define a UI that is shared across multiple places. A layout can render another layout or a page inside of it. Whenever a route changes to any component that is within the layout, its state is preserved because the layout component is not unmounted.

template.tsx file

template.tsx is similar to the layout.tsx file, but upon navigation, a new instance of the component is mounted and the state is not preserved.

Using layouts and templates allows us to take advantage of a concept known as partial rendering. While moving between routes inside of the same folder, only the layouts and pages inside of that folder are fetched and rendered:

Next js 13 App Template Routing Example

Caveats of using the app directory

With so many changes having been introduced in Next.js 13, there are some things that we need to keep in mind when moving to the app directory from the pages directory.

Mandatory root layout

There must be a file that defines the root layout at the top level of the app directory. This layout is applicable to all the routes in the app. In addition, the root layout must define the <html> and the <body> tags because Next.js doesn’t automatically add them.

Head tag

Inside any folder in the app directory, we’ll create a head.js file that will define the contents of the <head> tag for that folder. The component returned from this head.js file can only return certain limited tags like <title>, <meta>, <link>, and <script>.

Route groups

Every folder inside the app directory contributes to the URL path. But, it is possible to opt-out of it by wrapping the folder name inside of parentheses. All of the files and folders inside of this special folder are said to be a part of that route group:

Nextjs 13 Route Group Example Structure

Server components

By default, all of the components created inside of the app directory are React server components, leading to better performance due to a smaller bundle size. But, if we want to switch to the client component, we need to specify that with the use client directive at the top of the file.

Hands-on with Next.js 13

Now that we’re familiar with the new features in Next.js 13, let’s experiment by running through an example. First, we create a new Next.js project using Create Next App:

npx create-next-app next-13
cd next-13

Let’s run the bootstrapped code as is:

npm run dev

We’re greeted with the familiar homepage:

Create Nextjs Project Homepage

The page and layout file

Let’s create a folder parallel to the pages directory and name it app. Create a layout.js file inside of app with the code below:

export default function Layout({ children }) {
  return (
   <html lang="en">
    <head>
       <title>Next.js</title>
    </head>
     <body>
        {children}
      </body>
    </html>)
}

Create a page.js file with the following code:

import '../styles/globals.css'
export default function Page() {
  return <h1>Hello, Next.js!</h1>;
}

We’ve also imported the global.css file to use the global styles that are already defined. The app directory is still an experimental feature at the time of writing, so we need to set a flag in the next.config.js file to use it:

module.exports = {
  reactStrictMode: true,
  experimental:{appDir: true}
}

Finally, we need to delete the pages/index.js file, which will conflict with the file in the app directory. With that in place, we can now run the dev server:

npm run dev

We see that the root route / now shows the UI corresponding to the app/page.js file:

Next js 13 Root Route UI

Testing the layout

With that in place, let’s test how the layout file impacts the overall UI. First, we’ll write some CSS styles in a layout.module.css file in the same directory:

.header {
  width: 100%;
  height: 50vh;
  background-color: cyan;
  text-align: center;
  font-size: 2rem;
}

Next, we import those styles in the layout.js file and add them to a div inside the body just above the children:

import styles from ./layout.module.css

export default function Layout({ children }) {
  return (
    <html lang="en">
      <head>
        <title>Next.js</title>
      </head>
      <body>
       <div
          className={styles.header}
        >From layout</div>
        <div>
          {children}
        </div>
      </body>
    </html>)
}

The UI now looks like the following:

Layout File Nestjs UI Impact

Let’s add a new folder in the app directory called second. Create a file inside of it named page.js with the following code:

import '../../styles/globals.css'

export default function Page() {
  return <h1>Second route!</h1>;
}

Navigating to the second route http://localhost:3000/second loads the following UI:

Nest js Second Route Layout File UI

The layout file placed inside the app directory is being shared by the page.js in the same directory as well as the page.js inside of the second folder. You can accomplish any common changes that deal with the layout via the layout file.

Nested layouts in Next.js 13

Creating the layout file is not only supported at the root but also at each folder level. Therefore, we can create a layout.js file inside of the sub-folder as well.

Let’s name the second folder profile. We‘ll create two routes inside of this folder, which could manage the /student and /teacher profiles for a school’s web app, for instance:

Nested Layouts Nest js Folder Structure

Create the nested layout.js file

Inside of the profile folder, let’s create the layout.js file and place the following code inside of it:

import React from `react`;

import styles from './layout.module.css'

export default function Layout({ children }) {
  return (
    <div className={styles.innerLayout}>
     <div>
        Announcements: Tomorrow is a holiday!
      </div>
      <div>
        {children}
      </div>
    </div>)
}

The layout above has a div with the Announcement, which will be common for both the /student and the /teacher profile. Just below that, we have the div that renders the {childen} where we’ll plug the actual component that we build.



To differentiate this inner layout with the outer one, let’s add some CSS styles to it in the layout.module.css file that is imported above:

.innerLayout {
  width: 50%;
  height: auto;
  background-color: tomato;
  padding: 1rem;
}

.children {
  padding: 1rem;
}

Create the nested route

With that set, let’s create the student folder first. Inside the student folder, we’ll create a page.js file with the following code:

import React from 'react';
import '../../../styles/globals.css'

const students = {
  1: {
    name: 'John',
    age: 10,
    subjects: ['Math', 'English'],
  }
}

export default function Page() {
  const student = students[1];

  return (
    <React.Fragment>
      <h1>Student profile</h1>
     <div>Name: {student.name}</div>
     <div>Age: {student.age}</div>
    <div>Studies: {student.subjects.join(', ')}</div>
   </React.Fragment>
  )
}

We select a student from a list of students, which could come from a student database. We then render that as the profile of the student.

Upon navigating to the /profile/student route, we see the following:

Create Nested Route Nextjs UI

On the top, the cyan colored section comes from the root layout file. Below that, we have the tomato colored section from the nested layout. Inside of that, we see the common announcement on top and the student profile where the placeholder for {children} was provided in the nested layout.

We can create a similar page.js in the teacher sub-folder inside the profile folder:

import React from 'react';
import '../../../styles/globals.css'

const teachers = {
  1: {
    name: 'Mr Smith',
    age: 30,
    subjects: ['Math', 'English'],
  },
}

export default function Page() {
  const teacher = teachers[1];

  return (
    <React.Fragment>
      <h1>Teacher profile</h1>
      <div>Name: {teacher.name}</div>
      <div>Age: {teacher.age}</div>
      <div>Teaches: {teacher.subjects.join(', ')}</div>
    </React.Fragment>
  )
}

We’ll also make some modifications to the root layout file so it’s easier to navigate between these two routes:

<div className={styles.header}>
  <div>
   <h1 style={{ marginTop: 0 }}>From layout</h1>
  </div>
 <div>
<Link href="/profile/teacher">navigate to teacher profile</Link>
  </div>
  <div>
    <Link href="/profile/student">navigate to student profile</Link>
  </div>
</div>

With that in place, we can navigate between the /profile/student route and the /profile/teacher route and see that they both use the profile nested layout:

Navigate Between Routes Next js Nested Layouts

Testing the error file

Next, let’s check out the error.js file. We’ll create a folder inside of the app folder named breaking, and we’ll create separate page.js and breaking.module.css files:

import '../../styles/globals.css'
import styles from './breaking.module.css';

export default function Page() {
  return (
    <div className={styles.component}>
      <div>BREAKING</div>
      <div>
        <button onClick={(e) => console.log(e.b.c)}>
          break this
        </button>
      </div>
    </div>
  );
}

At the top of the page, use client tells Next.js to render this component as a client component, not a server component, which is the default. We’re handling user input via the button component below:

.component {
  width: 200px;
  height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid black;
  flex-direction: column;
}

.error {
  background-color: tomato;
  color: white;
}

With this CSS in place, the component looks something like the image below:

Next js Test Client Component UI

Now, let’s create an error.js file in the breaking folder. error.jswill act as an error boundary in case any error occurs either inside of this component or any components in its subtree:

`use client';
import '../../styles/globals.css'
import { useEffect } from 'react';
import styles from './breaking.module.css';

export default function Error({
  error,
  reset,
}) {
  useEffect(() => {
    // Log the error to an error reporting service
    console.error(error);
  }, [error]);

  return (
    <div className={styles.error}>
      <div>ERROR</div>
      <p>Something went wrong!</p>
      <button onClick={() => reset()}>Reset error boundary</button>
    </div>
  );
}

Notice that this is also a client component. Two props are passed to this component: the error prop provides more details about the error, and the reset function resets the error boundary. This should be enough to contain the error only to the component and preserve the UI as well as the state of the rest of the application.

Testing the loading file

Next, we’ll test the functionality of the loading.js file. Let’s create one inside of the same folder with the following code:

export default function Loading() {
  return <h1>Loading...</h1>
}

With that in place, we need to set up some navigation. Inside the second/page.js, we place a link to navigate to the /breaking route:

export default function Page() {
  return (<Link href="/breaking">navigate to breaking</Link>);
}

Upon clicking this link, we’ll see that before the breaking component gets mounted, the UI from the loading.js file will appear for a split second:

Test Loading Component UI Display

Data fetching

Lastly, we’ll explore how data fetching in Next.js 13 differs from earlier versions. All of the components inside the app folder are server components by default.

Let’s make the changes to the second.js component to fetch random dog facts from the Dog Facts API:

async function getData() {
 const index = Math.floor(Math.random()*10)
const res = await fetch(`https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=${index}`);
  return res.json();
}

We’ll call this function directly inside of our React component by making it async:

export default async function Page() {
  const data = await getData();
  return (
    <p>
      {data[0].fact}
    </p>
  );
}

The code above fetches the dog fact on the server side and displays it in our component:

Fetch Dog Fact Server Side Displayed Component

Client and server-side rendering

Using the Fetch API natively inside the component provides us with the ability to cache and revalidate the requests as per our requirement. Therefore, the previous utils like getStaticProps and getServerSideProps can be implemented via a single API, as seen below:

// Generates statically like getStaticProps.
fetch(URL, { cache: 'force-cache' });

// Generates server-side upon every request like getServerSideProps.
fetch(URL, { cache: 'no-store' });

// Generates statically but revalidates every 20 seconds
fetch(URL, { next: { revalidate: 20 } });

Conclusion

In this article, we covered almost all of the changes that were introduced with the app directory in Next.js 13. Although at the time of writing, these new features are in beta and are bound to change slightly before being officially released, we can agree that they provide much more flexibility to configure our UI through the loading, error, and layout components. The simplicity of the native Fetch API on server components is also a great addition.

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — .

Kapeel Kokane Coder by day, content creator by night, learner at heart!

6 Replies to “Working with the app directory in Next.js 13”

  1. I don’t think “App” is production ready as the async jsx is not possible yet. dose it work for someone?

    const SubComponent = async () => { // <— async will cause promise error
    return async
    }

    export default function Sidebar() {
    return sidebar // <– this will error and die
    }

  2. Thanks for this. It’s the first Next.js project i built after binging on tutorials

Leave a Reply