Open14

Next.js 13 を試す

izuchyizuchy

Server and Client Components の概要

https://beta.nextjs.org/docs/rendering/server-and-client-components#server-components

なぜ Server Components なのか

Server Components (RFC) allow developers to better leverage server infrastructure. For example, large dependencies that previously would impact the JavaScript bundle size on the client can instead remain entirely on the server, leading to improved performance. However, the app directory does still require JavaScript.

Server Componennts を利用することで、サーバーインフラをより活用できる(クライアント側のリソースを使わなくてもよかったり JavaScript のバンドルサイズが大きくならない)

When a route is loaded, the Next.js and React runtime will be loaded, which is cacheable and predictable in size. This runtime does not increase in size as your application grows. Further, the runtime is asynchronously loaded, enabling your HTML from the server to be progressively enhanced on the client.

Next.js と React のランタイムはルートの読み込み時に非同期で読み込まる。
また、アプリケーション本体が大きくなってもサイズは大きくならない。

Additional JavaScript is only added as client-side interactivity is needed in your application through the use of Client Components.

追加の JavaScript コードは Client Components を使ったときに必要に応じて追加される。

Server Components とは

https://beta.nextjs.org/docs/rendering/server-and-client-components#server-components

All components inside the app directory are React Server Components by default, including special files and colocated components. This allows you to automatically adopt Server Components with no extra work, and achieve great performance out of the box.

app ディレクトリ内のコンポーネントはデフォルトですべて、React Server Components になる。

Client Components とは

Client Components are rendered on the client. With Next.js, Client Components can also be pre-rendered on the server and hydrated on the client.

クライアントコンポーネントはクライアント上でレンダリングされる。
クライアント上でハイドレーション(サーバー上で生成した HTML をクライアント側で引き継いでレンダリングするといったこと)もできる。

izuchyizuchy

Server Components と Client Components の使い分け

https://beta.nextjs.org/docs/rendering/server-and-client-components#when-to-use-server-vs-client-components

To simplify the decision between Server and Client Components, we recommend using Server Components (default in the app directory) until you have a need to use a Client Component.
This table summarizes the different use cases for Server and Client Components:

Client Component が必要になるまで、Server Component(appディレクトリ内のデフォルト)を使用することをお勧めしている。

Client Component でないとできないこと

  • onClick や onChange などのイベントリスナーの追加したい場合
  • useXXX といった状態やライフサイクルを使う場合(カスタムフック含む)
  • ブラウザにしかない API を使う場合
  • React の クラスコンポーネントを使う場合
izuchyizuchy

サードパーティのパッケージの扱い方

https://beta.nextjs.org/docs/rendering/server-and-client-components#third-party-packages

サーバーサイドのコンポーネント内で、サードパーティのライブラリを使う場合、エラーになる。

e.g.
app/page.js

import { AcmeCarousel } from 'acme-carousel';
export default function Page() {
  return (
    <div>
      <p>View pictures</p>
      {/* 🔴 Error: `useState` can not be used within Server Components */}
      <AcmeCarousel />
    </div>
  );
}

app/carousel.js

'use client';
import { AcmeCarousel } from 'acme-carousel';
export default AcmeCarousel;

app/page.js

import Carousel from './carousel';
export default function Page() {
  return (
    <div>
      <p>View pictures</p>
      {/* 🟢 Works, since Carousel is a Client Component */}
      <Carousel />
    </div>
  );
}

のように、app/carousel.js を作り、'use client'; でラップしてあげたコンポーネントを使うようにすれば良い。

izuchyizuchy

データフェッチ

Although it's possible to fetch data in Client Components, we recommend fetching data in Server Components unless you have a specific reason for fetching data on the client. Moving data fetching to the server leads to better performance and user experience.
Learn more about data fetching.

特別な理由がなければ、Server Components でデータを取得することをお勧めしている。

izuchyizuchy

サーバー専用コードをクライアントコンポーネントから排除する

サーバーサイドの JavaScript のモジュールと思って作ったものが、知らないうちにクライアントサイドのコンポーネントに紛れ込むということも起こる可能性があります。

そんなことを防止できるのが、server-only という npm package です。

https://beta.nextjs.org/docs/rendering/server-and-client-components#keeping-server-only-code-out-of-client-components-poisoning

npm install server-only

import "server-only";

export async function getData() {
  let resp = await fetch("https://external-service.com/data", {
    headers: {
      authorization: process.env.API_KEY,
    },
  });

  return resp.json();
}
izuchyizuchy

Client Component はコンポーネントツリーの葉の部分に移動させよう

https://beta.nextjs.org/docs/rendering/server-and-client-components#moving-client-components-to-the-leaves

To improve the performance of your application, we recommend moving Client Components to the leaves of your component tree where possible.

可能な限り、Client Component をコンポーネントツリーの葉の部分に移動させることをお勧めしている。

For example, you may have a Layout that has static elements (e.g. logo, links, etc) and an interactive search bar that uses state.

たとえば、レイアウトに静的なロゴやリンクと、状態を有する検索バーがあるとする。

Instead of making the whole layout a Client Component, move the interactive logic to a Client Component (e.g. <SearchBar />) and keep your layout as a Server Component. This means you don't have to send all the component Javascript of the layout to the client.

静的な要素は、Server Component 側とし、状態を有する検索バーは Client Component とすることで、
最低限のコンポーネントだけクライアント側に送信することができる。

izuchyizuchy

Server Components を Client Components にインポートする

Server and Client Components can be interleaved in the same component tree. Behind the scenes, React will merge the work of both environments.

