React Router v7のプレリリースが来たよ
React Router v7のプレリリース版が10月4日にリリースされていました。これを書いている現在の最新版は、7.0.0-pre.2です。
React Routerといえば、もともとは文字通りReact向けに開発されたルーティングライブラリなわけです。現在のようにNext.jsが台頭する前は、クライアントサイドルーティングを実現するライブラリとしてメジャーだったのですが、v6になってからは、何方かと言えばRemixというフルスタックフレームワークの一部として開発されていた印象です。
が、今回のアップデートではReact RouterとRemixがついに併合され、React Router v7というフルスタックフレームワークになります。
React Router v7では、React Router v6、Remix v2からの破壊的変更は最小限に留められています。また、プリレンダリングやRSCといった新しい機能が追加されます。ただRSCに関しては、まだ開発途中のようです(ドキュメントがない)。プリレンダリングに関しても、まだバグが多い印象です。
インストール
テンプレートがあるので、これを使うと簡単に使い始められます。my-appは任意のディレクトリ名です。
$ npx degit remix-run/react-router/templates/basic#dev my-appお好みのパッケージマネージャでinstallします。
devコマンドで、開発サーバーが立ち上がります。
$ cd my-app
$ pnpm install
$ pnpm run devルーティング
ルーティングについてはRemix v2から少し変更されて、routes.tsという一つのファイルに全てのルートの設定を記述するのがデフォルトになったようです。
index(componentFile): 親パスをcomponentFileで描画する。route(path, componentFile): 親パスにpathを繋げたパスをcomponentFileで描画する。layout(componentFile, children):childrenの各ルートを、componentFileの<Outlet />のもとで描画する。パスは変化しない。prefix(prefixPath, routes):routesの各ルートのパスの先頭にprefixPathを繋げる。
公式ドキュメントから持ってきた例です。
import { type RouteConfig, route, index, layout, prefix } from "@react-router/dev/routes";
export const routes: RouteConfig = [
index("./home.tsx"),
route("about", "./about.tsx"),
layout("./auth/layout.tsx", [
route("login", "./auth/login.tsx"),
route("register", "./auth/register.tsx"),
]),
...prefix("concerts", [
index("./concerts/home.tsx"),
route(":city", "./concerts/city.tsx"),
route("trending", "./concerts/trending.tsx"),
]),
];初見だと面食らいますが、これは次のような意味です。
URL
/へのリクエストは、コンポーネント./home.tsxによって描画される/aboutは、./about.tsxによって描画される/loginは、./auth/login.tsxによって描画される。ただし、この描画結果は./auth/layout.tsxの<Outlet />に埋め込まれる/registerは、./auth/register.tsxによって描画され、/loginと同様のレイアウトがなされる/concertsは、./concerts/home.tsxによって描画される/concerts/:cityは、./concerts/city.tsxによって描画される/concerts/trendingは、./concerts/trending.tsxによって描画される
加えて、得られた描画結果は暗黙的にapp/root.tsxの<Outlet />に埋め込まれます。このため、app/root.tsxはルートルート(Root Route)と呼ばれます(ややこしい)。
Remix v2時代のファイルベースのルーティングは@react-router/fs-routesによって引き続きサポートされています。
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
export const routes: RouteConfig = flatRoutes();レンダリング方式
React Router v7では、次の3つのレンダリング方式が使えます。
Client Side Rendering
Server Side Rendering
Static Pre-rendering
レンダリング方式は、vite.config.tsで設定することができます。デフォルトではSSRになっているようです。
Client Side Rendering
reactRouter({
ssr: false
})Server Side Rendering
reactRouter({
ssr: true
})Static Pre-rendering
プリレンダリングはprerenderプロパティを指定するのですが、指定できるものがいくつかあります。
true静的なルートをすべてプリレンダする。パスに
:paramなどのパラメータがあるものはプリレンダされない
reactRouter({ prerender: true })配列
指定したルートのみをプリレンダする
reactRouter({ prerender: ['/', '/about'] })関数
プリレンダすべきパスを返す関数を指定する
引数の
getStaticPathsから、静的な全てのルートを配列として取得できる静的サイトジェネレータ的な使い方ができる
reactRouter({ async prerender({ getStaticPaths }) { let posts = await getPosts(); let staticPaths = getStaticPaths(); return staticPaths.concat( posts.map((post) => post.href) ); }, })
型定義の自動生成
React Router v7では、パスパラメータに安全にアクセスできるよう、ルートの型定義を自動生成することができるようになっています。TanStack Routerなどに触発された機能かと思います。
例えば、ファイルベースのルーティングをしていてapp/routes/blog.$pageID.tsxというルートがあったとします。この時、pageIDというパラメータが存在しますが、TypeScript上からparams['pageID']がstringであるという型情報を得ることができれば、params['pageID']!のような怪しい操作を回避することができ、安全になります。
型ファイルを生成するには、react-router typegenを使います。
$ pnpm react-router typegenこれにより、次のような型定義が自動生成されます。
// React Router generated types for route:
// routes/blog.$pageID.tsx
import * as T from "react-router/types"
export type Params = {
pageID: string
}
type Route = typeof import("./blog.$pageID")
export type LoaderData = T.CreateLoaderData<Route>
export type ActionData = T.CreateActionData<Route>
export type LoaderArgs = T.CreateServerLoaderArgs<Params>
export type ClientLoaderArgs = T.CreateClientLoaderArgs<Params, Route>
export type ActionArgs = T.CreateServerActionArgs<Params>
export type ClientActionArgs = T.CreateClientActionArgs<Params, Route>
export type HydrateFallbackProps = T.CreateHydrateFallbackProps<Params>
export type ComponentProps = T.CreateComponentProps<Params, LoaderData, ActionData>
export type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Params, LoaderData, ActionData>この型定義をインポートすることで、次のように型安全なルートコンポーネントを書くことができるようになります。パスパラメータだけでなく、loaderやactionの返り値についても正しい型を得ることができます。
import type * as Route from "./+types.blog.$pageID";
import { useLoaderData, useParams } from "react-router";
export const loader = async ({ }: Route.LoaderArgs) => {
return 'foo';
}
export default function BlogPage() {
const params = useParams() as Route.Params;
const data = useLoaderData() as Route.LoaderData;
return <>
<div>First letter of pageID: {params.pageID[0]}</div>
<div>First letter of loader data: {data[0]}</div>
</>;
}既知のバグ
まだReact Router v7はプレリリースの段階なので、結構な頻度でバグを踏みます。
loaderが非ASCII文字を含むデータを返す場合、プリレンダリングが失敗する
[Bug]: (v7) Cannot prerender resource routes · Issue #12196 · remix-run/react-router · GitHub
リソースルートをプリレンダすると失敗する
静的サイトジェネレータとして使う場合、この二つが結構厄介なので、パッチを(多少強引に)作りました。ご自由にお使いください。patch-packageなどで使えます。