Thomas Desmond Headshot
Thomas Desmond
Cloudflare Technical Marketing Engineer

3 Big Changes in The New Next.js App Folder Architecture

Logo for Next.js 13

With Next.js 13 the app/ folder architecture was released in beta. The goal with the architecture is to make Next.js development easier, faster, and ship less JavaScript to the client.

The new architecture is a big change to Next.js as it changes how you architect your application from the ground up. Luckily, the traditional page/ folder based architecture is still supported and will likely still be supported for a while.

However, it’s important to look at what the future of Next.js looks like. So let’s cover the 3 biggest changes you want to be aware of in the new Next.js app/ folder-based architecture.

  1. Component Rendering
  2. Data Fetching
  3. Routing

The changes to these three categories will be the focus of the article.

❗ Important: Next.js 13 still supports the previous architecture with all pages in the pages/ directory. And actually as of right now the new app/ based architecture is still in beta and not recommended for production. It is currently possible and I would encourage using the pages/ and app/ architectures side by side.

The Architecture

Before jumping into the 3 big changes let’s first cover the biggest change: The Architecture itself. The big change is that your application will now be built inside an app/ folder. As I have mentioned you could continue to build and use what you have in your pages/ folder. But if you are building new features and on the latest version of Next.js consider moving to the app/ folder. The pages and app architecture were designed to be used in conjunction.

1app/
2β”œβ”€ layout.js
3β”œβ”€ page.js
4β”œβ”€ error.js
5β”œβ”€ loading.js
6β”œβ”€ products/
7β”‚  β”œβ”€ page.js
8β”‚  β”œβ”€ loading.js
9node_modules/
10pages/
11β”œβ”€ api/
12β”œβ”€ app.js
13public/

Inside the app/ folder is where you get access to all the new features of Next.js 13. It may seem like a daunting task to re-architect your application but becoming familiar with the changes is the first step.

Component Rendering

By default, the app/ directory uses Server Components. The biggest reason for this is that the more you can render on the server the less JavaScript needs to be sent to the client

The server has limitations that the client does not and vice versa, so you can still create client-side components in Next.js. If you need access to things like useState or useEffect that will need to be a client component. You declare a client component by adding 'use client' at the top of your component.

1'use client'
2import { useState } from 'react';
3
4export default function Counter() {
5  const [active, setActive] = useState(false);
6
7  return (
8    <>
9    // Your code here
10    </>
11  );
12}

So you will be mixing and matching server with client components in your application. Server components are the default and thus should be used whenever possible. This encourages component-driven development. Ensuring that you separate logic that requires the client and the logic that can be executed on the server.

Mixing Server and Client Components in Architecture Tree

I’m excited about this feature and looking forward to leveraging the server more. I do think it adds complexity as you will need to design with a server vs client approach. Don’t take the easy way out and mark everything as use client and lose out on all the server-side benefits.

This change will require some getting used to if you are coming from the traditional Next.js architecture but the improvements Server Components can provide are worth the effort.

πŸ’‘ Want to learn more about Server Components? Check out this previous article: What and Why: React Server Components in Next.js

Next.js Documentation on Rendering Fundamentals

Data Fetching

The new architecture simplifies data fetching by moving to a more standard async/await approach using the fetch() API. This means no more getServerSideProps or getStaticProps.

This also allows you to fetch the data you need at the component level. This is a big improvement in Next.js. No more fetching at the page level and passing data deep down different component layers. Fetch the data when and where you need it.

So now when it comes to building a page in Next.js you don’t need to first ask: β€œWill this page be static or dynamic?” You now have the ability to mix and match dynamic and static components in a single page.

Whether a component is dynamic or static is determined by a cache property on your fetch().

  • force-cache: The default and will generate a static page, the equivalent of getStaticProps
  • no-store: Data will be fetched with each request, the equivalent of getServerSideProps
1export default async function Page() {
2  // This request should be cached until manually invalidated.
3  // Similar to `getStaticProps`.
4  // `force-cache` is the default and can be omitted.
5  const staticData = await fetch(`https://...`, { cache: 'force-cache' });
6
7  // This request should be refetched on every request.
8  // Similar to `getServerSideProps`.
9  const dynamicData = await fetch(`https://...`, { cache: 'no-store' });
10
11  // This request should be cached with a lifetime of 10 seconds.
12  // Similar to `getStaticProps` with the `revalidate` option.
13  const revalidatedData = await fetch(`https://...`, {
14    next: { revalidate: 10 },
15  });
16
17  return <div>...</div>;
18}

The last fetch() example in the code above shows how Incremental Static Regeneration can be achieved with the new fetch() paradigm.

This change of using fetch() instead of getServerSideProps and getStaticProps is not a huge change but you will have to adapt to the new syntax and structure when fetching your data.

Next.js Documentation on Data Fetching

Routing

Routing has been improved with support for layouts, nested routing, loading states, error states, and more.

The new routing strategy builds upon the file system-based routing previously available. So what's inside your app/ folder determines your routes but now there are more special files that determine the UI for your route. Note: I am using the .js file extension but .ts, .jsx, or .tsx are also valid.

  • page.js: Required for every route. Defines the UI and makes the path publicly accessible. Here is where you will bring all the components together to form your page.
  • layout.js: Required at the root level. Used to define the UI across multiple pages. A great example would be your navigation bar. A neat feature is that layouts preserve state and do not re-render if the page you route to has the same layout.
  • loading.js: Optional displayed when a loading state is active for the page. Used in conjunction with React Suspense to show loading states.
  • error.js: Optional gives you the ability to isolate errors and choose what to show if an error does occur.
  • head.js: Optional in this file you define what you want in the <head> tag component.

So for each route, you need a page.js file. The layout.js is required only at the root but can be left out of subsequent routes. The others are there to enhance reusability and separate concerns. Below is a possible routing structure inside your app/ folder.

1app/
2β”œβ”€ page.js
3β”œβ”€ layout.js
4β”œβ”€ error.js
5β”œβ”€ loading.js
6β”œβ”€ profile/
7β”‚  β”œβ”€ page.js
8β”œβ”€ admin/
9β”‚  β”œβ”€ page.js
10β”‚  β”œβ”€ error.js

You can store components, styles, tests, or really anything else in the routing folder structure as long as they do not conflict with the special file names in Next.js.

Hopefully, this change does not cause too many issues for you if you are migrating from traditional Next.js architecture. You will need to embrace the new page.js naming convention and possibly move logic around for your loading and error-handling states. This does provide a better separation between your page's different states.

Next.js Documentation on Routing

Closing

So the big changes happening in the new Next.js app/ folder-based architecture are how you do component rendering, data fetching, and routing in your applications. With the focus on leveraging the server as much as possible.

The new rendering process should help reduce the amount of JavaScript required by the client, leading to faster sites. The new data fetching process is more in line with standard React data fetching practices and allows for fetching at a component level. Routing doubles down on the file-system-based routing and supports new Server Components push in the new architecture.