suemor

suemor

前端萌新
telegram
github
twitter

NextJS 13.4 アプリケーションルーターの初体験

NextJS は最近、13.4 バージョンをリリースし、App Router を「安定」させました。同時に、公式の CLI も App Router をデフォルトで推奨するものに変更しましたが、App Router では React Server Components(以下、rsc)の概念が導入され、多くの API が変更されました。これにより、学習の難易度が高くなりました。

サーバーコンポーネント#

App Router では、NextJS はクライアントコンポーネントとサーバーコンポーネントを区別します。サーバーコンポーネントは特殊な React コンポーネントであり、ブラウザで実行されるのではなく、サーバー側でのみ実行されます。また、状態を持たないため、クライアント側でのみ存在する特性(つまり、useState、useEffect など)を使用することはできません。そのため、一般的にはデータの取得やコンポーネントのレンダリング(たとえば、マークダウンをレンダリングする場合、対応する JavaScript の依存関係はクライアント側にのみ存在します)に使用することができ、それによりクライアントのサイズを減らす効果があります。

同時に、App Router のファイルはデフォルトでサーバーコンポーネントです。クライアントコンポーネントを使用する場合は、use clientを追加する必要がありますが、実際にはこのコマンドは子コンポーネントに影響を与えます。つまり、親コンポーネントにuse clientを追加した場合、このファイルのすべての子コンポーネントはこの指示を追加しなくてもクライアントコンポーネントになります。そのため、私たちはレイアウトを適切に計画し、クライアントコンポーネントをレイアウトから分離する必要があります。

以下の<MyComponent />は実際にはクライアントコンポーネントです。

"use client";

import { useState } from "react";
import MyComponent from "./MyComponent";

export default function Home() {
  const [num, setNum] = useState(0);
  return (
    <main>
      <h1>{num}</h1>
      <button onClick={() => setNum(num + 1)}>+1</button>
      <MyComponent />
    </main>
  );
}
import { useEffect } from "react";

const MyComponent = () => {
  useEffect(() => {
    console.log("client component");
  }, []);
  return <div>123</div>;
};

export default MyComponent;

また、現在、多くのサードパーティライブラリはuse clientをサポートしていませんので、次のようなエラーが発生することがあります。

error-image

rsc で正常に使用するためには、いくつかの特殊な処理が必要です。以下は、framer-motion を例に挙げたものです。通常、これはコンポーネントの最上位にラップされます。

"use client"
import { FC, PropsWithChildren } from "react";
import { motion } from "framer-motion";

const MotionLayout: FC<PropsWithChildren> = ({ children }) => {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 0.5 }}
    >
      {children}
    </motion.div>
  );
};

export default MotionLayout;

または、以下のようにカプセル化することもできます。

"use client"

import { motion } from "framer-motion";

export const MotionDiv = motion.div;

データの取得#

データのフェッチは rsc で重要な要素です。以下のように書くことができます。

export default async function Home() {
  const data = await fetch("https://api.github.com/repos/vercel/next.js").then(
    (res) => res.json()
  );
  return <div>{data.id}</div>;
}

ただし、これはデフォルトで SSG です。NextJS はネイティブの fetch をいくつかの変更を加えていますので、SSR にするにはcacheの設定を追加する必要があります。

export default async function Home() {
  const data = await fetch("https://api.github.com/repos/vercel/next.js", {
    cache: "no-store",
  }).then((res) => res.json());
  return <div>{data.id}</div>;

以下は ISR の書き方です。

// ISRは10秒ごとに再取得します
export default async function Home() {
  const data = await fetch("https://api.github.com/repos/vercel/next.js", {
    next: {
      revalidate: 10,
    },
  }).then((res) => res.json());
  return <div>{data.id}</div>;
}

axios などを使用して SSR を行いたい場合は、次のように書くことができます。

import axios from "axios";

export const dynamic = "force-dynamic";

export default async function Home() {
  const { data } = await axios.get(
    "https://api.github.com/repos/vercel/next.js"
  );
  return <div>{data.id}</div>;
}

ルーティング#

App Router のルーティングは、以前のバージョンに比べて強化されています。(folderName)を使用してルーティングをグループ化することができます。括弧内のグループ名は実際のルートにマッピングされませんが、コードの可読性を向上させるだけでなく、レイアウトを共有することもできます。

動的ルーティングに関しては、以前と同様に[folderName]を使用して定義することができますが、props から直接対応する値を取得することもできます。

また、loading.tsxを作成することもできます。これは Suspense をラップしたもので、page.tsxでデータをフェッチする間に、loading.tsxの内容を表示することができます。

同様に、error.tsxもあります。ページのレンダリング中にエラーが発生した場合には、すぐにエラーページを表示することができます。

また、Parallel Routesというものもあります。@folderNameをレイアウトの props にマッピングして、「スロット」として使用することができます。一般的には、ヘッダーやフッターなどのウェブページの上部や下部に適用することができます。

export default function Layout(props: {
  children: React.ReactNode;
  analytics: React.ReactNode;
  team: React.ReactNode;
}) {
  return (
    <>
      {props.children}
      {props.team}
      {props.analytics}
    </>
  );
}

最後に#

App Router には非常に多くの API があります。ここで挙げたのはごく一部ですので、詳細は公式ドキュメントを参照してください。

公式ドキュメント

この記事はMix Spaceから xLog に同期されています。
元のリンクはhttps://suemor.com/posts/programming/approuterです。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。