Unleash the full potential of Next.js 13 with the most complete and authoritative tutorial on using server components and application catalogs.
Table of contents
- What does Next.js 13 bring?
- Server Components (RSC)
- layout
- Server Actions Server Actions
- Enhanced Router Enhanced Router
- What are server components?
- Server components and client components
- Define server components
- Define client components
- App application directory
- file structure
- hosting
- Layouts layout
- Load data in layout components
- Using Async/Await in server components
- Read Cookies and Headers
- redirect from layout
- Next.js redirect side effects
- Pages page
- Page Metadata and SEO Search Engine Optimization
- loading indicator
- error handling
- font
- API interface routing
- Handling Webhooks
- Server Actions Server Actions
- in conclusion
What does Next.js 13 bring?
Next.js 13 is the major version of the most popular React framework: in fact, it comes with a new routing system, also called App Router. In many ways, this new routing system is a complete rewrite of the previous routing system, fundamentally changing the way we will write Next.js applications .
The new app
Catalog brings many improvements to the existing pages
Catalog, which is the new default way to write Next.js apps after a period of experimental release.
Server Components (RSC)
The biggest change in the new App Router is the introduction of server components, a new type of React component that runs on the server and returns compiled JSX sent to the client . Server components can be used to render the skeleton of a page, fetch data from the server in parallel and pass it to "client components".
Server components are app
the default component type in new directories.
layout
Layouts are powered by server components and are the basic components that surround the page. Next.js layouts can be used not only to display common UI across pages, but also to reuse logic and data extraction across pages.
The layout also solves the waterfall problem, a common problem with the current Next.js routing system. In fact, with the new App Router we can fetch data in parallel and pass it to page components: a substantial performance improvement over the current routing system .
Server Actions Server Actions
Still in alpha stage, server operations are a new way to execute functions on the server rather than connecting them through API handlers. Server operations are useful for performing server-side logic such as sending emails or updating databases. Server actions are useful for submitting forms, performing server-side logic, and redirecting users to new pages.
Additionally, server operations allow us to re-validate data fetched from server components , removing the need for complex client-side state management due to data mutations such as updating the Redux store.
Enhanced Router Enhanced Router
Using regular file names, we can app
add various types of components in the directory. This is a big improvement over the current routing system, which requires us to use a specific directory structure to define pages, API handlers, etc.
What does it mean? app
From now on, we can create various types of components in the directory using specific filename conventions specific to Next.js :
- The page is defined as
page.tsx
- The layout is defined as
layout.tsx
- The error is defined as
error.tsx
- The loading state is defined as
loading.tsx
What are server components?
Server components are a new type of React component that run on the server and return compiled JSX sent to the client. Next.js released a new application catalog in Next.js 13 that fully embraces server components by making them the default type of component.
This is a major shift from traditional React components that run on the server and client. In fact, as we specified, React Server components are not executed on the client.
Therefore, we need to remember some constraints when using server components:
- Server components cannot use browser-only APIs
- Server components cannot use React hooks
- Server component cannot use Context context
So, what are they used for?
React Server components are useful for rendering the skeleton of a page, while leaving the interactive bits to so-called "client-side components".
Despite the name, "client-side components" are (imho unfortunately) also server-rendered, they run on both the server and the client.
React server components can be useful because they allow us to:
- Render pages faster
- Reduce the amount of JavaScript that needs to be sent to the client
- Improve routing performance for server-rendered pages
In short, we use server components to get data from the server and render the skeleton of the page: we can then pass the data to a "client component".
Server components and client components
As we have seen, server components are useful for rendering the framework of a page, while client components are what we know today.
This comparison from the Next.js documentation is a great way to understand the difference between the two.
Define server components
Server components do not need to have such a defined representation: the server component is the default component when rendered in the application catalog.
We cannot use React hooks, contexts or browser-only APIs in server components. However, we can only use server component APIs such as headers
, cookies
etc.
Server components can import client components .
There is no need to specify a notation to define server components: in fact, server components are app
the default component type in new directories.
Assuming that the component is not a child of the client component ServerComponent
, it will be rendered on the server and sent to the client as compiled JSX:
export default function ServerComponent() {
return <div>Server Component</div>;
}
Define client components
Instead, in the Next.js app directory, we need to specifically define the client component .
We can use client
do this by specifying the directive at the top of the file:
'use client';
export default function ClientComponent() {
return <div>Client Component</div>;
}
When we use client-side components, we can use React hooks, contexts, and browser-only APIs. However, we cannot just use certain server component APIs such as headers
, cookies
etc.
Note: Client components cannot import server components , but you can pass server components as subcomponents or properties of client components.
App application directory
The new "app" directory released in Next.js 13 is a new experimental way to build Next.js applications . It coexists with the directory and we can use it to incrementally migrate existing projects to a new pages
directory structure.
This new directory structure is more than just a new way to write applications, it's an entirely new routing system that's far more powerful than the current routing system.
file structure
What does the new Next.js 13 file structure look like? Let's take a look at the sample app we'll be using in this tutorial.
Here is app
an example of a Next.js 13 app with the new directory:
- app
- layout.tsx
- (site)
- page.tsx
- layout.tsx
- app
- dashboard
- page.tsx
- layout.tsx
As you can see, the name of the file reflects the type of component. For example, it is a layout component, but page.tsx
it is a page component, layout.tsx
and so on.
Don't worry, we'll cover all the different types of components in the next section.
hosting
app
An important side effect of the new directory is that it allows us to co-locate files. Since file names are conventional, we can define app
any file in the directory without it becoming a page component.
For example, we can place the component for a specific page directly in the folder where it is defined:
- app
- (site)
- components
- Dashboard.tsx
- hooks
- use-fetch-data-hook.ts
- page.tsx
Why in (site)
parentheses? By using parentheses, we make the directory site
"pathless", which means we can create new layouts, load files, and pages in the site directory without adding new path segments to the route.
(site)
All pages under will be /
accessed from the root path : for example, the page app/(site)/page.tsx
will be located at /
.
Layouts layout
Layouts are one of the biggest new features implemented by the new app router.
Layouts are the fundamental component that wraps pages: this is used not only to display a common UI across pages, but also to reuse data fetching and logic.
Next.js requires a root layout component:
export const metadata = {
title: 'Next.js Tutorial',
description: 'A Next.js tutorial using the App Router',
};
async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang={
'en'}>
<body>{
children}</body>
</html>
);
}
export default RootLayout;
Layouts are defined using app
conventions in directories layout.tsx
: Next.js will automatically wrap all pages in the folder where the layout is defined.
app/(site)/layout.tsx
For example, if we define a layout in , Next.js will wrap app/(site)
all pages in the directory with this layout:
export default async function SiteLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div>
<main>
{
children}
</main>
</div>
);
}
Result - app/(site)
all pages in the directory will be SiteLayout
wrapped with components.
Load data in layout components
The layout component is also very useful if you need some data needed to load all the pages of the directory: for example, we can load the user's profile in the layout component and pass it to the page component.
To fetch data in a layout component in Next.js, we can use the new use
hook, an experimental hook in React for Suspense
fetching data on the server.
import {
use } from "react";
export default function SiteLayout({
children,
}: {
children: React.ReactNode;
}) {
const data = use(getData());
return (
<div>
<header>
{
data.user ? <ProfileDropown /> : null }
</header>
<main>
{
children}
</main>
</div>
);
}
function getData() {
return fetch('/api/data').then(res => res.json());
}
In the example above:
- We use
use
hooks to get data in the layout component - We
data.user
conditionally renderProfileDropdown
the component based on the prop
Note: We use
use
hooks to get the data in a (seemingly) synchronous way. This is becauseuse
hooks are used under the hoodSuspense
, which allows us to write asynchronous code in a synchronous manner.
Using Async/Await in server components
Another way is to make the component async
a component and get the data async/await
from getData
:
export default async function SiteLayout({
children,
}: {
children: React.ReactNode;
}) {
const data = await getData()
return (
<div>
<header>
{
data.user ? <ProfileDropown /> : null }
</header>
<main>
{
children}
</main>
</div>
);
}
function getData() {
return fetch('/api/data').then(res => res.json());
}
Read Cookies and Headers
If you are using the server component, you can next/headers
use Read Cookies and Headers from the package.
NOTE: At the time of writing, we can only use these functions to read their values, not set or delete them.
import {
cookies } from 'next/headers';
export function Layout(
{
children }: {
children: React.ReactNode },
) {
const lang = cookies.get('lang');
return (
<html lang={
lang}>
<body>
{
children}
</body>
</html>
);
}
Don’t worry if you feel like something is missing, it’s not just you. Actually, unlike getServerSideProps
, we cannot access the request
object. That's why Next.js exposes these utilities to read data from requests.
redirect from layout
Within the layout we can also redirect the user to a different page.
For example, if we want to redirect the user to the login page if they are not authenticated, we can do this in the layout component:
import {
use } from 'react';
import {
redirect } from 'next/navigation';
function AuthLayout(
props: React.PropsWithChildren,
) {
const session = use(getSession());
if (session) {
return redirect('/dashboard');
}
return (
<div className={
'auth'}>
{
props.children}
</div>
);
}
function getSession() {
return fetch('/api/session').then(res => res.json());
}
Now, we can use the function in the layout component loadSession
:
import {
use } from 'react';
function AuthLayout(
props: React.PropsWithChildren,
) {
const response = use(loadSession());
const data = response.data;
// do something with data
return (
<div className={
'auth'}>
{
props.children}
</div>
);
}
Next.js redirect side effects
The new Next.js function redirect
will throw an error: in fact, its return type is never
. If you find an error, you need to be careful and make sure you follow the redirects thrown by the error.
To do this we can use some utilities exported by the Next.js package:
import {
use } from 'react';
import {
isRedirectError,
getURLFromRedirectError,
} from 'next/dist/client/components/redirect';
import {
redirect } from "next/navigation";
async function loadData() {
try {
const data = await getData();
if (!data) {
return redirect('/login');
}
const user = data.user;
console.log(`User ${
user.name} logged in`);
return user;
} catch (e) {
if (isRedirectError(e)) {
return redirect(getURLFromRedirectError(e));
}
throw e;
}
}
function Layout(
props: React.PropsWithChildren,
) {
const data = use(loadData());
// do something with data
return (
<div>
{
props.children}
</div>
);
}
Pages page
To define pages in the new application directory, we use special conventions page.tsx
. This means, if we want to app
define a page in the directory, we need to name the file page.tsx
.
For example, if we wanted to define the home page of your website, we could place the page app/(site)
in the directory and name it page.tsx
:
function SitePage() {
return <div>Site Page</div>;
}
export default SitePage;
Page Metadata and SEO Search Engine Optimization
To specify the page's metadata, we can export the properties page.tsx
in the file constant metadata
:
export const metadata = {
title: 'Site Page',
description: 'This is the site page',
};
If you need to access dynamic data, you can use the following generateMetadata
functions:
// path: app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
See full documentation for generating static paths .
loading indicator
We may want to display a loading indicator when navigating between pages. To do this we can use a file that can be defined in each directory loading.tsx
:
// path: /app/loading.tsx
export default function Loading() {
return <div>Loading...</div>;
}
Here you can add any components you want to display when the page loads, such as a top bar loader or a loading spinner, or both.
error handling
Currently, you can not-found.tsx
define a "404 Not Found" page using the convention:
export default function NotFound() {
return (
<>
<h2>Not Found</h2>
<p>Could not find requested resource</p>
</>
);
}
This file is only notFound
displayed when used in conjunction with the function. pages
This is why it is still recommended to use custom 400 and 500 pages with the old directory.
Customize 404 and 500 pages
As of this writing, we need to stick with the regular pages
directory to define custom 404 and 500 pages. This is because Next.js does not support app
custom 404 and 500 pages in directories.
font
We can use this package next/font
to load fonts in our application.
To do this, we need to define a client component and import it app/layout.tsx
into the root layout file:
// path: app/font.tsx
'use client';
import {
Inter } from 'next/font/google';
import {
useServerInsertedHTML } from 'next/navigation';
const heading = Inter({
subsets: ['latin'],
variable: '--font-family-heading',
fallback: ['--font-family-sans'],
weight: ['400', '500'],
display: 'swap',
});
export default function Fonts() {
useServerInsertedHTML(() => {
return (
<style
dangerouslySetInnerHTML={
{
__html: `
:root {
--font-family-sans: '-apple-system', 'BlinkMacSystemFont',
${
sans.style.fontFamily}, 'system-ui', 'Segoe UI', 'Roboto',
'Ubuntu', 'sans-serif';
--font-family-heading: ${
heading.style.fontFamily};
}
`,
}}
/>
);
});
return null;
}
Afterwards, we can import the component in the root layout Fonts
:
import Fonts from '~/components/Fonts';
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<Fonts />
<body>{
children}</body>
</html>
);
}
API interface routing
The new application catalog also supports API routing. The convention for defining API routes is app
to create route.tsx
a file named in the directory.
API routing now uses the standard Request object instead of express-like req
and res
-objects.
When we define API routes, we can export handlers for the methods we want to support. For example, if we want to support GET and POST methods, we can export the GET and POST functions:
// path: app/api/route.tsx
import {
NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({
hello: 'world' });
}
export async function POST(
request: Request
) {
const body = await request.json();
const data = await getData(body);
return NextResponse.json(data);
}
If we want to manipulate the response, for example by setting cookies, we can use the following NextResponse object:
export async function POST(
request: Request
) {
const organizationId = getOrganizationId();
const response = NextResponse.json({
organizationId });
response.cookies.set('organizationId', organizationId, {
path: '/',
httpOnly: true,
sameSite: 'lax',
});
return response;
}
In API routing, just like in the server component, we can also redirect users using functions next/navigation
imported from:redirect
import {
redirect } from 'next/navigation';
export async function GET(
request: Request
) {
return redirect('/login');
}
Handling Webhooks
Handling webhooks is a common use case for API routing, and getting the original body of the request is now much simpler. Actually, we can request.text()
get the original body of the request using:
// path: app/api/webhooks.tsx
export async function POST(
request: Request
) {
const rawBody = await request.text();
// handle webhook here
}
Server Actions Server Actions
Server operations are a new concept introduced in Next.js 13. They are a way of defining server-side operations that can be called from the client. All you need to do is define a function and use use server
the keyword at the top.
For example, the following is a valid server operation:
async function myActionFunction() {
'use server';
// do something
}
If you want to define a server action from a client component, you need to export the action from a separate file and import it into the client component. The file requires keywords at the top use server
.
'use server';
async function myActionFunction() {
// do something
}
To call server operations from the client, you can do it in several ways
- Define the operation
action
asform
a property of the component - Call an action
formAction
from a component using the attributebutton
- Use
useTransition
hooks to call the action (if it changes data) - Simply call the operation like a normal function (if it doesn't change the data)
If you want to learn more about server operations, check out the article on Next.js server operations .
in conclusion
In this article, we learned how to use the new experimental application router in Next.js 13.
The patterns and conventions we learned in this article are still experimental and may change in the future. However, they are already very useful and we can already start using them in our projects.
Did you know you can build your own SaaS applications with our Next.js 13 SaaS Starter Kit? This is a full-featured SaaS starter kit that includes everything you need to build a SaaS application with Next.js 13, including authentication, billing, and more.
While it's still experimental, you can start building SaaS apps today with Next.js 13 and future-proof your apps for the future without painful migrations from legacy architectures.