# Getting Started with Next.js Server Components

> A simple introduction to using Server Components and the new Layouts Folder Structure with Next.js 13

*Published: 2022-10-27*
*Canonical: https://makerkit.dev/blog/tutorials/next-server-components*

---

Vercel has finally shipped Next.js 13, and with it one of the most requested
features: **Layouts**. The new `app` structure is
basically a new framework, full of new functionalities and possibilities
that were simply impossible before.

As a result, Next 13 is easily the de-facto framework that React 18
envisioned when it was released, using the full power of Suspense and Server
Components.

The changes and possibilities Next.js 13 brings are revolutionary:
thanks to the new Server Components and seamless Suspense integration, **Next.
js applications will be smaller and faster**.

Yet, the transition to using Server Components won't be the simplest: as I
said above, the new Next.js 13 Layouts are basically a new framework within
the old one.

In this blog post, I'm to write a step-by-step tutorial to build a Hacker
News Clone using Next.js 13 and utilizing only the new Server Components
Layouts.

This blog post is great for those who want to see a quick overview of how
Server Components work with Next.js.

Are you ready? Let's start.

## Getting Started

To get started, let's start a new Next.js application with `create-next-app`:

```
npx create-next-app --ts
```

After following the prompts, we open the new folder in the IDE.

To enable the new `app` directory, we add the experimental flag to the
Next.js configuration file:

 ```js {% title="next.config.js" %}
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  experimental: {
    appDir: true,
    enableUndici: true
  }
}

module.exports = nextConfig;
```

Additionally, we added the property `enableUndici: true`, which uses the
`undici` package instead of `node-fetch`. The `node-fetch` package has a
weird bug of not allowing payloads larger than 15kb. Weird.

Now, I'll simply delete everything inside the `pages` folder, with the
exception of the `api` folder: Next.js requires the API functions to be
placed within `pages`, for the time being.

So, are we deleting `_app.tsx` and `_document.tsx`? Yes. These are no longer
needed when working with the new Next.js `app` directory. Gone. Done. Ciao.

This introduces lots of question marks:

1. How do we pass data to a component?
2. How do we initialize our applications?
3. Where do we place our app-wide Providers?

Believe me, I was as confused as you are, and still am while writing this.
But let's go on.

## Tree Structure

The new folder tree structure adds some conventions that we need to use for
handling specific states: for example, error boundaries, loading skeletons,
the page's heading, etc.

This is how our folder structure will look like:

```
├── lib
  └── api.ts
  └── types.ts
├── app
    ├── shared
│   ├── ask
      └── page.tsx
│   ├── top
      └── page.tsx
│   ├── latest
      └── page.tsx
│   ├── latest
      └── search.tsx
│   ├── stories
      └── [id]
        └── page.tsx
        └── loading.tsx
│   ├── loading.tsx
│   ├── error.tsx
│   ├── not-found.tsx
│   ├── layout.tsx
│   ├── head.tsx
```

As you can see from the above, every actual page needs to be named `page.tsx`.
If it's named anything but one of the special file names above, it's simply
another file we can import. This improves colocation, so we can add our
files close to where they're used.

Furthermore, you can see ewe added other "special" files:

1. `layout.tsx`: this component is the base layout. It replaces both `_app.
tsx` and `_document.tsx`.
1. `loading.tsx`: this will render a component when our page is `suspended`.
For example, while loading data from our Server Component.
2. `head.tsx`: this will be the base `head`, which replaced the
`next/head` component.
3. `not-found.tsx`: this component will be rendered when hitting a 404
4. `error.tsx`: this component will render when catching an uncaught
exception.

## Creating the Root Layout

All the Next.js applications using the new `app` folder structure need to
define a root layout. This component will replace the existing `_app.tsx` and
`_document.tsx` files.

To create the root layout, we need to add a `layout.tsx` file in the `app`
folder: this is the root component that will be applied to every page that
you add to your `app` folder.

In the case of our application, it will look like the below:

 ```tsx {% title="app/layout.tsx" %}
import '../styles/globals.css';

import Sidebar from './shared/Sidebar';

function RootLayout({ children }: React.PropsWithChildren) {
  return (
    <html lang={'en'}>
      <body>
        <div className={'flex'}>
          <div
            className={'w-2/12 max-w-xs dark:bg-black-400 bg-gray-50 h-screen'}
          >
            <Sidebar />
          </div>

          <div
            className={'bg-white dark:bg-black-500 w-full overflow-x-hidden'}
          >
            <div className={'relative mx-auto h-screen w-full overflow-y-auto'}>
              <div className={'p-6'}>{children}</div>
            </div>
          </div>
        </div>
      </body>
    </html>
  );
}

export default RootLayout;
```

