blog.itcode.devblog.itcode.dev

[Next.js] Exploring Next.js 13

When working on a React project, chances are you're going to end up using `Next.js`. I recently revamped my blog and ran `create-next-app`, and it seems there are some notable changes now that it's been updated to version 13. Some of these changes differ quite a bit from the previous usage, which gave me a bit of trouble, so I wanted to cover these changes in this post.

[Next.js] Exploring Next.js 13

When working on a React project, chances are you're going to end up using `Next.js`. I recently revamped my blog and ran `create-next-app`, and it seems there are some notable changes now that it's been updated to version 13. Some of these changes differ quite a bit from the previous usage, which gave me a bit of trouble, so I wanted to cover these changes in this post.
RWB0104
@RWBwritten at 2023-09-23 11:41:27

When working on a React project, chances are you're going to end up using Next.js. I recently revamped my blog and ran create-next-app, and it seems there are some notable changes now that it's been updated to version 13.

Some of these changes differ quite a bit from the previous usage, which gave me a bit of trouble, so I wanted to cover these changes in this post.

The changes defined in version 13 are as follows.

  • app Directory: easier, faster, less client-side JavaScript
    • Layouts
    • React Server Components
    • Streaming
  • Turbopack: a Rust-based Webpack alternative up to 700x faster
  • New next/image: an even faster image component using native browser lazy loading
  • New @next/font: self-hosted fonts for zero layout shift
  • Improved next/link: a simplified link API with an automatic a tag

Layout Shift refers to a phenomenon where the pre-render UI is shown to the user during the client's rendering process. A similar term is FOUC (Flash Of Unstyled Content). It occurs due to delays in asynchronously loaded resources (fonts, images, etc.), and can harm a site's credibility and appearance by showing users an unintended UI.

A few of these are internal performance improvements, so there's no change in how the code is used.

The app Directory feature, which was offered as an experimental feature starting from version 12, seems to have become the main approach now. I remember considering trying it back then, but skipping it since it was labeled experimental.

This is the most noticeable change in version 13. This item is a change to routing, one of the core features.

The example below is based on TypeScript.


Before

The existing project structure looked like this.

BASH

📦src
 ┣ 📂page
 ┃ ┣ 📂mypage
 ┃ ┃ ┣ 📜info.tsx # /mypage/info
 ┃ ┃ ┗ 📜update.tsx # /mypage/update
 ┃ ┣ 📜index.tsx # /
 ┃ ┗ 📜login.tsx # /login

As shown above, page files are created inside a folder called page. The file name itself becomes the URL. Since only page components are allowed under page/, there's a drawback that sub-components or design files can't be placed there.


After

Applying the app Directory in Next.js 13 makes the following structure the default.

BASH

📦src
 ┣ 📂app
 ┃ ┣ 📂login
 ┃ ┃ ┗ 📜page.tsx # /login
 ┃ ┣ 📂mypage
 ┃ ┃ ┣ 📂info
 ┃ ┃ ┃ ┗ 📜page.tsx # /mypage/info
 ┃ ┃ ┗ 📂update
 ┃ ┃    ┗ 📜page.tsx # /mypage/update
 ┃ ┗ 📜page.tsx # /

Unlike before where the file name was the URL, now the folder name becomes the URL, and a page file must exist under that folder name.

In the new routing scheme, each file name has a defined role, as shown below.

File NameDefinition
layoutShared UI component for the current and child pages
pagePage component
loadingLoading UI component for the current and child pages
not-found404 error UI component for the current and child routes
errorError UI component for the current and child routes
global-errorGlobal error UI component
routeServer-side API endpoint
templatePage template UI component
defaultFallback UI component for parallel routes

Simply adding a file with one of the names defined above under each folder lets you achieve that behavior, without any separate wiring.

🖼️ Next.js 13 page rendering structure

The official Next.js documentation depicts the behavior of each component like this. The Layout component and Template component wrap around it, and after the declared loading and error components are bundled together, the actual page component is finally rendered.

In particular, the layout component located directly under app is a global layout applied in common to every page. It replaces the role of _app and _document from the previous version.

Components marked as current and child in the table above, such as layout and loading, are all applied to child routes as well.

🖼️ Next.js 13 child page rendering structure

Child components are rendered as shown above. You can see that the parent's shared components are rendered together. Note that they aren't overridden.

Here's what this looks like in code.


  • app/layout.tsx

TSX

// app/layout.tsx
export default function Layout({children}: PropsWithChildren) {
  return (
    <div style={{backgroundColor: '#8F85ED', padding: 20}}>
      <p>app/layout</p>

      {children}
    </div>
  )
}

  • app/template.tsx

TSX

// app/template.tsx
export default function Template({children}: PropsWithChildren) {
  return (
    <div style={{backgroundColor: '#8B9DF7', padding: 20}}>
      <p>app/template</p>

      {children}
    </div>
  )
}

  • app/page.tsx

TSX

// app/page.tsx
export default function Page() {
  return (
    <div style={{backgroundColor: '#8AAFE1', padding: 20}}>
      <p>app/page.tsx</p>
    </div>
  )
}

