Next.js 13 Complete Guide to Server Components and Application Catalogs

Unleash the full potential of Next.js 13 with the most complete and authoritative tutorial on using server components and application catalogs.

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 appCatalog brings many improvements to the existing pagesCatalog, 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 appthe 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 appadd 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? appFrom now on, we can create various types of components in the directory using specific filename conventions specific to Next.js :

  • The page is defined aspage.tsx
  • The layout is defined aslayout.tsx
  • The error is defined aserror.tsx
  • The loading state is defined asloading.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, cookiesetc.

Server components can import client components .

There is no need to specify a notation to define server components: in fact, server components are appthe 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 clientdo 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, cookiesetc.

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 pagesdirectory 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 appan 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.tsxit is a page component, layout.tsxand so on.

Don't worry, we'll cover all the different types of components in the next section.

hosting

appAn important side effect of the new directory is that it allows us to co-locate files. Since file names are conventional, we can define appany 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.tsxwill 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 appconventions in directories layout.tsx: Next.js will automatically wrap all pages in the folder where the layout is defined.

app/(site)/layout.tsxFor 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 SiteLayoutwrapped 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 usehook, an experimental hook in React for Suspensefetching 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:

  1. We use usehooks to get data in the layout component
  2. We data.userconditionally render ProfileDropdownthe component based on the prop

Note: We use usehooks to get the data in a (seemingly) synchronous way. This is because usehooks are used under the hood Suspense, which allows us to write asynchronous code in a synchronous manner.

Using Async/Await in server components

Another way is to make the component asynca component and get the data async/awaitfrom 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/headersuse 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 requestobject. 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 redirectwill 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 appdefine 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.tsxin 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 generateMetadatafunctions:

// 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.tsxdefine 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 notFounddisplayed when used in conjunction with the function. pagesThis 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 pagesdirectory to define custom 404 and 500 pages. This is because Next.js does not support appcustom 404 and 500 pages in directories.

font

We can use this package next/fontto load fonts in our application.

To do this, we need to define a client component and import it app/layout.tsxinto 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 appto create route.tsxa file named in the directory.

API routing now uses the standard Request object instead of express-like reqand 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/navigationimported 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 serverthe 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 actionas forma property of the component
  • Call an action formActionfrom a component using the attributebutton
  • Use useTransitionhooks 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.

Guess you like

Origin blog.csdn.net/jslygwx/article/details/132392323