Move SSR-stable today loader to data.ts (Modern.js convention)
ci-deploy / build-deploy-test (push) Successful in 1m54s
ci-deploy / build-deploy-test (push) Successful in 1m54s
Inline export const loader from page.tsx didn't run — _ROUTER_DATA showed loaderData[(lang)/onlineboard/page] = null and useLoaderData() threw 'Cannot read properties of null'. Modern.js conventional routes require the loader in a co-located data.ts file. useLoaderData() now defensively handles null (defaults to undefined, component falls back to useRef(new Date())). Worst case if loader still doesn't fire: same hydration drift as before, no crash.
This commit is contained in:
@@ -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(),
|
||||
});
|
||||
@@ -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 {
|
||||
<>
|
||||
<SeoHead {...seoProps} jsonLd={jsonLd} />
|
||||
<Suspense fallback={<div aria-busy="true">{t("SHARED.LOADING")}</div>}>
|
||||
<FlightsMapStartPage tileUrl={tileUrl} today={today} />
|
||||
<FlightsMapStartPage tileUrl={tileUrl} {...(today ? { today } : {})} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
@@ -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 {
|
||||
<>
|
||||
<SeoHead {...seoProps} />
|
||||
<Suspense fallback={<div aria-busy="true">{t("SHARED.LOADING")}</div>}>
|
||||
<OnlineBoardStartPage today={today} />
|
||||
<OnlineBoardStartPage {...(today ? { today } : {})} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user