Skip to content

Commit 158ed99

Browse files
DennisKraaijeveldkentcdoddshakimLyon
authored
Feat/migrate to react router 7 (#897)
Co-authored-by: Kent C. Dodds <[email protected]> Co-authored-by: Hakim Gueye <[email protected]> Co-authored-by: Hakim Gueye <[email protected]> Co-authored-by: Kent C. Dodds <[email protected]>
1 parent b667696 commit 158ed99

File tree

83 files changed

+10158
-15451
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+10158
-15451
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ node_modules
2323

2424
# generated files
2525
/app/components/ui/icons
26+
.react-router/

app/components/error-boundary.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1+
import { captureException } from '@sentry/react'
2+
import { useEffect, type ReactElement } from 'react'
13
import {
24
type ErrorResponse,
35
isRouteErrorResponse,
46
useParams,
57
useRouteError,
6-
} from '@remix-run/react'
7-
import { captureRemixErrorBoundaryError } from '@sentry/remix'
8-
import { type ReactElement } from 'react'
9-
import { getErrorMessage } from '#app/utils/misc.tsx'
8+
} from 'react-router'
9+
import { getErrorMessage } from '#app/utils/misc'
1010

1111
type StatusHandler = (info: {
1212
error: ErrorResponse
@@ -27,13 +27,16 @@ export function GeneralErrorBoundary({
2727
unexpectedErrorHandler?: (error: unknown) => ReactElement | null
2828
}) {
2929
const error = useRouteError()
30-
captureRemixErrorBoundaryError(error)
3130
const params = useParams()
3231

3332
if (typeof document !== 'undefined') {
3433
console.error(error)
3534
}
3635

36+
useEffect(() => {
37+
captureException(error)
38+
}, [error])
39+
3740
return (
3841
<div className="container flex items-center justify-center p-20 text-h2">
3942
{isRouteErrorResponse(error)

app/components/progress-bar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useNavigation } from '@remix-run/react'
1+
import { useNavigation } from 'react-router'
22
import { useEffect, useRef, useState } from 'react'
33
import { useSpinDelay } from 'spin-delay'
44
import { cn } from '#app/utils/misc.tsx'

app/components/search-bar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Form, useSearchParams, useSubmit } from '@remix-run/react'
21
import { useId } from 'react'
2+
import { Form, useSearchParams, useSubmit } from 'react-router'
33
import { useDebounce, useIsPending } from '#app/utils/misc.tsx'
44
import { Icon } from './ui/icon.tsx'
55
import { Input } from './ui/input.tsx'
@@ -23,8 +23,8 @@ export function SearchBar({
2323
formAction: '/users',
2424
})
2525

26-
const handleFormChange = useDebounce((form: HTMLFormElement) => {
27-
submit(form)
26+
const handleFormChange = useDebounce(async (form: HTMLFormElement) => {
27+
await submit(form)
2828
}, 400)
2929

3030
return (

app/entry.client.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { RemixBrowser } from '@remix-run/react'
21
import { startTransition } from 'react'
32
import { hydrateRoot } from 'react-dom/client'
3+
import { HydratedRouter } from 'react-router/dom'
44

55
if (ENV.MODE === 'production' && ENV.SENTRY_DSN) {
66
void import('./utils/monitoring.client.tsx').then(({ init }) => init())
77
}
88

99
startTransition(() => {
10-
hydrateRoot(document, <RemixBrowser />)
10+
hydrateRoot(document, <HydratedRouter />)
1111
})

app/entry.server.tsx

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import { PassThrough } from 'node:stream'
2+
import { createReadableStreamFromReadable } from '@react-router/node'
3+
4+
import * as Sentry from '@sentry/node'
5+
import chalk from 'chalk'
6+
import { isbot } from 'isbot'
7+
import { renderToPipeableStream } from 'react-dom/server'
28
import {
3-
createReadableStreamFromReadable,
9+
ServerRouter,
410
type LoaderFunctionArgs,
511
type ActionFunctionArgs,
612
type HandleDocumentRequestFunction,
7-
} from '@remix-run/node'
8-
import { RemixServer } from '@remix-run/react'
9-
import * as Sentry from '@sentry/remix'
10-
import chalk from 'chalk'
11-
import { isbot } from 'isbot'
12-
import { renderToPipeableStream } from 'react-dom/server'
13+
} from 'react-router'
1314
import { getEnv, init } from './utils/env.server.ts'
1415
import { getInstanceInfo } from './utils/litefs.server.ts'
1516
import { NonceProvider } from './utils/nonce-provider.ts'
1617
import { makeTimings } from './utils/timing.server.ts'
1718

18-
const ABORT_DELAY = 5000
19+
export const streamTimeout = 5000
1920

2021
init()
2122
global.ENV = getEnv()
@@ -27,7 +28,7 @@ export default async function handleRequest(...args: DocRequestArgs) {
2728
request,
2829
responseStatusCode,
2930
responseHeaders,
30-
remixContext,
31+
reactRouterContext,
3132
loadContext,
3233
] = args
3334
const { currentInstance, primaryInstance } = await getInstanceInfo()
@@ -53,7 +54,11 @@ export default async function handleRequest(...args: DocRequestArgs) {
5354

5455
const { pipe, abort } = renderToPipeableStream(
5556
<NonceProvider value={nonce}>
56-
<RemixServer context={remixContext} url={request.url} />
57+
<ServerRouter
58+
nonce={nonce}
59+
context={reactRouterContext}
60+
url={request.url}
61+
/>
5762
</NonceProvider>,
5863
{
5964
[callbackName]: () => {
@@ -78,7 +83,7 @@ export default async function handleRequest(...args: DocRequestArgs) {
7883
},
7984
)
8085

81-
setTimeout(abort, ABORT_DELAY)
86+
setTimeout(abort, streamTimeout + 5000)
8287
})
8388
}
8489

@@ -103,12 +108,7 @@ export function handleError(
103108
}
104109
if (error instanceof Error) {
105110
console.error(chalk.red(error.stack))
106-
void Sentry.captureRemixServerException(
107-
error,
108-
'remix.server',
109-
request,
110-
true,
111-
)
111+
void Sentry.captureException(error)
112112
} else {
113113
console.error(error)
114114
Sentry.captureException(error)

app/root.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
import { wrapUseRoutesV7 } from '@sentry/react'
2+
import { useRef } from 'react'
13
import {
2-
json,
4+
data,
35
type LoaderFunctionArgs,
46
type HeadersFunction,
57
type LinksFunction,
68
type MetaFunction,
7-
} from '@remix-run/node'
8-
import {
99
Form,
1010
Link,
1111
Links,
@@ -16,9 +16,7 @@ import {
1616
useLoaderData,
1717
useMatches,
1818
useSubmit,
19-
} from '@remix-run/react'
20-
import { withSentry } from '@sentry/remix'
21-
import { useRef } from 'react'
19+
} from 'react-router'
2220
import { HoneypotProvider } from 'remix-utils/honeypot/react'
2321
import appleTouchIconAssetUrl from './assets/favicons/apple-touch-icon.png'
2422
import faviconAssetUrl from './assets/favicons/favicon.svg'
@@ -121,7 +119,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
121119
const { toast, headers: toastHeaders } = await getToast(request)
122120
const honeyProps = honeypot.getInputProps()
123121

124-
return json(
122+
return data(
125123
{
126124
user,
127125
requestInfo: {
@@ -161,7 +159,8 @@ function Document({
161159
children: React.ReactNode
162160
nonce: string
163161
theme?: Theme
164-
env?: Record<string, string>
162+
env?: Record<string, string | undefined>
163+
allowIndexing?: boolean
165164
}) {
166165
const allowIndexing = ENV.ALLOW_INDEXING !== 'false'
167166
return (
@@ -271,7 +270,7 @@ function AppWithProviders() {
271270
)
272271
}
273272

274-
export default withSentry(AppWithProviders)
273+
export default wrapUseRoutesV7(AppWithProviders)
275274

276275
function UserDropdown() {
277276
const user = useUser()
@@ -317,9 +316,9 @@ function UserDropdown() {
317316
<DropdownMenuItem
318317
asChild
319318
// this prevents the menu from closing before the form submission is completed
320-
onSelect={(event) => {
319+
onSelect={async (event) => {
321320
event.preventDefault()
322-
submit(formRef.current)
321+
await submit(formRef.current)
323322
}}
324323
>
325324
<Form action="/logout" method="POST" ref={formRef}>

app/routes.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { type RouteConfig } from '@react-router/dev/routes'
2+
import { remixRoutesOptionAdapter } from '@react-router/remix-routes-option-adapter'
3+
import { flatRoutes } from 'remix-flat-routes'
4+
5+
export default remixRoutesOptionAdapter((defineRoutes) => {
6+
return flatRoutes('routes', defineRoutes, {
7+
ignoredRouteFiles: [
8+
'.*',
9+
'**/*.css',
10+
'**/*.test.{js,jsx,ts,tsx}',
11+
'**/__*.*',
12+
// This is for server-side utilities you want to colocate
13+
// next to your routes without making an additional
14+
// directory. If you need a route that includes "server" or
15+
// "client" in the filename, use the escape brackets like:
16+
// my-route.[server].tsx
17+
'**/*.server.*',
18+
'**/*.client.*',
19+
],
20+
})
21+
}) satisfies RouteConfig

app/routes/$.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// ensure the user gets the right status code and we can display a nicer error
66
// message for them than the Remix and/or browser default.
77

8-
import { Link, useLocation } from '@remix-run/react'
8+
import { Link, useLocation } from 'react-router'
99
import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'
1010
import { Icon } from '#app/components/ui/icon.tsx'
1111

app/routes/_auth+/auth.$provider.callback.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { redirect, type LoaderFunctionArgs } from '@remix-run/node'
1+
import { redirect, type LoaderFunctionArgs } from 'react-router'
22
import {
33
authenticator,
44
getSessionExpirationDate,
@@ -39,8 +39,16 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
3939
const authResult = await authenticator
4040
.authenticate(providerName, request, { throwOnError: true })
4141
.then(
42-
(data) => ({ success: true, data }) as const,
43-
(error) => ({ success: false, error }) as const,
42+
(data) =>
43+
({
44+
success: true,
45+
data,
46+
}) as const,
47+
(error) =>
48+
({
49+
success: false,
50+
error,
51+
}) as const,
4452
)
4553

4654
if (!authResult.success) {

0 commit comments

Comments
 (0)