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 は Client Components と Server Components を区別します。Server Components は特殊な 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 です。

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