Review
AetherGrow bay detail: slim select + defer telemetry
Cellar techs on the AetherGrow rooftop greenhouse open bay detail dozens of times per shift. The loader was awaiting a full Prisma row (nutrient recipes, vendor notes, photo blobs) and a 24h Timescale telemetry series before the route could paint. Return only the header/control-strip fields via select, and defer the slow sparkline series so the shell streams immediately.
RemixTier 3remixloaderdeferoverfetch
Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.
app/routes/bays.$bayId.tsx+20-4
| 1 | 1 | import type { LoaderFunctionArgs } from "@remix-run/node"; | |
| 2 | 2 | import { defer } from "@remix-run/node"; | |
| 3 | 3 | import { Await, useLoaderData } from "@remix-run/react"; | |
| 4 | 4 | import { Suspense } from "react"; | |
| 5 | 5 | import { db } from "~/db.server"; | |
| 6 | 6 | import { loadBayTelemetry } from "~/services/telemetry.server"; | |
| 7 | 7 | import { BayHeader, TelemetrySparkline } from "~/components/bay"; | |
| 8 | 8 | ||
| 9 | 9 | export async function loader({ params }: LoaderFunctionArgs) { | |
| 10 | 10 | const bayId = params.bayId!; | |
| 11 | - | const bay = await db.hydroBay.findUniqueOrThrow({ where: { id: bayId } }); | |
| 12 | - | const telemetry = await loadBayTelemetry(bayId, { hours: 24 }); | |
| 13 | - | return json({ bay, telemetry }); | |
| 11 | + | // Header + control strip only — skip recipes, notes, photo blobs. | |
| 12 | + | const bay = await db.hydroBay.findUniqueOrThrow({ | |
| 13 | + | where: { id: bayId }, | |
| 14 | + | select: { | |
| 15 | + | id: true, | |
| 16 | + | crop: true, | |
| 17 | + | phase: true, | |
| 18 | + | ecTarget: true, | |
| 19 | + | phTarget: true, | |
| 20 | + | lastHarvestAt: true, | |
| 21 | + | }, | |
| 22 | + | }); | |
| 23 | + | // Do not await: stream the 24h series after the shell paints. | |
| 24 | + | const telemetry = loadBayTelemetry(bayId, { hours: 24 }); | |
| 25 | + | return defer({ bay, telemetry }); | |
| 14 | 26 | } | |
| 15 | 27 | ||
| 16 | 28 | export default function BayDetail() { | |
| 17 | 29 | const { bay, telemetry } = useLoaderData<typeof loader>(); | |
| 18 | 30 | return ( | |
| 19 | 31 | <div className="bay-detail"> | |
| 20 | 32 | <BayHeader bay={bay} /> | |
| 33 | + | <Suspense fallback={<p className="spark-skeleton">Loading EC/pH trend…</p>}> | |
| 34 | + | <Await resolve={telemetry}> | |
| 35 | + | {(series) => <TelemetrySparkline series={series} />} | |
| 36 | + | </Await> | |
| 37 | + | </Suspense> | |
| 21 | - | <TelemetrySparkline series={telemetry} /> | |
| 22 | 38 | </div> | |
| 23 | 39 | ); | |
| 24 | 40 | } |