{% alert type="warning" %}
Don't worry if you do not see the "Sidebar" component, you'll be able to see
  the details in the source code linked at the end of this article.
{% /alert %}

## Creating the root Head

The special `head.tsx` component is meant to replace the `Head` component we
import from `next/head`.

By adding the root `head` component, every page will automatically inject the
head component defined below:

 ```tsx {% title="app/head.tsx" %}
function Head() {
  return (
    <>
      <title>Next.js Layouts Demo</title>
    </>
  );
}

export default Head;
```

## Creating a Loading spinner for the root layout

When the component is suspended, we can render a loading indicator just by
adding `layout.tsx` to our root.

 ```tsx {% title="app/loading.tsx" %}
function Loading() {
  return (
    <ContentLoader
      width={1500}
      height={400}
      viewBox="0 0 1500 400"
      backgroundColor="#777"
      foregroundColor="#999"
    >
      {
        // more code here
      }
    </ContentLoader>
}
```

## Adding Server Components Pages

Ok, now let's add our root page component. To add pages, you will use the
convention `page.tsx`. The root `page.tsx` represents the home page of our
application.

In this page will render the Hacker News front page items. So, we will
loading the items using the function `getFrontPage`.

Let's define that first. To create our clone, we will be using the [Hacker
News Algolia API](https://hn.algolia.com/api):

 ```tsx {% title="lib/api.ts" %}
const BASE_URL = `https://hn.algolia.com/api/v1`;

export async function getFrontPage() {
  return fetch(`${BASE_URL}/search?tags=front_page`)
    .then((r) => r.json())
    .then((r) => r.hits);
}
```

Now, we import the function `getFrontPage` and we wrap it in the special
(and not stable) React hook named `use`. Thanks to this hook, the component
will suspend until the data is fetched.

As you can see from the below, we don't even need to use `async/await`:

 ```tsx {% title="app/page.tsx" %}
import { use } from 'react';
import Heading from './shared/Heading';
import { getFrontPage } from '../lib/api';
import StoriesList from './shared/StoriesList';
import SearchBar from './shared/SearchBar';

function HomePage() {
  const stories = use(getFrontPage());

  return (
    <div className={'flex flex-col space-y-6'}>
      <SearchBar />

      <Heading>Front Page</Heading>

      <div>
        <StoriesList stories={stories} />
      </div>
    </div>
  );
}

export default HomePage;
```

## Using Page Parameters

Let's assume we want to use a search bar, and we will display the results in
the `search/page.tsx` page.

Let's create a simple search component: this component will submit a GET
request to the `/search` page, and will append the input `query` as a query
parameter:

 ```tsx {% title="shared/SearchBar.tsx" %}
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';

function SearchBar(
  props: React.PropsWithChildren<{
    value?: string | undefined;
  }>
) {
  return (
    <form className={'w-full'} action={'/search'} method={'GET'}>
      <div
        className={
          'dark:bg-black-300 flex space-x-4 items-center transition-all dark:focus-in:bg-black-400 bg-gray-100 focus-in:bg-gray-200 rounded-lg px-4 py-3 w-full'
        }
      >
        <MagnifyingGlassIcon className={'h-5'} />

        <input
          autoComplete={'off'}
          className={'bg-transparent w-full dark:bg-black-300 outline-none'}
          name={'query'}
          defaultValue={props.value}
          type="text"
          placeholder={'Search...'}
        />
      </div>
    </form>
  );
}

export default SearchBar;
```

Now, we can create our `search/page.tsx` page that will read the query and
submit a request to the HN API.

In the component below, we can also see how to redirect users without the
`ctx` API we used to use using `getServerSideProps`.

 ```tsx {% title="search/page.tsx" %}
import { use } from 'react';
import { redirect } from 'next/navigation';

import StoriesList from '../shared/StoriesList';
import { getResultsFromQuery } from '../../lib/api';
import SearchBar from '../shared/SearchBar';
import Heading from '../shared/Heading';

function SearchPage(props: { searchParams: Record<string, string> }) {
  const query = props.searchParams.query;

  if (!query) {
    return redirect('/');
  }

  const data = use(getResultsFromQuery({ query, page: 0 }));

  return (
    <div className={'flex flex-col space-y-6'}>
      <SearchBar value={query} />

      <Heading>
        {data.nbHits.toLocaleString()} Search Results found for &quot;{query}
        &quot;
      </Heading>

      <StoriesList stories={data.hits} />
    </div>
  );
}

export default SearchPage;
```

And below is the API function:

 ```tsx {% title="lib/api.ts" %}
export async function getResultsFromQuery(params: {
  page: number;
  query: string;
}) {
  const queryParams = `tags=story&hitsPerPage=100&page=${params.page}&query=$
{params.query}`;

  return fetch(`${BASE_URL}/search?${queryParams}`).then((r) => r.json());
}
```

## Using Page segment parameters

Finally, let's see an example to retrieve the page's segment parameters
using the new Server Components. The page below will display the detailed
data from a Hacker News story.

To add a dynamic segment, we will simply add a `[id]` folder, and add the
`page.tsx` file within it:

 ```tsx {% title="app/story/[id]/page.tsx" %}
import { use } from 'react';
import { redirect } from 'next/navigation';
import Link from 'next/link';
import { ArrowLeftIcon } from '@heroicons/react/24/outline';

import { getCommentsByStoryId, getItemById } from '../../../lib/api';
import Heading from '../../shared/Heading';
import CommentsSection from '../../shared/CommentsSection';
import { StoryItemChild } from '../../../lib/types';

function StoryPage(props: { params: Record<string, string> }) {
  const id = props.params.id;

  if (!id) {
    return redirect('/');
  }

  const item = use(getItemById(id));
  const comments = use(getCommentsByStoryId(id));

  return (
    <div>
      <div className={'flex flex-col space-y-4'}>
        <Link className={'hover:underline'} href={'../'}>
          <span className={'flex items-center text-sm font-medium space-x-2'}>
            <ArrowLeftIcon className={'h-4'} />
            <span>Back</span>
          </span>
        </Link>

        <div className={'flex flex-col space-y-2'}>
          <Heading>
            {item.url ? (
              <Link
                className={'hover:underline'}
                target={'_blank'}
                href={item.url}
              >
                {item.title}
              </Link>
            ) : (
              item.title
            )}
          </Heading>

          <div className={'text-sm text-gray-500 dark:text-gray-400'}>
            <p>
              {item.points} points by {item.author}
            </p>
          </div>
        </div>

        <div className={'max-w-2xl'}>
          <StoryChildrenRenderer items={item.children} />
        </div>

        <div>
          <CommentsSection comments={comments} />
        </div>
      </div>
    </div>
  );
}

function StoryChildrenRenderer(
  props: React.PropsWithChildren<{
    items: StoryItemChild[];
  }>
) {
  return (
    <div className={'flex flex-col space-y-4'}>
      {props.items.map((child) => {
        return (
          <div key={child.id}>
            <div dangerouslySetInnerHTML={{ __html: child.text }} />

            <StoryChildrenRenderer items={child.children ?? []} />
          </div>
        );
      })}
    </div>
  );
}

export default StoryPage;
```

Below we define the API function to fetch an item and its comments (which
are not nested, for simplicity):

```tsx
export async function getItemById(storyId: string): Promise<StoryItem> {
  return fetch(`${BASE_URL}/items/${storyId}`).then((r) => r.json());
}

export async function getCommentsByStoryId(
  storyId: string
): Promise<StoryComment[]> {
  return fetch(`${BASE_URL}/search?tags=comment,story_${storyId}`)
    .then((r) => r.json())
    .then((response) => response.hits);
}
```

As we want to display a different loading screen for the item's page, we can
add a `loading.tsx` component within the `story` folder:

 ```tsx {% title="app/story/loading.tsx" %}
import { Facebook } from 'react-content-loader';

function Loading() {
  return (
    <Facebook
      width={1500}
      height={400}
      viewBox="0 0 1500 400"
      backgroundColor="#777"
      foregroundColor="#999"
    />
  );
}

export default Loading;
```

## Source Code

You can grab the [full source code on GitHub](https://github.com/makerkit/hn-next). Please follow the README to run
the example locally. If you have any question, please shoot!
