From e0e58f88140374c718828e839950bc5ed7be4a6a Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Tue, 29 Sep 2026 16:59:31 -0700 Subject: [PATCH] fix(studio): redirect moved dashboard routes (#51056) I added permanent redirects for the moved Dashboard routes that still send visitors to 404s. Project and organization identifiers carry through, while the old project billing path opens the organization picker for billing. **Note:** The bare `/dashboard/project` path redirects straight to Organizations instead of the `/dashboard/projects` hop named in GROWTH-1295, since `/projects` already redirects there. ## To test Tested on the Studio preview: - [x] Requested the eight old Dashboard paths in GROWTH-1295 while signed out. Each returned 308 with the specified destination. - [ ] Request bare `/dashboard/project` while signed out on the latest preview. Expect a single 308 to `/dashboard/organizations`. - [x] Requested a project backup path with a query string. The destination kept the project ref and query string. - [x] Requested `/dashboard/project/_`. The project picker remained reachable. ## Linear - fixes GROWTH-1295 ## Summary by CodeRabbit * **Bug Fixes** * Added permanent redirects for legacy Studio routes covering account and project pages, backups, email templates, edge-function logs, secrets, and billing settings. * Redirects preserve incoming query parameters and URL fragments; the project selector remains unaffected. --- apps/studio/redirects.shared.test.ts | 38 ++++++++++++++++++++++++++++ apps/studio/redirects.shared.ts | 24 ++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/apps/studio/redirects.shared.test.ts b/apps/studio/redirects.shared.test.ts index 128645cc631..462efb2ef62 100644 --- a/apps/studio/redirects.shared.test.ts +++ b/apps/studio/redirects.shared.test.ts @@ -51,6 +51,44 @@ describe('preserveQueryAndHash', () => { }) }) +describe('matchRedirect moved Studio routes', () => { + const ref = 'abcdefghijklmnopqrst' + + it.each([ + [`/project/${ref}/database/backups`, `/project/${ref}/database/backups/scheduled`], + [`/project/${ref}/auth/emails`, `/project/${ref}/auth/templates`], + [`/project/${ref}/logs/edge-functions`, `/project/${ref}/logs/edge-functions-logs`], + [`/project/${ref}/settings/vault`, `/project/${ref}/integrations/vault/secrets`], + [`/project/${ref}/settings/billing`, '/org/_/billing'], + ['/account', '/account/me'], + ['/org/my-org/projects', '/org/my-org'], + ['/project', '/organizations'], + ])('redirects %s to %s', (pathname, destination) => { + expect(matchRedirect({ pathname, search: {}, isPlatform: true })).toEqual({ + destination, + permanent: true, + }) + }) + + it('preserves the incoming query and hash', () => { + expect( + matchRedirect({ + pathname: `/project/${ref}/database/backups`, + search: { source: 'bookmark' }, + hash: 'scheduled', + isPlatform: true, + }) + ).toEqual({ + destination: `/project/${ref}/database/backups/scheduled?source=bookmark#scheduled`, + permanent: true, + }) + }) + + it('does not intercept the project selector', () => { + expect(matchRedirect({ pathname: '/project/_', search: {}, isPlatform: true })).toBeNull() + }) +}) + describe('matchRedirect query/hash preservation', () => { it('redirects the legacy compute and disk route while preserving query and hash', () => { expect( diff --git a/apps/studio/redirects.shared.ts b/apps/studio/redirects.shared.ts index a0c920a2661..97c7b3b4e56 100644 --- a/apps/studio/redirects.shared.ts +++ b/apps/studio/redirects.shared.ts @@ -41,6 +41,30 @@ export const SELF_HOSTED_REDIRECTS: StudioRedirect[] = [ ] export const SHARED_REDIRECTS: StudioRedirect[] = [ + { source: '/project', destination: '/organizations', permanent: true }, + { source: '/account', destination: '/account/me', permanent: true }, + { source: '/org/:slug/projects', destination: '/org/:slug', permanent: true }, + { + source: '/project/:ref/database/backups', + destination: '/project/:ref/database/backups/scheduled', + permanent: true, + }, + { + source: '/project/:ref/auth/emails', + destination: '/project/:ref/auth/templates', + permanent: true, + }, + { + source: '/project/:ref/logs/edge-functions', + destination: '/project/:ref/logs/edge-functions-logs', + permanent: true, + }, + { + source: '/project/:ref/settings/vault', + destination: '/project/:ref/integrations/vault/secrets', + permanent: true, + }, + { source: '/project/:ref/settings/billing', destination: '/org/_/billing', permanent: true }, { source: '/project/:ref/auth', destination: '/project/:ref/auth/users', permanent: true }, { source: '/project/:ref/auth/advanced',