···11-import { isRouteErrorResponse, Link, useRouteError } from "@remix-run/react";
21import { useEffect } from "react";
32import { useTranslation } from "react-i18next";
33+import { isRouteErrorResponse, Link, useRouteError } from "react-router";
4455import { Card } from "~/components/card";
66import { Main, RootLayout } from "~/components/layout";
+1-1
app/components/layout.tsx
···11import { LanguageIcon } from "@heroicons/react/24/outline";
22-import { Form, Link } from "@remix-run/react";
32import { type ReactNode, useRef } from "react";
43import GitHubButton from "react-github-btn";
54import { useTranslation } from "react-i18next";
55+import { Form, Link } from "react-router";
6677import { cn } from "~/utils/cn";
88
+2-2
app/components/logout-button.tsx
···11-import { Form, useSubmit } from "@remix-run/react";
21import { useTranslation } from "react-i18next";
22+import { Form, useSubmit } from "react-router";
3344import { Button } from "./button";
55···1111 event.preventDefault();
1212 const ok = confirm(t("logout-button.confirm-message"));
1313 if (ok) {
1414- submit(event.currentTarget);
1414+ void submit(event.currentTarget);
1515 }
1616 void umami.track("handle-logout", {
1717 action: ok ? "confirm" : "cancel",
+2-2
app/entry.client.tsx
···11-import { RemixBrowser } from "@remix-run/react";
21import i18next from "i18next";
32import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector";
43import { startTransition, StrictMode } from "react";
54import { hydrateRoot } from "react-dom/client";
65import { I18nextProvider, initReactI18next } from "react-i18next";
66+import { HydratedRouter } from "react-router/dom";
77import { getInitialNamespaces } from "remix-i18next/client";
8899import { i18nConfig } from "./i18n/config";
···2323 document,
2424 <I18nextProvider i18n={i18next}>
2525 <StrictMode>
2626- <RemixBrowser />
2626+ <HydratedRouter />
2727 </StrictMode>
2828 </I18nextProvider>,
2929 );
+18-18
app/entry.server.tsx
···7788import { PassThrough } from "node:stream";
991010+import { createReadableStreamFromReadable } from "@react-router/node";
1111+import { createInstance } from "i18next";
1212+import { isbot } from "isbot";
1313+import { renderToPipeableStream } from "react-dom/server";
1414+import { I18nextProvider, initReactI18next } from "react-i18next";
1015import type {
1116 ActionFunctionArgs,
1217 AppLoadContext,
1318 EntryContext,
1419 LoaderFunctionArgs,
1515-} from "@remix-run/node";
1616-import { createReadableStreamFromReadable } from "@remix-run/node";
1717-import { isRouteErrorResponse, RemixServer } from "@remix-run/react";
1818-import { createInstance } from "i18next";
1919-import { isbot } from "isbot";
2020-import { renderToPipeableStream } from "react-dom/server";
2121-import { I18nextProvider, initReactI18next } from "react-i18next";
2020+} from "react-router";
2121+import { isRouteErrorResponse, ServerRouter } from "react-router";
22222323import { i18nConfig } from "./i18n/config";
2424import { i18nServer } from "./i18n/i18n";
···2929 request: Request,
3030 responseStatusCode: number,
3131 responseHeaders: Headers,
3232- remixContext: EntryContext,
3232+ reactRouterContext: EntryContext,
3333 // This is ignored so we can keep it in the template for visibility. Feel
3434 // free to delete this parameter in your app if you're not using it!
3535···4040 request,
4141 responseStatusCode,
4242 responseHeaders,
4343- remixContext,
4343+ reactRouterContext,
4444 )
4545 : handleBrowserRequest(
4646 request,
4747 responseStatusCode,
4848 responseHeaders,
4949- remixContext,
4949+ reactRouterContext,
5050 );
5151}
5252···5454 request: Request,
5555 responseStatusCode: number,
5656 responseHeaders: Headers,
5757- remixContext: EntryContext,
5757+ reactRouterContext: EntryContext,
5858) {
5959 const instance = createInstance();
6060 const lng = await i18nServer.getLocale(request);
6161- const ns = i18nServer.getRouteNamespaces(remixContext);
6161+ const ns = i18nServer.getRouteNamespaces(reactRouterContext);
6262 await instance.use(initReactI18next).init({
6363 ...i18nConfig,
6464 lng,
···6868 let shellRendered = false;
6969 const { pipe, abort } = renderToPipeableStream(
7070 <I18nextProvider i18n={instance}>
7171- <RemixServer
7272- context={remixContext}
7171+ <ServerRouter
7272+ context={reactRouterContext}
7373 url={request.url}
7474 abortDelay={ABORT_DELAY}
7575 />
···115115 request: Request,
116116 responseStatusCode: number,
117117 responseHeaders: Headers,
118118- remixContext: EntryContext,
118118+ reactRouterContext: EntryContext,
119119) {
120120 const instance = createInstance();
121121 const lng = await i18nServer.getLocale(request);
122122- const ns = i18nServer.getRouteNamespaces(remixContext);
122122+ const ns = i18nServer.getRouteNamespaces(reactRouterContext);
123123 await instance.use(initReactI18next).init({
124124 ...i18nConfig,
125125 lng,
···129129 let shellRendered = false;
130130 const { pipe, abort } = renderToPipeableStream(
131131 <I18nextProvider i18n={instance}>
132132- <RemixServer
133133- context={remixContext}
132132+ <ServerRouter
133133+ context={reactRouterContext}
134134 url={request.url}
135135 abortDelay={ABORT_DELAY}
136136 />
+1-1
app/features/board/board-viewer.tsx
···11import { PencilSquareIcon } from "@heroicons/react/24/outline";
22-import { Form, useNavigation } from "@remix-run/react";
32import { useState } from "react";
43import { useTranslation } from "react-i18next";
44+import { Form, useNavigation } from "react-router";
5566import { Button } from "~/components/button";
77import type { ValidBoard } from "~/models/board";
+1-1
app/features/board/card/profile-card.tsx
···11import { PencilSquareIcon, ShareIcon } from "@heroicons/react/24/outline";
22import { UserIcon } from "@heroicons/react/24/solid";
33import type { User } from "@prisma/client";
44-import { Link } from "@remix-run/react";
54import { useState } from "react";
65import { useTranslation } from "react-i18next";
66+import { Link } from "react-router";
7788import { Button } from "~/components/button";
99import { Card } from "~/components/card";
+1-1
app/features/board/form/card-form.tsx
···55} from "@conform-to/react";
66import Picker from "@emoji-mart/react";
77import { XMarkIcon } from "@heroicons/react/24/outline";
88-import { Form } from "@remix-run/react";
98import { useState } from "react";
109import { useTranslation } from "react-i18next";
1010+import { Form } from "react-router";
11111212import { Button } from "~/components/button";
1313import { Input } from "~/components/input";
+1-1
app/features/board/share-modal.tsx
···22 ClipboardDocumentCheckIcon,
33 ClipboardIcon,
44} from "@heroicons/react/24/outline";
55-import { useSearchParams } from "@remix-run/react";
65import { useEffect, useRef, useState } from "react";
76import { useTranslation } from "react-i18next";
77+import { useSearchParams } from "react-router";
8899import { Button } from "~/components/button";
1010import { BlueskyIcon } from "~/components/icons/bluesky";
+1-1
app/features/login/login-form.tsx
···11import { getFormProps, getInputProps, useForm } from "@conform-to/react";
22import { getZodConstraint, parseWithZod } from "@conform-to/zod";
33import { AtSymbolIcon } from "@heroicons/react/24/outline";
44-import { Form, useNavigation } from "@remix-run/react";
54import { useTranslation } from "react-i18next";
55+import { Form, useNavigation } from "react-router";
66import { z } from "zod";
7788import { Button } from "~/components/button";
+1-1
app/features/toast/route.tsx
···11-import { useActionData } from "@remix-run/react";
21import { useEffect } from "react";
22+import { useActionData } from "react-router";
3344import { useToast } from "~/atoms/toast/hooks";
55
+1-1
app/i18n/i18n.ts
···11-import { createCookie } from "@remix-run/node";
11+import { createCookie } from "react-router";
22import { RemixI18Next } from "remix-i18next/server";
3344import { i18nConfig } from "./config";
+4-4
app/root.tsx
···11import "./tailwind.css";
2233-import type { LoaderFunctionArgs } from "@remix-run/node";
33+import type { LoaderFunctionArgs } from "react-router";
44import {
55- json,
55+ data,
66 Links,
77 Meta,
88 Outlet,
···1010 ScrollRestoration,
1111 useLoaderData,
1212 useRouteLoaderData,
1313-} from "@remix-run/react";
1313+} from "react-router";
1414import { useChangeLanguage } from "remix-i18next/react";
15151616import { Toaster } from "./features/toast/toaster";
···24242525export async function loader({ request }: LoaderFunctionArgs) {
2626 const locale = await i18nServer.getLocale(request);
2727- return json(
2727+ return data(
2828 {
2929 locale,
3030 umami: {
+4
app/routes.ts
···11+import { type RouteConfig } from "@react-router/dev/routes";
22+import { flatRoutes } from "@react-router/fs-routes";
33+44+export default flatRoutes() satisfies RouteConfig;
+3-3
app/routes/$handle.og.tsx
···11import type { User } from "@prisma/client";
22-import type { LoaderFunctionArgs } from "@remix-run/node";
32import { Resvg } from "@resvg/resvg-js";
43import fs from "fs";
54import { LRUCache } from "lru-cache";
···7687import { userService } from "~/server/service/userService";
98import { required } from "~/utils/required";
99+1010+import type { Route } from "./+types/$handle.og";
10111112const cache = new LRUCache<string, Buffer>({
1213 max: 100,
···133134 return image;
134135};
135136136136-export async function loader({ params }: LoaderFunctionArgs) {
137137+export async function loader({ params }: Route.LoaderArgs) {
137138 const user = await userService.findOrFetchUser({
138139 handleOrDid: required(params.handle),
139140 });
140141 if (!user) {
141141- // eslint-disable-next-line @typescript-eslint/only-throw-error
142142 throw new Response(null, { status: 404 });
143143 }
144144 const image = cache.get(user.did) ?? (await createImage(user));
+6-8
app/routes/$handle.tsx
···11-import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
22-import { useLoaderData } from "@remix-run/react";
33-41import { Footer, Main } from "~/components/layout";
52import { BoardViewer } from "~/features/board/board-viewer";
63import { ShareModal } from "~/features/board/share-modal";
···129import { createMeta } from "~/utils/meta";
1310import { required } from "~/utils/required";
14111212+import type { Route } from "./+types/$handle";
1313+1514const notFound = () => {
1616- // eslint-disable-next-line @typescript-eslint/only-throw-error
1715 throw new Response("Not Found", { status: 404 });
1816};
19172020-export async function loader({ request, params }: LoaderFunctionArgs) {
1818+export async function loader({ request, params }: Route.LoaderArgs) {
2119 const maybeHandle = params.handle;
2220 if (!maybeHandle || !maybeHandle.includes(".")) {
2321 return notFound();
···4846 };
4947}
50485151-export const meta: MetaFunction<typeof loader> = ({ data }) => {
4949+export const meta = ({ data }: Route.MetaArgs) => {
5250 const { title, url } = required(data);
5351 return createMeta({
5452 title,
···5755 });
5856};
59576060-export default function Index() {
6161- const { user, board, url, isMine } = useLoaderData<typeof loader>();
5858+export default function Index({ loaderData }: Route.ComponentProps) {
5959+ const { user, board, url, isMine } = loaderData;
6260 return (
6361 <>
6462 <Main>
+7-6
app/routes/_index.tsx
···33 AtSymbolIcon,
44 PencilSquareIcon,
55} from "@heroicons/react/24/outline";
66-import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
77-import { Link, useLoaderData } from "@remix-run/react";
86import { useTranslation } from "react-i18next";
77+import { Link } from "react-router";
98109import { Main, RootLayout } from "~/components/layout";
1110import { LogoutButton } from "~/components/logout-button";
···1615import { createMeta } from "~/utils/meta";
1716import { required } from "~/utils/required";
18171919-export const loader = async ({ request }: LoaderFunctionArgs) => {
1818+import type { Route } from "./+types/_index";
1919+2020+export const loader = async ({ request }: Route.LoaderArgs) => {
2021 const userDid = await getSessionUserDid(request);
2122 const t = await i18nServer.getFixedT(request);
2223 return {
···2728 };
2829};
29303030-export const meta: MetaFunction<typeof loader> = ({ data }) => {
3131+export const meta = ({ data }: Route.MetaArgs) => {
3132 const { title, description, url } = required(data);
3233 return createMeta({ title, description, url });
3334};
34353535-export default function Index() {
3636- const { isLogin } = useLoaderData<typeof loader>();
3636+export default function Index({ loaderData }: Route.ComponentProps) {
3737+ const { isLogin } = loaderData;
3738 const { t, i18n } = useTranslation();
3839 return (
3940 <RootLayout>
+4-3
app/routes/board.$handle.tsx
···11-import type { LoaderFunctionArgs } from "@remix-run/node";
22-import { redirect } from "@remix-run/node";
11+import { redirect } from "react-router";
22+33+import type { Route } from "./+types/board.$handle";
3444-export function loader({ params }: LoaderFunctionArgs) {
55+export function loader({ params }: Route.LoaderArgs) {
56 return redirect(`/${params.handle}`);
67}
+1-3
app/routes/client-metadata[.json].tsx
···11-import { json } from "@remix-run/node";
22-31import { createOAuthClient } from "~/server/oauth/client";
4253export async function loader() {
64 const oauthClient = await createOAuthClient();
77- return json(oauthClient.clientMetadata);
55+ return Response.json(oauthClient.clientMetadata);
86}
+8-12
app/routes/edit.tsx
···11-import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
22-import {
33- redirect,
44- useBeforeUnload,
55- useBlocker,
66- useLoaderData,
77-} from "@remix-run/react";
81import { useEffect } from "react";
92import { useTranslation } from "react-i18next";
33+import { redirect, useBeforeUnload, useBlocker } from "react-router";
104115import { Main } from "~/components/layout";
126import { BoardViewer } from "~/features/board/board-viewer";
···1711import { boardService } from "~/server/service/boardService";
1812import { env } from "~/utils/env";
1913import { createLogger } from "~/utils/logger";
1414+1515+import type { Route } from "./+types/edit";
20162117const logger = createLogger("edit");
22182323-export async function action({ request }: ActionFunctionArgs) {
1919+export async function action({ request }: Route.ActionArgs) {
2420 const t = await i18nServer.getFixedT(request);
2521 const [user, agent] = await Promise.all([
2622 getSessionUser(request),
···5046 return redirect(`/${user.handle}?success`);
5147}
52485353-export async function loader({ request }: LoaderFunctionArgs) {
4949+export async function loader({ request }: Route.LoaderArgs) {
5450 const user = await getSessionUser(request);
5551 if (!user) {
5656- return redirect("/login");
5252+ throw redirect("/login");
5753 }
5854 const board = await boardService.findOrFetchBoard(user.did);
5955 return { user, board, url: `${env.PUBLIC_URL}/${user.handle}` };
6056}
61576262-export default function Index() {
6363- const { user, board, url } = useLoaderData<typeof loader>();
5858+export default function Index({ loaderData }: Route.ComponentProps) {
5959+ const { user, board, url } = loaderData;
6460 const { t } = useTranslation();
65616662 // 更新ボタンを押したりしたときに確認ダイアログを出す
···11-import { json } from "@remix-run/node";
22-31import { createOAuthClient } from "~/server/oauth/client";
4253export async function loader() {
64 const oauthClient = await createOAuthClient();
77- return json(oauthClient.jwks);
55+ return Response.json(oauthClient.jwks);
86}
+4-3
app/routes/login.tsx
···11import { OAuthResolverError } from "@atproto/oauth-client-node";
22-import type { ActionFunctionArgs } from "@remix-run/node";
33-import { redirect } from "@remix-run/node";
22+import { redirect } from "react-router";
4354import { Main, RootLayout } from "~/components/layout";
65import { LoginForm } from "~/features/login/login-form";
···98import { createOAuthClient } from "~/server/oauth/client";
109import { createLogger } from "~/utils/logger";
11101111+import type { Route } from "./+types/login";
1212+1213const logger = createLogger("login");
13141414-export async function action({ request }: ActionFunctionArgs) {
1515+export async function action({ request }: Route.ActionArgs) {
1516 const t = await i18nServer.getFixedT(request);
1617 const form = await request.formData();
1718 const handle = form.get("identifier");
+4-3
app/routes/logout.tsx
···11-import type { ActionFunctionArgs } from "@remix-run/node";
22-import { redirect } from "@remix-run/node";
11+import { redirect } from "react-router";
3243import { destroySession, getSession } from "~/server/oauth/session";
5466-export const action = async ({ request }: ActionFunctionArgs) => {
55+import type { Route } from "./+types/logout";
66+77+export const action = async ({ request }: Route.ActionArgs) => {
78 const session = await getSession(request);
89 return redirect("/", {
910 headers: {
+4-2
app/routes/oauth.callback.tsx
···11-import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
11+import { redirect } from "react-router";
2233import { createOAuthClient } from "~/server/oauth/client";
44import { commitSession, getSession } from "~/server/oauth/session";
55import { createLogger } from "~/utils/logger";
6677+import type { Route } from "./+types/oauth.callback";
88+79const logger = createLogger("oauth.callback");
81099-export async function loader({ request }: LoaderFunctionArgs) {
1111+export async function loader({ request }: Route.LoaderArgs) {
1012 const remixSession = await getSession(request);
1113 try {
1214 const oauthClient = await createOAuthClient();
+3-3
app/routes/sample.tsx
···11-import { useLoaderData } from "@remix-run/react";
21import { useTranslation } from "react-i18next";
3243import { Footer, Main } from "~/components/layout";
54import { BoardViewer } from "~/features/board/board-viewer";
65import { env } from "~/utils/env";
66+77+import type { Route } from "./+types/sample";
7889export function loader() {
910 return {
···1112 };
1213}
13141414-export default function Index() {
1515- const { url } = useLoaderData<typeof loader>();
1515+export default function Index({ loaderData: { url } }: Route.ComponentProps) {
1616 const { t } = useTranslation();
1717 return (
1818 <>
+3-3
app/server.ts
···11-import { createRequestHandler } from "@remix-run/express";
22-import type { ServerBuild } from "@remix-run/node";
11+import { createRequestHandler } from "@react-router/express";
32import express from "express";
43import morgan from "morgan";
44+import type { ServerBuild } from "react-router";
5566import { jetstream } from "./server/jetstream/subscription.js";
77import { env } from "./utils/env.js";
···6464const build = viteDevServer
6565 ? () =>
6666 viteDevServer.ssrLoadModule(
6767- "virtual:remix/server-build",
6767+ "virtual:react-router/server-build",
6868 ) as Promise<ServerBuild>
6969 : // eslint-disable-next-line
7070 // @ts-ignore: ビルド成果物はあったりなかったりするのでts-expect-errorを使わない
+1-1
app/server/oauth/session.ts
···11-import { createCookieSessionStorage } from "@remix-run/node"; // or cloudflare/deno
11+import { createCookieSessionStorage } from "react-router"; // or cloudflare/deno
2233import { LinkatAgent } from "~/libs/agent";
44import { userService } from "~/server/service/userService";