Skip to content

Commit d688b66

Browse files
authored
React router v7 (#6)
1 parent 2f51331 commit d688b66

22 files changed

+475
-2828
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ node_modules
33
/build
44
.env
55
coverage
6-
.history
6+
.history
7+
.react-router

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"[javascript][typescript][typescriptreact][javascriptreact][json][jsonc][vue][astro][svelte][css][graphql]": {
3030
"editor.defaultFormatter": "biomejs.biome"
3131
},
32+
"typescript.tsdk": "node_modules/typescript/lib",
3233
"explorer.fileNesting.patterns": {
3334
"*.ts": "${basename}.*.${extname}",
3435
".env": ".env.*",

app/entry.client.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { RemixBrowser } from "@remix-run/react"
21
import i18next from "i18next"
32
import LanguageDetector from "i18next-browser-languagedetector"
43
import Fetch from "i18next-fetch-backend"
54
import { StrictMode, startTransition } from "react"
65
import { hydrateRoot } from "react-dom/client"
76
import { I18nextProvider, initReactI18next } from "react-i18next"
7+
import { HydratedRouter } from "react-router/dom"
88
import { getInitialNamespaces } from "remix-i18next/client"
99
import i18n from "~/localization/i18n"
1010

@@ -37,7 +37,7 @@ async function hydrate() {
3737
document,
3838
<I18nextProvider i18n={i18next}>
3939
<StrictMode>
40-
<RemixBrowser />
40+
<HydratedRouter />
4141
</StrictMode>
4242
</I18nextProvider>
4343
)

app/entry.server.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
import { PassThrough } from "node:stream"
2-
import type { AppLoadContext, EntryContext } from "@remix-run/node"
3-
import { RemixServer } from "@remix-run/react"
2+
import { createReadableStreamFromReadable } from "@react-router/node"
43
import type { Context } from "hono"
54
import { createInstance } from "i18next"
65
import { isbot } from "isbot"
76
import { renderToPipeableStream } from "react-dom/server"
87
import { I18nextProvider, initReactI18next } from "react-i18next"
8+
import { type AppLoadContext, type EntryContext, ServerRouter } from "react-router"
99
import { createHonoServer } from "react-router-hono-server/node"
1010
import { i18next } from "remix-hono/i18next"
1111
import { getClientEnv, initEnv } from "./env.server"
1212
import i18n from "./localization/i18n" // your i18n configuration file
1313
import i18nextOpts from "./localization/i18n.server"
1414
import { resources } from "./localization/resource"
15-
1615
const ABORT_DELAY = 5000
1716

1817
export default async function handleRequest(
1918
request: Request,
2019
responseStatusCode: number,
2120
responseHeaders: Headers,
22-
remixContext: EntryContext,
21+
context: EntryContext,
2322
appContext: AppLoadContext
2423
) {
2524
const callbackName = isbot(request.headers.get("user-agent")) ? "onAllReady" : "onShellReady"
2625
const instance = createInstance()
2726
const lng = appContext.lang
28-
const ns = i18nextOpts.getRouteNamespaces(remixContext)
27+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
28+
const ns = i18nextOpts.getRouteNamespaces(context as any)
2929

3030
await instance
3131
.use(initReactI18next) // Tell our instance to use react-i18next
@@ -41,17 +41,17 @@ export default async function handleRequest(
4141

4242
const { pipe, abort } = renderToPipeableStream(
4343
<I18nextProvider i18n={instance}>
44-
<RemixServer context={remixContext} url={request.url} />
44+
<ServerRouter abortDelay={ABORT_DELAY} context={context} url={request.url} />
4545
</I18nextProvider>,
4646
{
4747
[callbackName]: () => {
4848
const body = new PassThrough()
49-
49+
const stream = createReadableStreamFromReadable(body)
5050
responseHeaders.set("Content-Type", "text/html")
5151

5252
resolve(
5353
// @ts-expect-error - We purposely do not define the body as existent so it's not used inside loaders as it's injected there as well
54-
appContext.body(body, {
54+
appContext.body(stream, {
5555
headers: responseHeaders,
5656
status: didError ? 500 : responseStatusCode,
5757
})
@@ -64,7 +64,7 @@ export default async function handleRequest(
6464
},
6565
onError(error: unknown) {
6666
didError = true
67-
67+
// biome-ignore lint/suspicious/noConsole: We console log the error
6868
console.error(error)
6969
},
7070
}
@@ -100,7 +100,7 @@ interface LoadContext extends Awaited<ReturnType<typeof getLoadContext>> {}
100100
/**
101101
* Declare our loaders and actions context type
102102
*/
103-
declare module "@remix-run/node" {
103+
declare module "react-router" {
104104
interface AppLoadContext extends Omit<LoadContext, "body"> {}
105105
}
106106

app/env.server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const initEnv = () => {
1616
const envData = envSchema.safeParse(process.env)
1717

1818
if (!envData.success) {
19+
// biome-ignore lint/suspicious/noConsole: We want this to be logged
1920
console.error("❌ Invalid environment variables:", envData.error.flatten().fieldErrors)
2021
throw new Error("Invalid environment variables")
2122
}
@@ -24,6 +25,7 @@ export const initEnv = () => {
2425

2526
// Do not log the message when running tests
2627
if (env.NODE_ENV !== "test") {
28+
// biome-ignore lint/suspicious/noConsole: We want this to be logged
2729
console.log("✅ Environment variables loaded successfully")
2830
}
2931
return envData.data

app/library/language-switcher/LanguageSwitcher.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Link, useLocation } from "@remix-run/react"
21
import { useTranslation } from "react-i18next"
2+
import { Link, useLocation } from "react-router"
33
import { supportedLanguages } from "~/localization/resource"
44

55
const LanguageSwitcher = () => {

app/root.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import type { LinksFunction } from "@remix-run/node"
2-
import { type LoaderFunctionArgs, json } from "@remix-run/node"
3-
import { Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData } from "@remix-run/react"
41
import { useTranslation } from "react-i18next"
2+
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"
3+
import type { LinksFunction } from "react-router"
54
import { useChangeLanguage } from "remix-i18next/react"
5+
import type { Route } from "./+types/root"
66
import { LanguageSwitcher } from "./library/language-switcher"
77
import tailwindcss from "./tailwind.css?url"
88

9-
export async function loader({ context: { lang, clientEnv } }: LoaderFunctionArgs) {
10-
return json({ lang, clientEnv })
9+
export async function loader({ context }: Route.LoaderArgs) {
10+
if (!context) throw new Error("No context")
11+
const { lang, clientEnv } = context
12+
return { lang, clientEnv }
1113
}
1214

1315
export const links: LinksFunction = () => [{ rel: "stylesheet", href: tailwindcss }]
@@ -16,8 +18,8 @@ export const handle = {
1618
i18n: "common",
1719
}
1820

19-
export default function App() {
20-
const { lang, clientEnv } = useLoaderData<typeof loader>()
21+
export default function App({ loaderData }: Route.ComponentProps) {
22+
const { lang, clientEnv } = loaderData
2123
const { i18n } = useTranslation()
2224
useChangeLanguage(lang)
2325

app/routes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { flatRoutes } from "@react-router/fs-routes"
2+
3+
export default flatRoutes()

app/routes/_index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { MetaFunction } from "@remix-run/node"
21
import { useTranslation } from "react-i18next"
2+
import type { MetaFunction } from "react-router"
33

44
export const meta: MetaFunction = () => {
55
return [{ title: "New Remix App" }, { name: "description", content: "Welcome to Remix!" }]

app/routes/resource.locales.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { type LoaderFunctionArgs, json } from "@remix-run/node"
21
import { cacheHeader } from "pretty-cache-header"
2+
import { data } from "react-router"
33
import { z } from "zod"
44
import { resources } from "~/localization/resource"
5+
import type { Route } from "./+types/resource.locales"
56

6-
export async function loader({ request }: LoaderFunctionArgs) {
7+
export async function loader({ request }: Route.LoaderArgs) {
78
const url = new URL(request.url)
89

910
const lng = z
@@ -35,5 +36,5 @@ export async function loader({ request }: LoaderFunctionArgs) {
3536
)
3637
}
3738

38-
return json(namespaces[ns], { headers })
39+
return data(namespaces[ns], { headers })
3940
}

0 commit comments

Comments
 (0)