The result is rendered as shown below. (CSS applied arbitrarily)

🖼️ Rendering result

As shown in the diagram above, the <Layout /> component is positioned outermost, with the <Template /> component from the same path as its child. The actual <Page /> component is then rendered inside, completing a single page.


Now, what would it look like if we created a main folder under app/ to add a child route?

Suppose the main/ folder has the following files.


  • app/home/layout.tsx

TSX

// app/home/layout.tsx
export default function Layout({children}: PropsWithChildren) {
  return (
    <div style={{backgroundColor: '#ED8774', padding: 20}}>
      <p>app/home/layout</p>

      {children}
    </div>
  )
}

  • app/home/template.tsx

TSX

// app/home/template.tsx
export default function Template({children}: PropsWithChildren) {
  return (
    <div style={{backgroundColor: '#F7A079', padding: 20}}>
      <p>app/home/template</p>

      {children}
    </div>
  )
}

  • app/home/page.tsx

TSX

// app/home/page.tsx
export default function Page() {
  return (
    <div style={{backgroundColor: '#E1A97A', padding: 20}}>
      <p>app/home/page.tsx</p>
    </div>
  )
}

🖼️ Rendering result

You can see that the parent's shared components <Layout /> and <Template /> render on top, and below them the elements from the main/ folder are rendered.

If you make good use of this structure, it becomes easy to apply layouts that should be shared across the whole service, and it's also convenient for grouping URLs by page topic to compose layouts. Additionally, making appropriate use of shared components this way can help prevent unnecessary component re-renders.

One thing to watch out for with this structure, though, is that a shared component declared at a higher level apparently cannot be arbitrarily excluded at a lower level. In other words, since you can't exclude a parent's layout.tsx or template.tsx for a specific child route, you should design shared components so that such cases don't arise, or so that they have no impact even if they do.

For more details, check the official Next.js 13 routing documentation.

Next.js components are divided into server and client components. Depending on which type a component is, the roles it can perform differ slightly.

Featureserverclient
Data fetching
Direct access to backend resources
Managing sensitive server-side information (tokens, etc.)
Keeping large server dependencies / reducing client-side JS
Using event listeners (onChange, etc.)
Using state management and lifecycle (useEffect, etc.)
Using browser APIs (Navigator API, etc.)
Using custom Hooks
Using React class components

Ultimately, this specifies whether a given component's rendering method is SSR or CSR. In previous versions, there was no explicit declaration — the distinction was made based on the use of methods like getServerSideProps or getStaticProps.

Starting from version 13, you distinguish them by writing use client at the top of a component file. The default is a server component — if nothing is specified, it behaves as a server component.

Here's what this looks like in code.

TSX

// Server component (no need to declare anything)

export default function Component(): ReactNode
{
    // ...
}

TSX

// Client component
'use client'

export default function Component(): ReactNode
{
    // ...
}

Using the eslint-config-next plugin, which is included by default with the ESLint setup when installing Next.js, catches logic that's not allowed for a given component type. For example, it detects and warns about using useState or browser events inside a server component.

For more details about components, check the official Next.js 13 rendering documentation.

Unlike the two changes above, this seems to be a newly added feature that improves the convenience of applying fonts. Using next/font lets you apply fonts much more easily compared to the previous approach.

The only officially supported service so far is Google Font. It also supports linking local font files.

You can use a Google Font by declaring it like below.

TSX

import { Noto_Sans_KRr } from 'next/font/google';

export const notoSans = Noto_Sans_KR({ subsets: [ 'latin' ], weight: [ '100', '300', '400', '500', '700', '900' ] });

export default function Component(): ReactNode
{
	return (
		<div>
            <div className={notoSans.className}>applied via className</div>
            <div style={{ fontFamily: notoSans.style.fontFamily }}>applied via font-family</div>
		</div>
	);
}

The code above is an example applying Noto Sans KR, one of the well-known Korean fonts.

There are two ways to apply it: via className and via font-family.

This is much simpler than fetching a font file locally or from a CDN and applying CSS — just pick the font you want from Google Font, find it via next/font, and apply it.

The biggest drawback of next/font is that it doesn't yet support any service other than Google Font. Fortunately, since it also supports local fonts, you can use fonts that aren't registered on Google Font as well.

TSX

import localFont from 'next/font/local';

const pretendard = localFont({
    src: [
        {
            path: './pretendard-regular.otf',
            weight: 'normal',
            style: 'normal',
        },
        {
            path: './pretendard-bold.otf',
            weight: 'bold',
            style: 'normal',
        },
        {
            path: './pretendard-italic.otf',
            weight: 'normal',
            style: 'italic',
        },
    ],
});

export default function Component(): ReactNode
{
	return (
		<div>
            <div className={pretendard.className}>applied via className</div>
            <div style={{ fontFamily: pretendard.style.fontFamily }}>applied via font-family</div>
		</div>
	);
}

As shown above, you can access the font's path directly to use it.

For more details about fonts, check the official Next.js 13 fonts documentation.

# React# Web# Next.js
ship
blog.itcode.dev

Notes from the π-th Alpaca

7.0.1
Developed by RWB since 2021.057th upgraded at 2026.08