Add ClientMap wrapper to avoid lazy() SSR resolution
.client.tsx modules return undefined on the server, but lazy() still tries to resolve them during SSR causing "Element type is invalid". Replace lazy()+Suspense with a ClientMap component that dynamically imports the map only in useEffect (client-side only). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
81eb38a24d
commit
9b2adebc4d
5 changed files with 30 additions and 32 deletions
21
apps/journal/app/components/ClientMap.tsx
Normal file
21
apps/journal/app/components/ClientMap.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { useState, useEffect, type ComponentProps } from "react";
|
||||
|
||||
type MapProps = ComponentProps<typeof import("./RouteMapThumbnail.client").RouteMapThumbnail>;
|
||||
|
||||
export function ClientMap(props: MapProps) {
|
||||
const [Component, setComponent] = useState<React.ComponentType<MapProps>>();
|
||||
|
||||
useEffect(() => {
|
||||
import("./RouteMapThumbnail.client").then((m) => setComponent(() => m.RouteMapThumbnail));
|
||||
}, []);
|
||||
|
||||
if (!Component) {
|
||||
return (
|
||||
<div className={`flex items-center justify-center bg-gray-100 text-gray-500 ${props.className ?? "h-36 w-full rounded"}`}>
|
||||
Loading map...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <Component {...props} />;
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { Suspense, lazy } from "react";
|
||||
import { data, redirect } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { Route } from "./+types/activities.$id";
|
||||
|
|
@ -7,10 +6,7 @@ import { getActivity, deleteActivity, linkActivityToRoute, createRouteFromActivi
|
|||
import { deleteImportByActivity } from "~/lib/sync/imports.server";
|
||||
import { listRoutes } from "~/lib/routes.server";
|
||||
import { ClientDate } from "~/components/ClientDate";
|
||||
|
||||
const RouteMapThumbnail = lazy(() =>
|
||||
import("~/components/RouteMapThumbnail.client").then((m) => ({ default: m.RouteMapThumbnail })),
|
||||
);
|
||||
import { ClientMap } from "~/components/ClientMap";
|
||||
|
||||
export async function loader({ params, request }: Route.LoaderArgs) {
|
||||
const activity = await getActivity(params.id);
|
||||
|
|
@ -125,9 +121,7 @@ export default function ActivityDetailPage({ loaderData }: Route.ComponentProps)
|
|||
|
||||
{activity.geojson && (
|
||||
<div className="mt-6 overflow-hidden rounded-lg border border-gray-200" style={{ height: 400 }}>
|
||||
<Suspense fallback={<div className="flex h-full items-center justify-center bg-gray-100 text-gray-500">Loading map...</div>}>
|
||||
<RouteMapThumbnail geojson={activity.geojson} interactive className="h-full w-full" />
|
||||
</Suspense>
|
||||
<ClientMap geojson={activity.geojson} interactive className="h-full w-full" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import { data, redirect } from "react-router";
|
||||
import { Suspense, lazy } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { Route } from "./+types/activities._index";
|
||||
import { getSessionUser } from "~/lib/auth.server";
|
||||
import { listActivities } from "~/lib/activities.server";
|
||||
import { ClientDate } from "~/components/ClientDate";
|
||||
|
||||
const RouteMapThumbnail = lazy(() =>
|
||||
import("~/components/RouteMapThumbnail.client").then((m) => ({ default: m.RouteMapThumbnail })),
|
||||
);
|
||||
import { ClientMap } from "~/components/ClientMap";
|
||||
|
||||
export async function loader({ request }: Route.LoaderArgs) {
|
||||
const user = await getSessionUser(request);
|
||||
|
|
@ -64,9 +60,7 @@ export default function ActivitiesListPage({ loaderData }: Route.ComponentProps)
|
|||
<div className="flex gap-4">
|
||||
<div className="w-48 shrink-0">
|
||||
{activity.geojson ? (
|
||||
<Suspense fallback={<div className="h-36 w-full animate-pulse rounded bg-gray-100" />}>
|
||||
<RouteMapThumbnail geojson={activity.geojson} />
|
||||
</Suspense>
|
||||
<ClientMap geojson={activity.geojson} />
|
||||
) : (
|
||||
<div className="flex h-36 w-full items-center justify-center rounded bg-gray-100 text-xs text-gray-400">
|
||||
{t("routes.noMapPreview")}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import { useState, useCallback, Suspense, lazy } from "react";
|
||||
import { useState, useCallback } from "react";
|
||||
import { data, redirect } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { Route } from "./+types/routes.$id";
|
||||
import { getSessionUser } from "~/lib/auth.server";
|
||||
import { getRoute, getRouteWithVersions, deleteRoute, updateRoute } from "~/lib/routes.server";
|
||||
import { ClientDate } from "~/components/ClientDate";
|
||||
|
||||
const RouteMapThumbnail = lazy(() =>
|
||||
import("~/components/RouteMapThumbnail.client").then((m) => ({ default: m.RouteMapThumbnail })),
|
||||
);
|
||||
import { ClientMap } from "~/components/ClientMap";
|
||||
|
||||
|
||||
export async function loader({ params, request }: Route.LoaderArgs) {
|
||||
|
|
@ -161,9 +158,7 @@ export default function RouteDetailPage({ loaderData }: Route.ComponentProps) {
|
|||
|
||||
{route.geojson && (
|
||||
<div className="mt-6 overflow-hidden rounded-lg border border-gray-200" style={{ height: 400 }}>
|
||||
<Suspense fallback={<div className="flex h-full items-center justify-center bg-gray-100 text-gray-500">Loading map...</div>}>
|
||||
<RouteMapThumbnail geojson={route.geojson} interactive className="h-full w-full" />
|
||||
</Suspense>
|
||||
<ClientMap geojson={route.geojson} interactive className="h-full w-full" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import { data, redirect } from "react-router";
|
||||
import { Suspense, lazy } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { Route } from "./+types/routes._index";
|
||||
import { getSessionUser } from "~/lib/auth.server";
|
||||
import { listRoutes } from "~/lib/routes.server";
|
||||
import { ClientDate } from "~/components/ClientDate";
|
||||
|
||||
const RouteMapThumbnail = lazy(() =>
|
||||
import("~/components/RouteMapThumbnail.client").then((m) => ({ default: m.RouteMapThumbnail })),
|
||||
);
|
||||
import { ClientMap } from "~/components/ClientMap";
|
||||
|
||||
export async function loader({ request }: Route.LoaderArgs) {
|
||||
const user = await getSessionUser(request);
|
||||
|
|
@ -62,9 +58,7 @@ export default function RoutesListPage({ loaderData }: Route.ComponentProps) {
|
|||
<div className="flex gap-4">
|
||||
<div className="w-48 shrink-0">
|
||||
{route.geojson ? (
|
||||
<Suspense fallback={<div className="h-36 w-full animate-pulse rounded bg-gray-100" />}>
|
||||
<RouteMapThumbnail geojson={route.geojson} />
|
||||
</Suspense>
|
||||
<ClientMap geojson={route.geojson} />
|
||||
) : (
|
||||
<div className="flex h-36 w-full items-center justify-center rounded bg-gray-100 text-xs text-gray-400">
|
||||
{t("routes.noMapPreview")}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue