Files
Reactive-Resume/apps/web/src/routes/__root.tsx
T
Amruth Pillai 500cabaf4a perf(web): stop every navigation from waiting on the network
Root beforeLoad now reads the session and flags through the query cache
instead of refetching them on every navigation and preload. A signed-in
session is reused for a minute, a signed-out one is always re-checked,
and every sign-in, profile and 2FA change invalidates the cached session
before re-running route guards. The active Lingui locale is no longer
re-activated per navigation.

Links now preload on intent with TanStack Query deciding freshness, the
pending loader shows after 300ms and holds for 300ms, and it fades in on
in-app navigations while still taking over the HTML loader without a
blink on first load.
2026-09-29 12:35:38 +02:00

157 lines
6.2 KiB
TypeScript

import type { IconProps } from "@phosphor-icons/react";
import type { FeatureFlags } from "@reactive-resume/api/features/flags";
import type { AuthSession } from "@reactive-resume/auth/types";
import type { Locale } from "@reactive-resume/utils/locale";
import type { QueryClient } from "@tanstack/react-query";
import type { orpc } from "@/libs/orpc/client";
import type { Theme } from "@/libs/theme";
import { DirectionProvider } from "@base-ui/react/direction-provider";
import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import { IconContext } from "@phosphor-icons/react";
import { TanStackDevtools } from "@tanstack/react-devtools";
import { HotkeysProvider } from "@tanstack/react-hotkeys";
import { QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools";
import { createRootRouteWithContext, HeadContent, Outlet, useRouterState } from "@tanstack/react-router";
import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools";
import { domMax, LazyMotion, MotionConfig } from "motion/react";
import { useEffect } from "react";
import { Toaster } from "@reactive-resume/ui/components/toast";
import { TooltipProvider } from "@reactive-resume/ui/components/tooltip";
import { isRTL } from "@reactive-resume/utils/locale";
import { BreakpointIndicator } from "@/components/layout/breakpoint-indicator";
import { DonationToast } from "@/components/ui/donation-toast";
import { DialogManager } from "@/dialogs/manager";
import { CommandPalette } from "@/features/command-palette";
import { ThemeProvider } from "@/features/theme/provider";
import { ConfirmDialogProvider } from "@/hooks/use-confirm";
import { loadRootContext } from "@/libs/root-context";
type RouterContext = {
theme: Theme;
locale: Locale;
orpc: typeof orpc;
queryClient: QueryClient;
session: AuthSession | null;
flags: FeatureFlags;
};
const appName = "Reactive Resume";
const tagline = "A free and open-source resume builder";
const title = `${appName} — ${tagline}`;
// Keep under ~120 characters so Google's mobile SERP snippet is not truncated at 3 lines.
const description =
"Free, open-source resume builder. Create, update, and share your resume, with no ads and no paywall.";
const iconContextValue: IconProps = { size: 16, weight: "regular" };
export const Route = createRootRouteWithContext<RouterContext>()({
component: RootComponent,
head: () => {
const appUrl = window.location.origin;
return {
links: [
// Icons
{ rel: "icon", href: "/favicon.ico", type: "image/x-icon", sizes: "128x128" },
{ rel: "icon", href: "/favicon.svg", type: "image/svg+xml", sizes: "256x256 any" },
{ rel: "apple-touch-icon", href: "/apple-touch-icon-180x180.png", type: "image/png", sizes: "180x180 any" },
// Manifest
{ rel: "manifest", href: "/manifest.webmanifest", crossOrigin: "use-credentials" },
],
meta: [
{ title },
{ charSet: "UTF-8" },
{ name: "description", content: description },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
// Meta Tags
{ name: "theme-color", content: "#F8F7F3", media: "(prefers-color-scheme: light)" },
{ name: "theme-color", content: "#100F0C", media: "(prefers-color-scheme: dark)" },
{ name: "application-name", content: "Reactive Resume" },
{ name: "mobile-web-app-capable", content: "yes" },
{ name: "apple-mobile-web-app-capable", content: "yes" },
{ name: "apple-mobile-web-app-title", content: "Reactive Resume" },
{ name: "apple-mobile-web-app-status-bar-style", content: "black-translucent" },
// Twitter Tags — X only reads these as `name`, not `property`
{ name: "twitter:image", content: `${appUrl}/opengraph/banner.jpg` },
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:url", content: appUrl },
{ name: "twitter:title", content: title },
{ name: "twitter:description", content: description },
// OpenGraph Tags
{ property: "og:type", content: "website" },
{ property: "og:image", content: `${appUrl}/opengraph/banner.jpg` },
{ property: "og:site_name", content: appName },
{ property: "og:title", content: title },
{ property: "og:description", content: description },
{ property: "og:url", content: appUrl },
],
};
},
beforeLoad: ({ context }) => loadRootContext(context.queryClient),
});
function RootComponent() {
const { theme, locale, queryClient } = Route.useRouteContext();
const dir = isRTL(locale) ? "rtl" : "ltr";
// Suppress the app-wide donation toast inside the builder so it doesn't cover the right-sidebar controls.
const isBuilder = useRouterState({ select: (s) => s.location.pathname.startsWith("/builder") });
// The theme class is owned by ThemeProvider, which also follows the system appearance.
useEffect(() => {
document.documentElement.lang = locale;
document.documentElement.dir = dir;
}, [dir, locale]);
return (
<>
<HeadContent />
<QueryClientProvider client={queryClient}>
<MotionConfig reducedMotion="user">
<LazyMotion features={domMax}>
<I18nProvider i18n={i18n}>
<IconContext.Provider value={iconContextValue}>
<ThemeProvider theme={theme}>
<HotkeysProvider>
<DirectionProvider direction={dir}>
<TooltipProvider>
<ConfirmDialogProvider>
<Outlet />
{!isBuilder && <DonationToast />}
<DialogManager />
<CommandPalette />
<Toaster />
{import.meta.env.DEV && <BreakpointIndicator />}
{import.meta.env.DEV && (
<TanStackDevtools
config={{ position: "bottom-left" }}
plugins={[
{
name: "TanStack Query",
render: <ReactQueryDevtoolsPanel />,
},
{
name: "TanStack Router",
render: <TanStackRouterDevtoolsPanel />,
},
]}
/>
)}
</ConfirmDialogProvider>
</TooltipProvider>
</DirectionProvider>
</HotkeysProvider>
</ThemeProvider>
</IconContext.Provider>
</I18nProvider>
</LazyMotion>
</MotionConfig>
</QueryClientProvider>
</>
);
}