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.
- Component Rendering
- Data Fetching
- 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 newapp/
based architecture is still in beta and not recommended for production. It is currently possible and I would encourage using thepages/
andapp/
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.
app/
├─ layout.js
├─ page.js
├─ error.js
├─ loading.js
├─ products/
│ ├─ page.js
│ ├─ loading.js
node_modules/
pages/
├─ api/
├─ app.js
public/
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.
'use client'
import { useState } from 'react';
export default function Counter() {
const [active, setActive] = useState(false);
return (
<>
// Your code here
</>
);
}
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.
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 ofgetStaticProps
-
no-store
: Data will be fetched with each request, the equivalent ofgetServerSideProps
export default async function Page() {
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
const staticData = await fetch(`https://...`, { cache: 'force-cache' });
// This request should be refetched on every request.
// Similar to `getServerSideProps`.
const dynamicData = await fetch(`https://...`, { cache: 'no-store' });
// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
});
return <div>...</div>;
}
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.
app/
├─ page.js
├─ layout.js
├─ error.js
├─ loading.js
├─ profile/
│ ├─ page.js
├─ admin/
│ ├─ page.js
│ ├─ 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.
Top comments (0)