クライアントサイドナビゲーション
サーバが最初のページをレンダリングした後、クライアントが引き継いで、その後のすべてのナビゲーションを処理します。これは、@lazarv/react-server/navigationモジュールからエクスポートされたクライアントコンポーネントを使用するか、@lazarv/react-server/clientモジュールのuseClientフックによって公開された関数を使用して行うことができます。これらの関数を使用して、新しいページに移動したり、ページをプリフェッチしたり、現在のページを更新したりすることができます。
ナビゲーションと更新には、組み込みのクライアントコンポーネントを使用することをお勧めします。これらのコンポーネントは、変更されている部分のみをレンダリングするReactトランジションを自動的に開始します。これにより、ユーザーがページの読み込みを待つ時間を短縮し、ユーザーエクスペリエンスを向上させます。
useClientフックによって公開される関数はより低レベルであり、Reactトランジションを手動で開始する必要がある場合にのみ使用すべきです。
@lazarv/react-server/navigationモジュールからエクスポートされたLinkクライアントコンポーネントを使用して、新しいページに移動することができます。
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about">About</Link>
</div>
);
}
Linkコンポーネントにprefetch propsを渡すことで、ページのプリフェッチを有効にすることができます。これにより、ユーザーがリンクにホバーした時にページがプリフェッチされます。
import { Link } from "@lazarv/react-server/client";
export default function Home() {
return (
<div>
<Link to="/about" prefetch>
About
</Link>
</div>
);
}
デフォルトでは、プリフェッチされたページは無期限にキャッシュされます。Linkコンポーネントのprefetch propsにタイムアウト値を渡すことで、この動作を変更することができます。prefetch propsは、ページがキャッシュされるミリ秒数を表す数値を受け入れます。タイムアウトが経過すると、ページはキャッシュから削除され、次にユーザーがリンクにホバーすると、ページが再度プリフェッチされます。
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" prefetch={5000}>
About
</Link>
</div>
);
}
プログラムでページをプリフェッチするには、useClientフックが返すprefetch関数を使用できます。
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { prefetch } = useClient();
return (
<div>
<button onMouseOver={() => prefetch("/about")}>About</button>
</div>
);
}
ユーザーがページから離れると、Linkコンポーネントにrollback propsを指定すると、ルーターは最後のページをキャッシュします。これにより、ユーザーはサーバがページを再度レンダリングするのを待つことなく、前のページに戻ることができます。提供された値は、ページがキャッシュされるミリ秒数を表します。タイムアウトが経過すると、ページはキャッシュから削除され、次にユーザーがページに戻ると、ページはサーバによって再度レンダリングされます。
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" rollback={5000}>
About
</Link>
</div>
);
}
プログラムで新しいページに移動するには、useClientフックが返すnavigate関数を使用できます。変更されている部分のみをレンダリングするために、Reactトランジションでnavigateを使用することをお勧めします。
import { startTransition } from "react";
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { navigate } = useClient();
return (
<div>
<button onClick={() => startTransition(async () => navigate("/about"))}>
About
</button>
</div>
);
}
@lazarv/react-server/navigationモジュールからエクスポートされた<Refresh>クライアントコンポーネントを使用するか、@lazarv/react-server/clientモジュールのuseClientフックが返すrefresh関数を使用して、現在のページを更新することができます。
import { Refresh } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Refresh>Click to refresh!</Refresh>
</div>
);
}
プログラムで現在のページを更新するには、useClientフックが返すrefresh関数を使用できます。ここでも、ページの更新にはReactトランジションを開始することをお勧めします。
import { startTransition } from "react";
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { refresh } = useClient();
return (
<div>
<button onClick={() => startTransition(async () => refresh())}>
Refresh
</button>
</div>
);
}
@lazarv/react-server/navigationモジュールからエクスポートされたFormクライアントコンポーネントを使用して、新しいページに移動することができます。このコンポーネントは、フォーム送信時にフォームデータをクエリパラメータとして使用し、現在のルートに移動します。
import { Form } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<Form>
<input name="name" />
<button type="submit">Submit</button>
</Form>
);
}
ReactServerComponentを使用して、サブページナビゲーションに使用できるアウトレットを作成できます。これは、<iframe>要素のモダンなReact Server Componentバージョンです。LinkまたはRefreshコンポーネントを使用するか、navigateまたはreplace関数を使用してプログラムで移動する場合、クライアントサイドルーターはアウトレットのコンテンツを自動的に更新し、サーバサイドレンダリングのアウトレットの結果をレンダリングします。サーバサイドルーティングとuseOutletヘルパー関数を使用して、アウトレットのサーバサイドレンダリングを自分で処理する必要があります。
"use client";
import { Link, ReactServerComponent } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/photos/1" target="modal">My photo</Link>
<ReactServerComponent outlet="modal" />
</div>
);
}
ReactServerComponent内には4種類のLinkまたはRefreshナビゲーションがあります。未指定、target、local、rootです。LinkまたはRefreshコンポーネントでtargetを使用すると、同じ名前のアウトレットのコンテンツのみが更新されます。localを使用すると、現在のページのURLは更新されず、親アウトレットのコンテンツが更新されます。rootを使用すると、ページのコンテンツが更新されます。target属性を指定しない場合は、ページ全体が更新対象となり、ページ内に存在するすべてのアウトレットのコンテンツも含めて更新されます。
"use client";
import { Link, ReactServerComponent } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<ReactServerComponent outlet="modal">
<Link to="/photos/2" local>My photo</Link>
</ReactServerComponent>
</div>
);
}
defer propsを持つReactServerComponentを使用すると、ユーザーがdefer propsを持つReactServerComponentを含むページに移動した時にのみ、アウトレットのコンテンツがレンダリングされます。これは、ユーザーがdefer propsを持つReactServerComponentを含むページに移動するまで、アウトレットのコンテンツのレンダリングを延期したい場合に役立ちます。アウトレットのコンテンツはサーバからフェッチされます。
また、ReactServerComponentにurl propsを指定して、アウトレットのコンテンツのURLを指定することもできます。これは、現在のページとは異なるURLからアウトレットのコンテンツをレンダリングしたい場合に役立ちます。
ReactServerComponentにdeferとurlの両方のpropsを使用することで、iframe要素と同様の動作を実現できます。
useClientフックは、次のプロパティを持つオブジェクトを返します:
navigate(url: string, options: { outlet?: string; push?: boolean; rollback?: number; signal?: AbortSignal; fallback?: React.ReactNode; Component?: React.ReactNode }): 新しいページまたは指定されたアウトレットに移動する関数。rollbackオプションを使用すると、現在のページを指定した時間だけキャッシュすることができます。replace(url: string, options: { outlet?: string; rollback?: number; signal?: AbortSignal; fallback?: React.ReactNode; Component?: React.ReactNode }): 現在のページを新しいページまたは指定されたアウトレットのコンテンツに置き換える関数。rollbackオプションを使用すると、現在のページを指定した時間だけキャッシュすることができます。prefetch(url: string, options: { outlet?: string; ttl?: number; signal?: AbortSignal }): ページまたは指定されたアウトレットのコンテンツをプリフェッチする関数。ttlオプションを使用すると、ページを指定した時間だけキャッシュすることができます。refresh(outlet?: string, options: { signal?: AbortSignal; fallback?: React.ReactNode; Component?: React.ReactNode }): 現在のページまたは指定されたアウトレットのコンテンツを更新する関数。abort(outlet?: string, reason?: unknown): 指定されたアウトレットのナビゲーションを中止する関数。
これらの関数をプログラムでのナビゲーションに使用できます。
import { startTransition } from "react";
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { navigate, replace, prefetch, refresh } = useClient();
return (
<div>
<button onClick={() => startTransition(async () => navigate("/about"))}>
About
</button>
<button onClick={() => startTransition(async () => replace("/about"))}>
Replace
</button>
<button onMouseOver={() => prefetch("/about")}>Prefetch</button>
<button onClick={() => startTransition(async () => refresh())}>
Refresh
</button>
</div>
);
}
useOutletフックは、現在のアウトレットとやり取りするための一連の関数を返します。これらの関数はuseClientフックが返す関数と同じですが、outletオプションを指定する必要はありません。以下の例では、navigate関数は現在のアウトレットにスコープされており、ページ全体ではありません。ただし、現在のアウトレットがルートアウトレットである場合、navigate関数はページ全体に移動します。
"use client";
import { useOutlet } from "@lazarv/react-server/client";
export default function Home() {
const { navigate } = useOutlet();
return (
<div>
<button onClick={() => navigate("/about")}>About</button>
</div>
);
}
LinkとRefreshコンポーネントのfallbackオプションを使用すると、アウトレットがReact Server Componentのレンダリングを開始するまでレンダリングするフォールバックコンポーネントを指定できます。これは、React Server Componentリクエストがサーバに到達するのを待たずに、React Server Componentがレンダリングされている間にローディングインジケーターやスケルトンをすぐにレンダリングしたい場合に役立ちます。
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" target="content" fallback={<div>Loading...</div>}>About</Link>
</div>
);
}
また、navigateまたはreplace関数を使用してプログラムで移動する際に、fallbackオプションを指定することもできます。
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { navigate } = useClient();
return (
<div>
<button onClick={() => navigate("/about", { fallback: <div>Loading...</div> })}>About</button>
</div>
);
}
LinkおよびRefreshコンポーネントのComponentオプションを使用すると、レンダリングするReactコンポーネントを指定できます。これは、サーバにリクエストを送信する代わりに、Reactコンポーネントをアウトレットに直接レンダリングしたい場合に役立ちます。
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" target="content" Component={<div>About</div>}>About</Link>
</div>
);
}
また、navigateまたはreplace関数を使用してプログラムで移動する際に、Componentオプションを指定することもできます。
import { useClient } from "@lazarv/react-server/client";
export default function Home() {
const { navigate } = useClient();
return (
<div>
<button onClick={() => navigate("/about", { Component: <div>About</div> })}>About</button>
</div>
);
}
Link、Refresh、Formコンポーネントのrevalidate propsを使用して、ページまたは指定したアウトレットのコンテンツのクライアントサイドキャッシングを制御できます。revalidate propsは、ターゲットURLのページまたはアウトレットがキャッシュされるミリ秒数を表す数値を受け入れることができます。タイムアウトが経過すると、ページはキャッシュから削除され、次にユーザーがページに移動すると、サーバからコンテンツをフェッチしてReact Server Componentをレンダリングすることで、ページが再度レンダリングされます。
デフォルトでは、revalidate propsは設定されておらず、ページまたはアウトレットはまったくキャッシュされません。そのため、すべてのナビゲーションでサーバからコンテンツをフェッチします。
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" revalidate={5000}>About</Link>
</div>
);
}
revalidate propsにfalseを渡すことで、再検証を無効にすることができます。再検証を無効にすることで、ページまたはアウトレットは無期限にキャッシュされます。
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" revalidate={false}>About</Link>
</div>
);
}
また、revalidate propsに関数を渡すことで、再検証のロジックを完全にカスタマイズすることもできます。この関数は、outlet、ターゲットurl、およびキャッシュされたコンテンツのtimestampを含むコンテキストオブジェクトを受け取ります。この関数はコンテンツを再検証すべきかどうかを示すbool値を返す必要があります。
"use client";
import { Link } from "@lazarv/react-server/navigation";
export default function Home() {
return (
<div>
<Link to="/about" revalidate={async ({ outlet, url, timestamp }) => {
return Math.random() > 0.5;
}}>About</Link>
</div>
);
}