diff --git a/app/entry.server.tsx b/app/entry.server.tsx index f0c651ed..4ab96f44 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -4,10 +4,16 @@ import { createInstance } from "i18next" import { isbot } from "isbot" import { renderToPipeableStream } from "react-dom/server" import { I18nextProvider, initReactI18next } from "react-i18next" -import { type AppLoadContext, type EntryContext, type HandleDataRequestFunction, ServerRouter } from "react-router" +import { + type EntryContext, + type HandleDataRequestFunction, + type RouterContextProvider, + ServerRouter, +} from "react-router" import i18n from "./localization/i18n" // your i18n configuration file import i18nextOpts from "./localization/i18n.server" import { resources } from "./localization/resource" +import { globalAppContext } from "./server/context" // Reject all pending promises from handler functions after 10 seconds export const streamTimeout = 10000 @@ -17,11 +23,12 @@ export default async function handleRequest( responseStatusCode: number, responseHeaders: Headers, context: EntryContext, - appContext: AppLoadContext + appContext: RouterContextProvider ) { const callbackName = isbot(request.headers.get("user-agent")) ? "onAllReady" : "onShellReady" const instance = createInstance() - const lng = appContext.lang + const ctx = appContext.get(globalAppContext) + const lng = ctx.lang const ns = i18nextOpts.getRouteNamespaces(context) await instance @@ -48,7 +55,7 @@ export default async function handleRequest( resolve( // @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 - appContext.body(stream, { + ctx.body(stream, { headers: responseHeaders, status: didError ? 500 : responseStatusCode, }) diff --git a/app/root.tsx b/app/root.tsx index 6aeaa9fe..b9149d10 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -4,11 +4,12 @@ import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration, import { useChangeLanguage } from "remix-i18next/react" import type { Route } from "./+types/root" import { LanguageSwitcher } from "./library/language-switcher" +import { globalAppContext } from "./server/context" import { ClientHintCheck, getHints } from "./services/client-hints" import tailwindcss from "./tailwind.css?url" export async function loader({ context, request }: Route.LoaderArgs) { - const { lang, clientEnv } = context + const { lang, clientEnv } = context.get(globalAppContext) const hints = getHints(request) return { lang, clientEnv, hints } } diff --git a/app/routes/resource.locales.ts b/app/routes/resource.locales.ts index 29e3a92c..01641095 100644 --- a/app/routes/resource.locales.ts +++ b/app/routes/resource.locales.ts @@ -1,10 +1,11 @@ import { cacheHeader } from "pretty-cache-header" import { z } from "zod/v4" import { type Language, type Namespace, resources } from "~/localization/resource" +import { globalAppContext } from "~/server/context" import type { Route } from "./+types/resource.locales" export async function loader({ request, context }: Route.LoaderArgs) { - const { isProductionDeployment } = context + const { isProductionDeployment } = context.get(globalAppContext) const url = new URL(request.url) const lng = z.enum(Object.keys(resources) as Language[]).parse(url.searchParams.get("lng")) diff --git a/app/routes/robots[.]txt.ts b/app/routes/robots[.]txt.ts index 548d1e94..a9242229 100644 --- a/app/routes/robots[.]txt.ts +++ b/app/routes/robots[.]txt.ts @@ -1,10 +1,10 @@ import { generateRobotsTxt } from "@forge42/seo-tools/robots" - +import { globalAppContext } from "~/server/context" import { createDomain } from "~/utils/http" import type { Route } from "./+types/robots[.]txt" export async function loader({ request, context }: Route.LoaderArgs) { - const { isProductionDeployment } = context + const { isProductionDeployment } = context.get(globalAppContext) const domain = createDomain(request) const robotsTxt = generateRobotsTxt([ { diff --git a/app/server/context.ts b/app/server/context.ts index ddf43b2b..5b1daf94 100644 --- a/app/server/context.ts +++ b/app/server/context.ts @@ -1,8 +1,11 @@ import type { Context } from "hono" +import { createContext, RouterContextProvider } from "react-router" import { i18next } from "remix-hono/i18next" import { getClientEnv, getServerEnv } from "~/env.server" -export const getLoadContext = async (c: Context) => { +export const globalAppContext = createContext() + +export const getAppContext = async (c: Context) => { // get the locale from the context const locale = i18next.getLocale(c) // get t function for the default namespace @@ -21,7 +24,14 @@ export const getLoadContext = async (c: Context) => { } } -interface LoadContext extends Awaited> {} +export const getLoadContext = async (c: Context) => { + const ctx = new RouterContextProvider() + const globalCtx = await getAppContext(c) + ctx.set(globalAppContext, globalCtx) + return ctx +} + +interface LoadContext extends Awaited> {} /** * Declare our loaders and actions context type diff --git a/react-router.config.ts b/react-router.config.ts index 117b8d5e..87c729a8 100644 --- a/react-router.config.ts +++ b/react-router.config.ts @@ -5,5 +5,6 @@ export default { unstable_viteEnvironmentApi: true, unstable_splitRouteModules: true, unstable_optimizeDeps: true, + v8_middleware: true, }, } satisfies Config