Skip to content

Commit 91b6504

Browse files
committed
RBAC: split dashboardBuilder so client-bundle imports resolve
The dev build was crashing with 'dashboardLoader is not a function' on first navigation to any /admin route, then the browser would hard-reload back to the previous page. Symptom: clicking 'Admin dashboard' (or anywhere /@ → /admin chain) flashed admin then bounced back, with no obvious cause server-side (every loader returned 200). Root cause: routes export their loader at module top-level via the wrapper: export const loader = dashboardLoader(...); The factory call evaluates at module load. dashboardBuilder lived in a .server.ts file, which Remix strips from the client bundle. In the prod build the loader export + its RHS are both tree-shaken, so the import is unreferenced and removed — fine. In the dev build the call is preserved (HMR/source-map friendliness) and resolves dashboardLoader to undefined on the client, throwing on module load. Remix's recovery is to reload the page, which lands on the previous URL because that's the last known-good navigation entry. Fix: split the wrapper so the import target exists on both server and client. - dashboardBuilder.ts (no .server) — exports types + dashboardLoader / dashboardAction wrappers. Wrappers return closures whose bodies dynamic-import the server impl. The closure body never runs on the client, so the dynamic import only resolves at server runtime. Client just sees a function that returns another function — the top-level call now works there. - dashboardBuilder.server.ts — slimmed down to authenticateAndAuthorize + the redirect/authorization helpers. Imported via dynamic import from the wrapper. Stays out of the client bundle. Routes drop the .server suffix on the import path. No change to the route's loader/action surface. Verified end-to-end via Chrome DevTools: /@ → /admin chain renders the admin dashboard cleanly, no console errors, no extra document fetch back to the org URL.
1 parent b9d119a commit 91b6504

16 files changed

Lines changed: 166 additions & 143 deletions

apps/webapp/app/routes/admin._index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
import { useUser } from "~/hooks/useUser";
2121
import { adminGetUsers, redirectWithImpersonation } from "~/models/admin.server";
2222
import { commitImpersonationSession, setImpersonationId } from "~/services/impersonation.server";
23-
import { dashboardAction, dashboardLoader } from "~/services/routeBuilders/dashboardBuilder.server";
23+
import { dashboardAction, dashboardLoader } from "~/services/routeBuilders/dashboardBuilder";
2424
import { createSearchParams } from "~/utils/searchParams";
2525

2626
export const SearchParams = z.object({

apps/webapp/app/routes/admin.back-office._index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { typedjson } from "remix-typedjson";
22
import { LinkButton } from "~/components/primitives/Buttons";
33
import { Header2 } from "~/components/primitives/Headers";
44
import { Paragraph } from "~/components/primitives/Paragraph";
5-
import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder.server";
5+
import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder";
66

77
export const loader = dashboardLoader(
88
{ authorization: { requireSuper: true } },

apps/webapp/app/routes/admin.back-office.orgs.$orgId.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from "~/services/authorizationRateLimitMiddleware.server";
1919
import { logger } from "~/services/logger.server";
2020
import { type Duration } from "~/services/rateLimiter.server";
21-
import { dashboardAction, dashboardLoader } from "~/services/routeBuilders/dashboardBuilder.server";
21+
import { dashboardAction, dashboardLoader } from "~/services/routeBuilders/dashboardBuilder";
2222

2323
const SAVED_QUERY_KEY = "saved";
2424
const SAVED_QUERY_VALUE = "1";

apps/webapp/app/routes/admin.back-office.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Outlet } from "@remix-run/react";
22
import { typedjson } from "remix-typedjson";
3-
import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder.server";
3+
import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder";
44

55
export const loader = dashboardLoader(
66
{ authorization: { requireSuper: true } },

apps/webapp/app/routes/admin.concurrency.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson";
33
import { Header1 } from "~/components/primitives/Headers";
44
import { InfoPanel } from "~/components/primitives/InfoPanel";
55
import { Paragraph } from "~/components/primitives/Paragraph";
6-
import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder.server";
6+
import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder";
77
import { concurrencyTracker } from "~/v3/services/taskRunConcurrencyTracker.server";
88

99
export const loader = dashboardLoader(

apps/webapp/app/routes/admin.feature-flags.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { env } from "~/env.server";
1010
import {
1111
dashboardAction,
1212
dashboardLoader,
13-
} from "~/services/routeBuilders/dashboardBuilder.server";
13+
} from "~/services/routeBuilders/dashboardBuilder";
1414
import {
1515
FEATURE_FLAG,
1616
GLOBAL_LOCKED_FLAGS,

apps/webapp/app/routes/admin.llm-models.$modelId.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Button, LinkButton } from "~/components/primitives/Buttons";
77
import { Input } from "~/components/primitives/Input";
88
import { Paragraph } from "~/components/primitives/Paragraph";
99
import { prisma } from "~/db.server";
10-
import { dashboardAction, dashboardLoader } from "~/services/routeBuilders/dashboardBuilder.server";
10+
import { dashboardAction, dashboardLoader } from "~/services/routeBuilders/dashboardBuilder";
1111
import { llmPricingRegistry } from "~/v3/llmPricingRegistry.server";
1212

1313
const ParamsSchema = z.object({

apps/webapp/app/routes/admin.llm-models._index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
TableRow,
1717
} from "~/components/primitives/Table";
1818
import { prisma } from "~/db.server";
19-
import { dashboardAction, dashboardLoader } from "~/services/routeBuilders/dashboardBuilder.server";
19+
import { dashboardAction, dashboardLoader } from "~/services/routeBuilders/dashboardBuilder";
2020
import { createSearchParams } from "~/utils/searchParams";
2121
import { seedLlmPricing, syncLlmCatalog } from "@internal/llm-model-catalog";
2222
import { llmPricingRegistry } from "~/v3/llmPricingRegistry.server";

apps/webapp/app/routes/admin.llm-models.missing.$model.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson";
33
import { z } from "zod";
44
import { Button, LinkButton } from "~/components/primitives/Buttons";
55
import { Paragraph } from "~/components/primitives/Paragraph";
6-
import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder.server";
6+
import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder";
77
import {
88
getMissingModelSamples,
99
type MissingModelSample,

apps/webapp/app/routes/admin.llm-models.missing._index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
TableHeaderCell,
1313
TableRow,
1414
} from "~/components/primitives/Table";
15-
import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder.server";
15+
import { dashboardLoader } from "~/services/routeBuilders/dashboardBuilder";
1616
import { getMissingLlmModels } from "~/services/admin/missingLlmModels.server";
1717

1818
const LOOKBACK_OPTIONS = [

0 commit comments

Comments
 (0)