同じコンポーネントツリーに、Server Components と Client Components を混ぜることができる。
React がよしなにマージしてくれる。

However, in React, there's a restriction around importing Server Components inside Client Components because Server Components might have server-only code (e.g. database or filesystem utilities).

インポートする際に制約が存在している。

For example, importing a Server Component in a Client Component will not work:

'use client';
// ❌ Client Component に Server Component をインポートすることはできない
import ServerComponent from './ServerComponent';
export default function ClientComponent() {
  return (
    <>
      <ServerComponent />
    </>
  );
}
// ✅ ClientComponent の children や props に指定すると動作する
import ClientComponent from "./ClientComponent";
import ServerComponent from "./ServerComponent";

// Pages are Server Components by default
export default function Page() {
  return (
    <ClientComponent>
      <ServerComponent />
    </ClientComponent>
  );
}

この場合、React はサーバ上で <ServerComponent> をレンダリングして、その結果をクライアントに送信して表示する。<ClientComponent> からすると、すでに <ServerComponent> はレンダリングされていることになる。

izuchyizuchy

app/layout.tsx、app/page.tsx をつくる

app ディレクトリを実際に試してみる。

https://beta.nextjs.org/docs/upgrade-guide#step-2-creating-a-root-layout

layout.tsx は従来の、_app.tsx と _document.tsx の置き換えになる。

app/layout.tsx

export default function RootLayout({
                                     // Layouts must accept a children prop.
                                     // This will be populated with nested layouts or pages
                                     children,
                                   }: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
    <head>
      <title>Next.js</title>
    </head>
    <body>{children}</body>
    </html>
  );
}

app/page.tsx

export default function Page() {
  return <h1>Hello, Next.js!</h1>;
}

app/page.tsx は従来の pages/index.tsx と同じで、ルートにアクセスしたときのコンポーネントの扱いっぽい。

izuchyizuchy

@next/font の概要

https://nextjs.org/docs/basic-features/font-optimization

@next/font includes built-in automatic self-hosting for any font file. This means you can optimally load web fonts with zero layout shift, thanks to the underlying CSS size-adjust property used.

This new font system also allows you to conveniently use all Google Fonts with performance and privacy in mind. CSS and font files are downloaded at build time and self-hosted with the rest of your static assets. No requests are sent to Google by the browser.

  • 自動的なセルフホスティング機能
  • CSS と フォントはビルド時にダウンロードされ、セルフホスティングされる
  • Google にリクエストは送信されない

インストール

npm install @next/font

Google Fonts

https://nextjs.org/docs/basic-features/font-optimization#google-fonts

以下のように定義することで自動的に Google Fonts をセルフホスティングすることが可能である。

// app/layout.tsx
import { Inter } from '@next/font/google'

// If loading a variable font, you don't need to specify the font weight
const inter = Inter()

export default function RootLayout({
  children,
}: {
  children: React.ReactNode,
}) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  )
}

ローカルフォント

next/font/local をインポートしてフォントファイルのパスを指定する。

/// app/layout.tsx
import localFont from '@next/font/local'

// Font files can be colocated inside of `app`
const myFont = localFont({ src: './my-font.woff2' })

export default function RootLayout({
  children,
}: {
  children: React.ReactNode,
}) {
  return (
    <html lang="en" className={myFont.className}>
      <body>{children}</body>
    </html>
  )
}
izuchyizuchy

ローディングUI

https://beta.nextjs.org/docs/routing/loading-ui

Next.js 13 introduced a new file convention, loading.js, to help you create meaningful Loading UI with React Suspense. With this convention, you can show an instant loading state from the server while the content of a route segment loads, the new content is automatically swapped in once rendering is complete.

Next.js 13 では、React Suspense で ローディングの UI を作成できる。
loading.js というファイルを用意すれば、読み込み中の UI を定義できる

app/feeds/loading.tsx

export default function Loading() {
  // You can add any UI inside Loading, including a Skeleton.
  return <div>loading</div>;
}

app/feeds/page.tsx

export default async function Page() {
  const wait = () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(null);
      }, 2000)
    })
  }
  await wait();
  return <h1>Hello, Feeds</h1>;
}
izuchyizuchy

app ディレクトリの各ファイルについて

https://beta.nextjs.org/docs/routing/fundamentals#special-files

page.tsx: A file used to define the unique UI of a route. Pages represent the leaf of the route and are needed for the path to be accessible.

ルートにアクセスされたときに使うファイル

layout.tsx: A file used to define UI that is shared across multiple pages. A layout accepts another layout or a page as its child. You can nest layouts to create nested routes.

複数ページで共有されるレイアウトファイル。他のレイアウトやページを children として受け取る。

loading.tsx: An optional file used to create loading UI for a specific part of an app. It automatically wraps a page or child layout in a React Suspense Boundary, showing your loading component immediately on the first load and when navigating between sibling routes.

アプリの特定部分のローディング UI を作成するためのファイル。

error.tsx: An optional file used to isolate errors to specific parts of an app, show specific error information, and functionality to attempt to recover from the error. It automatically wraps a page or child layout in a React Error Boundary. Showing your error component whenever an error in a subtree is caught.

エラーが生じた場合に表示するためのファイル。

template.tsx: An optional file, similar to layouts, but on navigation, a new instance of the component is mounted and the state is not shared. You can use templates for cases where you require this behavior, such as enter/exit animations.

head.tsx: An optional file used to define the contents of the <head> tag for a given route.
<head> タグの内容を定義するためのファイル。