diff --git a/src/routes/[lang]/flights-map/data.ts b/src/routes/[lang]/flights-map/data.ts new file mode 100644 index 00000000..a29be3f5 --- /dev/null +++ b/src/routes/[lang]/flights-map/data.ts @@ -0,0 +1,6 @@ +import { todayYyyymmdd } from "@/shared/utils/datetime/index.js"; + +/** Modern.js conventional data loader; see /onlineboard/data.ts. */ +export const loader = (): { today: string } => ({ + today: todayYyyymmdd(), +}); diff --git a/src/routes/[lang]/flights-map/page.tsx b/src/routes/[lang]/flights-map/page.tsx index 6df8eab9..7408fd7a 100644 --- a/src/routes/[lang]/flights-map/page.tsx +++ b/src/routes/[lang]/flights-map/page.tsx @@ -15,18 +15,13 @@ import { buildFlightsMapSeo } from "@/features/flights-map/seo.js"; import { buildFlightsMapJsonLd } from "@/features/flights-map/json-ld.js"; import { useFeatureFlag } from "@/features/flights-map/hooks/useFeatureFlag.js"; import { getEnv } from "@/env/index.js"; -import { todayYyyymmdd } from "@/shared/utils/datetime/index.js"; - const FlightsMapStartPage = lazy(() => import("@/features/flights-map/components/FlightsMapStartPage.js").then( (m) => ({ default: m.FlightsMapStartPage }), ), ); -/** SSR-stable today; see src/routes/[lang]/onlineboard/page.tsx loader. */ -export const loader = (): { today: string } => ({ - today: todayYyyymmdd(), -}); +// Loader lives in ./data.ts (Modern.js conventional data file). export default function FlightsMapPage(): JSX.Element { const { t } = useTranslation(); @@ -37,7 +32,8 @@ export default function FlightsMapPage(): JSX.Element { // Tile URL read on the server (where process.env is available) and passed // down as a prop — the client bundle can't read MAP_TILE_URL itself. const tileUrl = env.MAP_TILE_URL; - const { today } = useLoaderData() as { today: string }; + const data = useLoaderData() as { today?: string } | null; + const today = data?.today; const isEnabled = useFeatureFlag("flightsMap"); if (!isEnabled) { @@ -55,7 +51,7 @@ export default function FlightsMapPage(): JSX.Element { <> {t("SHARED.LOADING")}}> - + ); diff --git a/src/routes/[lang]/onlineboard/data.ts b/src/routes/[lang]/onlineboard/data.ts new file mode 100644 index 00000000..4bbdb8a0 --- /dev/null +++ b/src/routes/[lang]/onlineboard/data.ts @@ -0,0 +1,10 @@ +import { todayYyyymmdd } from "@/shared/utils/datetime/index.js"; + +/** + * Modern.js conventional data loader. Co-located with page.tsx. + * Computes today on the server, exposes it via useLoaderData() so SSR + * and client hydration agree on the same yyyyMMdd. + */ +export const loader = (): { today: string } => ({ + today: todayYyyymmdd(), +}); diff --git a/src/routes/[lang]/onlineboard/page.tsx b/src/routes/[lang]/onlineboard/page.tsx index 399c51d2..857c6c19 100644 --- a/src/routes/[lang]/onlineboard/page.tsx +++ b/src/routes/[lang]/onlineboard/page.tsx @@ -11,31 +11,23 @@ import { useTranslation } from "@/i18n/provider.js"; import { SeoHead } from "@/ui/seo/SeoHead.js"; import { buildOnlineBoardStartSeo } from "@/features/online-board/seo.js"; import { getEnv } from "@/env/index.js"; -import { todayYyyymmdd } from "@/shared/utils/datetime/index.js"; - const OnlineBoardStartPage = lazy(() => import("@/features/online-board/components/OnlineBoardStartPage.js").then( (m) => ({ default: m.OnlineBoardStartPage }), ), ); -/** - * Compute "today" once on the server. The result rides _ROUTER_DATA into - * the client bundle, so the first client render — the one that hydrates - * the SSR markup — sees the same yyyyMMdd value the server saw. Without - * this, components calling `new Date()` during render produce different - * markup on the two sides and React throws hydration error #423. - */ -export const loader = (): { today: string } => ({ - today: todayYyyymmdd(), -}); +// Loader lives in ./data.ts (Modern.js conventional data file). Result +// rides _ROUTER_DATA into the client so SSR and hydration agree on the +// same `today` value, eliminating render-path Date() drift (React #423). export default function OnlineBoardPage(): JSX.Element { const { t } = useTranslation(); const routeParams = useParams<{ lang: string }>(); const locale = routeParams.lang ?? "ru-ru"; const canonicalOrigin = getEnv().PROD_ORIGIN; - const { today } = useLoaderData() as { today: string }; + const data = useLoaderData() as { today?: string } | null; + const today = data?.today; const seoProps = buildOnlineBoardStartSeo(t, locale, canonicalOrigin); @@ -43,7 +35,7 @@ export default function OnlineBoardPage(): JSX.Element { <> {t("SHARED.LOADING")}}> - + );