From bfb0d6a9b87ec7aba56d06a25063aeb1f778dbc6 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Wed, 12 Aug 2026 21:25:30 +0100 Subject: [PATCH 1/3] perf(webapp): drop archived branch environments from project env loads Several project pages loaded every RuntimeEnvironment for a project, including the potentially thousands of archived preview-branch environments that are never shown. On projects with heavy preview-branch usage this produced very large result sets and a rare multi-second tail on the environment lookup. Filter these environment relation loads to archivedAt: null (base environments never archive, so only archived branches are excluded) in ProjectPresenter and the concurrency, api keys, environment variables, and settings layout loaders. Also remove an unused environments select from DeploymentListPresenter. --- .../bound-project-environment-loads.md | 6 ++++++ .../app/presenters/ProjectPresenter.server.ts | 1 + .../v3/DeploymentListPresenter.server.ts | 18 ------------------ ...ationSlug.projects.$projectParam.apikeys.ts | 1 + ...nSlug.projects.$projectParam.concurrency.ts | 1 + ...ects.$projectParam.environment-variables.ts | 1 + ...tionSlug.projects.$projectParam.settings.ts | 1 + 7 files changed, 11 insertions(+), 18 deletions(-) create mode 100644 .server-changes/bound-project-environment-loads.md diff --git a/.server-changes/bound-project-environment-loads.md b/.server-changes/bound-project-environment-loads.md new file mode 100644 index 0000000000..6dfa59bc7e --- /dev/null +++ b/.server-changes/bound-project-environment-loads.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: improvement +--- + +Project pages now load faster for projects with a large number of preview branches, by no longer loading archived branch environments that aren't shown. diff --git a/apps/webapp/app/presenters/ProjectPresenter.server.ts b/apps/webapp/app/presenters/ProjectPresenter.server.ts index 66b51a33b5..773b78edab 100644 --- a/apps/webapp/app/presenters/ProjectPresenter.server.ts +++ b/apps/webapp/app/presenters/ProjectPresenter.server.ts @@ -30,6 +30,7 @@ export class ProjectPresenter { version: true, externalRef: true, environments: { + where: { archivedAt: null }, select: { id: true, slug: true, diff --git a/apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts b/apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts index b4b236a491..6d758f6b5a 100644 --- a/apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts @@ -43,24 +43,6 @@ export class DeploymentListPresenter { const project = await this.#prismaClient.project.findFirstOrThrow({ select: { id: true, - environments: { - select: { - id: true, - type: true, - slug: true, - orgMember: { - select: { - user: { - select: { - id: true, - name: true, - displayName: true, - }, - }, - }, - }, - }, - }, connectedGithubRepository: { select: { branchTracking: true, diff --git a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.apikeys.ts b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.apikeys.ts index f348731051..51bdb7879d 100644 --- a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.apikeys.ts +++ b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.apikeys.ts @@ -16,6 +16,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { }, include: { environments: { + where: { archivedAt: null }, select: { id: true, type: true, diff --git a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.concurrency.ts b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.concurrency.ts index de31a13736..4bde0ccaef 100644 --- a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.concurrency.ts +++ b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.concurrency.ts @@ -16,6 +16,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { }, include: { environments: { + where: { archivedAt: null }, select: { id: true, type: true, diff --git a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.environment-variables.ts b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.environment-variables.ts index 13a79aaad3..23208dda6a 100644 --- a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.environment-variables.ts +++ b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.environment-variables.ts @@ -16,6 +16,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { }, include: { environments: { + where: { archivedAt: null }, select: { id: true, type: true, diff --git a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.settings.ts b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.settings.ts index 0ac309bbeb..aef178d2e5 100644 --- a/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.settings.ts +++ b/apps/webapp/app/routes/orgs.$organizationSlug.projects.$projectParam.settings.ts @@ -16,6 +16,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { }, include: { environments: { + where: { archivedAt: null }, select: { id: true, type: true, From c0c5dd263ff797633a036186cc55012946e86624 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Wed, 12 Aug 2026 22:42:28 +0100 Subject: [PATCH 2/3] perf(webapp): also filter archived envs in the best-environment resolver Extends the archivedAt: null filter to SelectBestEnvironmentPresenter's own project env loads so the project-root default redirect resolves consistently with the section routes and never lands on an archived (dead) preview branch. --- .../app/presenters/SelectBestEnvironmentPresenter.server.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/webapp/app/presenters/SelectBestEnvironmentPresenter.server.ts b/apps/webapp/app/presenters/SelectBestEnvironmentPresenter.server.ts index 13d8691192..29559a87a3 100644 --- a/apps/webapp/app/presenters/SelectBestEnvironmentPresenter.server.ts +++ b/apps/webapp/app/presenters/SelectBestEnvironmentPresenter.server.ts @@ -46,6 +46,7 @@ export class SelectBestEnvironmentPresenter { include: { organization: true, environments: { + where: { archivedAt: null }, select: { id: true, type: true, @@ -71,6 +72,7 @@ export class SelectBestEnvironmentPresenter { include: { organization: true, environments: { + where: { archivedAt: null }, select: { id: true, type: true, From 74a4f5e50cf2c01854d285670f695af879953313 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Wed, 12 Aug 2026 22:50:54 +0100 Subject: [PATCH 3/3] perf(webapp): filter archived envs in the project index redirect loader The project _index route resolves the default environment via selectBestEnvironment from its own project env load; add archivedAt: null there too so every selectBestEnvironment caller is consistent and this loader gets the same bounding. --- .../route.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx index 91854efbc2..0874432fe5 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx @@ -17,6 +17,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { }, include: { environments: { + where: { archivedAt: null }, select: { id: true, type: true,