diff --git a/.gitignore b/.gitignore
index 2a7f82dbf..6ba5a4f2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,4 @@ node_modules
# generated files
/app/components/ui/icons
+.react-router/
diff --git a/app/components/error-boundary.tsx b/app/components/error-boundary.tsx
index a03fc45a0..262851bf8 100644
--- a/app/components/error-boundary.tsx
+++ b/app/components/error-boundary.tsx
@@ -1,12 +1,12 @@
+import { captureException } from '@sentry/react'
+import { useEffect, type ReactElement } from 'react'
import {
type ErrorResponse,
isRouteErrorResponse,
useParams,
useRouteError,
-} from '@remix-run/react'
-import { captureRemixErrorBoundaryError } from '@sentry/remix'
-import { type ReactElement } from 'react'
-import { getErrorMessage } from '#app/utils/misc.tsx'
+} from 'react-router'
+import { getErrorMessage } from '#app/utils/misc'
type StatusHandler = (info: {
error: ErrorResponse
@@ -27,13 +27,16 @@ export function GeneralErrorBoundary({
unexpectedErrorHandler?: (error: unknown) => ReactElement | null
}) {
const error = useRouteError()
- captureRemixErrorBoundaryError(error)
const params = useParams()
if (typeof document !== 'undefined') {
console.error(error)
}
+ useEffect(() => {
+ captureException(error)
+ }, [error])
+
return (
{isRouteErrorResponse(error)
diff --git a/app/components/progress-bar.tsx b/app/components/progress-bar.tsx
index 00737aea7..6e7c59150 100644
--- a/app/components/progress-bar.tsx
+++ b/app/components/progress-bar.tsx
@@ -1,4 +1,4 @@
-import { useNavigation } from '@remix-run/react'
+import { useNavigation } from 'react-router'
import { useEffect, useRef, useState } from 'react'
import { useSpinDelay } from 'spin-delay'
import { cn } from '#app/utils/misc.tsx'
diff --git a/app/components/search-bar.tsx b/app/components/search-bar.tsx
index 30c77d391..db7af73f0 100644
--- a/app/components/search-bar.tsx
+++ b/app/components/search-bar.tsx
@@ -1,5 +1,5 @@
-import { Form, useSearchParams, useSubmit } from '@remix-run/react'
import { useId } from 'react'
+import { Form, useSearchParams, useSubmit } from 'react-router'
import { useDebounce, useIsPending } from '#app/utils/misc.tsx'
import { Icon } from './ui/icon.tsx'
import { Input } from './ui/input.tsx'
@@ -23,8 +23,8 @@ export function SearchBar({
formAction: '/users',
})
- const handleFormChange = useDebounce((form: HTMLFormElement) => {
- submit(form)
+ const handleFormChange = useDebounce(async (form: HTMLFormElement) => {
+ await submit(form)
}, 400)
return (
diff --git a/app/entry.client.tsx b/app/entry.client.tsx
index cef8316ea..9b7749f3a 100644
--- a/app/entry.client.tsx
+++ b/app/entry.client.tsx
@@ -1,11 +1,11 @@
-import { RemixBrowser } from '@remix-run/react'
import { startTransition } from 'react'
import { hydrateRoot } from 'react-dom/client'
+import { HydratedRouter } from 'react-router/dom'
if (ENV.MODE === 'production' && ENV.SENTRY_DSN) {
void import('./utils/monitoring.client.tsx').then(({ init }) => init())
}
startTransition(() => {
- hydrateRoot(document,
)
+ hydrateRoot(document,
)
})
diff --git a/app/entry.server.tsx b/app/entry.server.tsx
index ed4e4b815..59adaf914 100644
--- a/app/entry.server.tsx
+++ b/app/entry.server.tsx
@@ -1,21 +1,22 @@
import { PassThrough } from 'node:stream'
+import { createReadableStreamFromReadable } from '@react-router/node'
+
+import * as Sentry from '@sentry/node'
+import chalk from 'chalk'
+import { isbot } from 'isbot'
+import { renderToPipeableStream } from 'react-dom/server'
import {
- createReadableStreamFromReadable,
+ ServerRouter,
type LoaderFunctionArgs,
type ActionFunctionArgs,
type HandleDocumentRequestFunction,
-} from '@remix-run/node'
-import { RemixServer } from '@remix-run/react'
-import * as Sentry from '@sentry/remix'
-import chalk from 'chalk'
-import { isbot } from 'isbot'
-import { renderToPipeableStream } from 'react-dom/server'
+} from 'react-router'
import { getEnv, init } from './utils/env.server.ts'
import { getInstanceInfo } from './utils/litefs.server.ts'
import { NonceProvider } from './utils/nonce-provider.ts'
import { makeTimings } from './utils/timing.server.ts'
-const ABORT_DELAY = 5000
+export const streamTimeout = 5000
init()
global.ENV = getEnv()
@@ -27,7 +28,7 @@ export default async function handleRequest(...args: DocRequestArgs) {
request,
responseStatusCode,
responseHeaders,
- remixContext,
+ reactRouterContext,
loadContext,
] = args
const { currentInstance, primaryInstance } = await getInstanceInfo()
@@ -53,7 +54,11 @@ export default async function handleRequest(...args: DocRequestArgs) {
const { pipe, abort } = renderToPipeableStream(
-
+
,
{
[callbackName]: () => {
@@ -78,7 +83,7 @@ export default async function handleRequest(...args: DocRequestArgs) {
},
)
- setTimeout(abort, ABORT_DELAY)
+ setTimeout(abort, streamTimeout + 5000)
})
}
@@ -103,12 +108,7 @@ export function handleError(
}
if (error instanceof Error) {
console.error(chalk.red(error.stack))
- void Sentry.captureRemixServerException(
- error,
- 'remix.server',
- request,
- true,
- )
+ void Sentry.captureException(error)
} else {
console.error(error)
Sentry.captureException(error)
diff --git a/app/root.tsx b/app/root.tsx
index 9ba6c23e7..5fdd9ff3a 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -1,11 +1,11 @@
+import { wrapUseRoutesV7 } from '@sentry/react'
+import { useRef } from 'react'
import {
- json,
+ data,
type LoaderFunctionArgs,
type HeadersFunction,
type LinksFunction,
type MetaFunction,
-} from '@remix-run/node'
-import {
Form,
Link,
Links,
@@ -16,9 +16,7 @@ import {
useLoaderData,
useMatches,
useSubmit,
-} from '@remix-run/react'
-import { withSentry } from '@sentry/remix'
-import { useRef } from 'react'
+} from 'react-router'
import { HoneypotProvider } from 'remix-utils/honeypot/react'
import appleTouchIconAssetUrl from './assets/favicons/apple-touch-icon.png'
import faviconAssetUrl from './assets/favicons/favicon.svg'
@@ -121,7 +119,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
const { toast, headers: toastHeaders } = await getToast(request)
const honeyProps = honeypot.getInputProps()
- return json(
+ return data(
{
user,
requestInfo: {
@@ -161,7 +159,8 @@ function Document({
children: React.ReactNode
nonce: string
theme?: Theme
- env?: Record
+ env?: Record
+ allowIndexing?: boolean
}) {
const allowIndexing = ENV.ALLOW_INDEXING !== 'false'
return (
@@ -271,7 +270,7 @@ function AppWithProviders() {
)
}
-export default withSentry(AppWithProviders)
+export default wrapUseRoutesV7(AppWithProviders)
function UserDropdown() {
const user = useUser()
@@ -317,9 +316,9 @@ function UserDropdown() {
{
+ onSelect={async (event) => {
event.preventDefault()
- submit(formRef.current)
+ await submit(formRef.current)
}}
>