Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/entry.client.tsx
T

105 lines
4.4 KiB
TypeScript

// IMPORTANT: OTel MUST initialise BEFORE Sentry. The Sentry browser SDK
// plugs into @opentelemetry/api's global TracerProvider; initialising the
// WebTracerProvider first installs that provider so the two stacks share
// the trace_id. No-op when VITE_OTEL_EXPORTER_OTLP_ENDPOINT is unset.
const publicEnv = ((import.meta as unknown as { env?: Record<string, string | undefined> }).env ?? {}) as Record<string, string | undefined>;
function hasRuntimeSentryDsn() {
return typeof globalThis !== "undefined" && typeof (globalThis as { __SENTRY_WEB_DSN__?: unknown }).__SENTRY_WEB_DSN__ === "string";
}
async function initClientObservability() {
if (publicEnv.VITE_OTEL_EXPORTER_OTLP_ENDPOINT) {
const { initClientOtel } = await import("./otel.client");
initClientOtel();
}
if (publicEnv.VITE_SENTRY_DSN_WEB || hasRuntimeSentryDsn()) {
const { initSentryClient } = await import("./sentry.client");
initSentryClient();
// Core Web Vitals → Sentry (#70). Fire-and-forget; the helper is SSR-safe
// and silently no-ops when Sentry/web-vitals aren't available. Keep it out
// of observabilityReady so hydration never waits for the web-vitals chunk.
void import("./lib/observability/web-vitals").then(({ reportWebVitals }) => reportWebVitals()).catch(() => {});
}
}
// Start configured observability immediately, but keep it off hydration's
// critical path. A failed SDK import must degrade to browser/React defaults
// instead of delaying or blanking the app.
void initClientObservability().catch(() => {});
import { HydratedRouter } from "react-router/dom";
import { StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { sentryHydrationErrorHandler } from "./lib/sentry/routeTracing";
import { getInitialNamespaces } from "remix-i18next/client";
import { I18nextProvider, initReactI18next } from "react-i18next";
import i18next from "i18next";
import Backend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";
import { formalLngs, i18nConfig } from "./locale/i18n.js";
function hydrate() {
const localeAssetVersion = publicEnv.VITE_APP_REVISION || publicEnv.VITE_SENTRY_RELEASE;
const localeLoadPath = localeAssetVersion ? `/locales/{{lng}}/{{ns}}.json?v=${encodeURIComponent(localeAssetVersion)}` : "/locales/{{lng}}/{{ns}}.json";
i18next
// Use the react-i18next plugin.
.use(initReactI18next)
// Setup client-side language detector.
.use(LanguageDetector)
// Setup backend.
.use(Backend)
.init({
// Spread configuration.
...i18nConfig,
// Permit the formal variants as languages so a post-hydration
// `changeLanguage("<locale>-formal")` (driven by the Anrede toggle in
// root.tsx) actually loads "/locales/<locale>-formal/*.json" instead of
// being filtered out. Purely additive: with the default "du" the language
// stays the base locale detected from `<html lang>`, so existing
// behaviour is unchanged. Missing formal files fall back to the base
// locale via `fallbackLng`.
supportedLngs: [...i18nConfig.supportedLngs, ...formalLngs],
// Detects the namespaces your routes rendered while SSR use
// and pass them here to load the translations.
ns: getInitialNamespaces(),
backend: {
loadPath: localeLoadPath,
},
detection: {
// We'll detect the language only server-side with remix-i18next.
// By using `<html lang>` attribute we communicate to the Client.
order: ["htmlTag"],
},
})
.then(() => {
hydrateRoot(
document,
<I18nextProvider i18n={i18next}>
<StrictMode>
<HydratedRouter onError={sentryHydrationErrorHandler} />
</StrictMode>
</I18nextProvider>
);
// Signal hydration completion so end-to-end tests can wait for the app to
// be interactive before driving JS-only controls (sidebar drawer, command
// palette, dropdowns). React 18 attaches event handlers during the
// hydrateRoot commit; flag after two animation frames so the marker only
// appears once that commit has painted. Progressive-enhanced <Form>s work
// before this, but pure-JS buttons do not — hence the explicit signal.
requestAnimationFrame(() => {
requestAnimationFrame(() => {
document.documentElement.setAttribute("data-hydrated", "true");
});
});
});
}
hydrate();