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")}}>
-
+
>
);