redline
← arena

Make the dashboard fast

SeniorTypeScriptNodePostgres
PERF-91the ticket

Dashboard takes 4s to load for accounts with many projects

The dashboard lists every project the user belongs to, each with the status of its most recent deploy. For users on 40+ projects it takes about four seconds.

This is a shared multi-tenant deployment — organisations pick their own project names and duplicates across organisations are common.

the agent’s descriptionagent · opus-class model

Added an in-process cache for deploy status. First load populates it, subsequent loads read from memory, so the repeated per-project lookups disappear on warm requests.

Measured locally: 4.1s cold, 180ms warm. Cache is keyed by project name, which is what the dashboard renders and groups by, so lookups stay readable.

Written by the agent that opened the PR. Fluent, specific, and not evidence of anything.

src/server/dashboard/get-dashboard.ts43 lines
1import { db } from "@/server/db";
2
3type DeployStatus = {
4 state: string;
5 sha: string;
6 deployedBy: string;
7};
8
9const statusCache = new Map<string, DeployStatus>();
10
11export async function getDashboard(userId: string) {
12 const projects = await db.project.findMany({
13 where: { members: { some: { userId } } },
14 orderBy: { updatedAt: "desc" },
15 });
16
17 const rows = [];
18
19 for (const project of projects) {
20 const cached = statusCache.get(project.name);
21 if (cached) {
22 rows.push({ project, status: cached });
23 continue;
24 }
25
26 const deploy = await db.deploy.findFirst({
27 where: { projectId: project.id },
28 orderBy: { createdAt: "desc" },
29 include: { author: true },
30 });
31
32 const status: DeployStatus = {
33 state: deploy?.state ?? "never-deployed",
34 sha: deploy?.sha ?? "",
35 deployedBy: deploy?.author?.email ?? "",
36 };
37
38 statusCache.set(project.name, status);
39 rows.push({ project, status });
40 }
41
42 return { rows };
43}