From b4922199cd22f91da3da8d561d3c84484137f3ec Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Tue, 16 Sep 2025 13:03:13 +0200 Subject: [PATCH 1/2] unblock cms draft mode (#38409) * remove port from cms start script * address cors * 30s revalidation on blog index * fix types * remove duplicate cache strategy * disable graphql * fix cms build * fix ProductDropdown crash * fix env var turbo --- apps/cms/next.config.mjs | 4 - apps/cms/package.json | 2 +- .../(payload)/api/graphql-playground/route.ts | 7 - .../src/app/(payload)/api/graphql/route.ts | 8 -- apps/cms/src/payload.config.ts | 2 +- apps/www/app/api-v2/cms-posts/route.ts | 135 ++++++++++++------ apps/www/app/api-v2/cms/revalidate/route.ts | 45 +++--- apps/www/app/api-v2/ticket-og/route.tsx | 2 +- apps/www/app/blog/[slug]/page.tsx | 2 +- .../app/blog/categories/[category]/page.tsx | 1 + apps/www/app/blog/tags/[tag]/page.tsx | 1 + apps/www/components/Nav/ProductDropdown.tsx | 2 - apps/www/lib/constants.ts | 4 +- apps/www/lib/get-cms-posts.tsx | 8 +- apps/www/pages/launch-week/6/index.tsx | 2 +- .../6/types6.d.ts => types/launch-week-6.ts} | 0 turbo.json | 1 + 17 files changed, 128 insertions(+), 98 deletions(-) delete mode 100644 apps/cms/src/app/(payload)/api/graphql-playground/route.ts delete mode 100644 apps/cms/src/app/(payload)/api/graphql/route.ts rename apps/www/{pages/launch-week/6/types6.d.ts => types/launch-week-6.ts} (100%) diff --git a/apps/cms/next.config.mjs b/apps/cms/next.config.mjs index 3317297c825..092c737bfb8 100644 --- a/apps/cms/next.config.mjs +++ b/apps/cms/next.config.mjs @@ -47,10 +47,6 @@ const nextConfig = { // We are already running linting via GH action, this will skip linting during production build on Vercel ignoreDuringBuilds: true, }, - experimental: { - // Ensure compatibility with Turbopack and Sharp - serverComponentsExternalPackages: ['sharp'], - }, // Configure Sharp as an external package for server-side rendering serverExternalPackages: ['sharp'], } diff --git a/apps/cms/package.json b/apps/cms/package.json index 13e5841a04e..b4d5728204f 100644 --- a/apps/cms/package.json +++ b/apps/cms/package.json @@ -15,7 +15,7 @@ "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint", "migrate": "cross-env NODE_OPTIONS=--no-deprecation tsx scripts/migrate.ts", "payload": "cross-env NODE_OPTIONS=--no-deprecation payload", - "start": "cross-env NODE_OPTIONS=--no-deprecation next start --port 3030", + "start": "cross-env NODE_OPTIONS=--no-deprecation next start", "vercel-build": "pnpm migrate && pnpm build", "typecheck_IGNORED": "tsc --noEmit" }, diff --git a/apps/cms/src/app/(payload)/api/graphql-playground/route.ts b/apps/cms/src/app/(payload)/api/graphql-playground/route.ts deleted file mode 100644 index 17d2954ca2d..00000000000 --- a/apps/cms/src/app/(payload)/api/graphql-playground/route.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ -/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ -import config from '@payload-config' -import '@payloadcms/next/css' -import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes' - -export const GET = GRAPHQL_PLAYGROUND_GET(config) diff --git a/apps/cms/src/app/(payload)/api/graphql/route.ts b/apps/cms/src/app/(payload)/api/graphql/route.ts deleted file mode 100644 index 2069ff86b0a..00000000000 --- a/apps/cms/src/app/(payload)/api/graphql/route.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ -/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ -import config from '@payload-config' -import { GRAPHQL_POST, REST_OPTIONS } from '@payloadcms/next/routes' - -export const POST = GRAPHQL_POST(config) - -export const OPTIONS = REST_OPTIONS(config) diff --git a/apps/cms/src/payload.config.ts b/apps/cms/src/payload.config.ts index 8858ce58a5e..8e7a31c40d2 100644 --- a/apps/cms/src/payload.config.ts +++ b/apps/cms/src/payload.config.ts @@ -102,7 +102,7 @@ export default buildConfig({ // Global configuration for better performance globals: [], graphQL: { - disable: process.env.NODE_ENV !== 'development', // Disable GraphQL in production for better performance + disable: true, }, // Reduce payload init overhead telemetry: false, diff --git a/apps/www/app/api-v2/cms-posts/route.ts b/apps/www/app/api-v2/cms-posts/route.ts index 2243cab04db..04f64cf7680 100644 --- a/apps/www/app/api-v2/cms-posts/route.ts +++ b/apps/www/app/api-v2/cms-posts/route.ts @@ -6,6 +6,12 @@ import { generateReadingTime } from '~/lib/helpers' // Lightweight runtime for better performance export const runtime = 'edge' +// CORS headers for cross-origin requests +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +} + // Lightweight TOC generation for edge runtime type TocItem = { content: string; slug: string; lvl: number } @@ -115,6 +121,14 @@ function convertRichTextToMarkdown(content: any): string { .join('\n\n') } +// Handle preflight requests +export async function OPTIONS() { + return new Response(null, { + status: 200, + headers: corsHeaders, + }) +} + export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) @@ -212,12 +226,15 @@ export async function GET(request: NextRequest) { _status: latestVersion._status, } - return NextResponse.json({ - success: true, - post: processedPost, - mode, - isDraft: shouldFetchDraft, - }) + return NextResponse.json( + { + success: true, + post: processedPost, + mode, + isDraft: shouldFetchDraft, + }, + { headers: corsHeaders } + ) } } } @@ -289,12 +306,15 @@ export async function GET(request: NextRequest) { _status: post._status, } - return NextResponse.json({ - success: true, - post: processedPost, - mode, - isDraft: shouldFetchDraft, - }) + return NextResponse.json( + { + success: true, + post: processedPost, + mode, + isDraft: shouldFetchDraft, + }, + { headers: corsHeaders } + ) } } @@ -364,12 +384,15 @@ export async function GET(request: NextRequest) { _status: post._status, } - return NextResponse.json({ - success: true, - post: processedPost, - mode, - isDraft: shouldFetchDraft, - }) + return NextResponse.json( + { + success: true, + post: processedPost, + mode, + isDraft: shouldFetchDraft, + }, + { headers: corsHeaders } + ) } } } @@ -452,16 +475,21 @@ export async function GET(request: NextRequest) { toc_depth: post.toc_depth || 3, } - return NextResponse.json({ - success: true, - post: processedPost, - mode, - source: 'versions-api', - }) + return NextResponse.json( + { + success: true, + post: processedPost, + mode, + source: 'versions-api', + }, + { headers: corsHeaders } + ) } } } else { - console.log('[cms-posts] Versions API failed, response:', await versionsResponse.text()) + if (process.env.NODE_ENV !== 'production') { + console.log('[cms-posts] Versions API failed, response:', await versionsResponse.text()) + } } // Strategy 2: If versions API didn't work, try finding the parent post first, then get its latest published version @@ -556,12 +584,15 @@ export async function GET(request: NextRequest) { richContent: mode === 'full' ? post.content : undefined, } - return NextResponse.json({ - success: true, - post: processedPost, - mode, - source: 'versions-by-parent-api', - }) + return NextResponse.json( + { + success: true, + post: processedPost, + mode, + source: 'versions-by-parent-api', + }, + { headers: corsHeaders } + ) } } } @@ -590,6 +621,12 @@ export async function GET(request: NextRequest) { // For individual post requests, don't cache to ensure fresh data cache: slug ? 'no-store' : 'default', next: slug ? undefined : { revalidate: 300 }, + // Add SSL configuration for production + ...(process.env.NODE_ENV === 'production' && { + // Allow self-signed certificates in development, but use proper SSL in production + // This helps with Vercel's internal networking + agent: false, + }), }) if (!response.ok) { @@ -600,7 +637,7 @@ export async function GET(request: NextRequest) { error: 'Failed to fetch posts from CMS', status: response.status, }, - { status: response.status } + { status: response.status, headers: corsHeaders } ) } @@ -613,7 +650,7 @@ export async function GET(request: NextRequest) { error: 'CMS returned non-JSON response', contentType, }, - { status: 502 } + { status: 502, headers: corsHeaders } ) } @@ -695,20 +732,26 @@ export async function GET(request: NextRequest) { // For single post requests, return the post directly if (slug && posts.length > 0) { - return NextResponse.json({ - success: true, - post: posts[0], - mode, - }) + return NextResponse.json( + { + success: true, + post: posts[0], + mode, + }, + { headers: corsHeaders } + ) } - return NextResponse.json({ - success: true, - posts, - total: posts.length, - mode, - cached: true, - }) + return NextResponse.json( + { + success: true, + posts, + total: posts.length, + mode, + cached: true, + }, + { headers: corsHeaders } + ) } catch (error) { console.error('[cms-posts] Error:', error) return NextResponse.json( @@ -717,7 +760,7 @@ export async function GET(request: NextRequest) { error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error', }, - { status: 500 } + { status: 500, headers: corsHeaders } ) } } diff --git a/apps/www/app/api-v2/cms/revalidate/route.ts b/apps/www/app/api-v2/cms/revalidate/route.ts index b743c4870dd..3ff73f9e8d8 100644 --- a/apps/www/app/api-v2/cms/revalidate/route.ts +++ b/apps/www/app/api-v2/cms/revalidate/route.ts @@ -1,27 +1,32 @@ -import type { NextApiRequest, NextApiResponse } from 'next' - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - // Check for secret to confirm this is a valid request - if (req.query.secret !== process.env.CMS_PREVIEW_SECRET) { - return res.status(401).json({ message: 'Invalid token' }) - } - - const { path } = req.query - - if (!path) { - return res.status(400).json({ message: 'Missing path parameter' }) - } +import { revalidatePath } from 'next/cache' +import { NextRequest, NextResponse } from 'next/server' +export async function POST(request: NextRequest) { try { - // This will revalidate the specific page - await res.revalidate(String(path)) + const body = await request.json() + const { secret, path } = body - return res.json({ revalidated: true, path }) + // Check for secret to confirm this is a valid request + if (secret !== process.env.CMS_PREVIEW_SECRET) { + return NextResponse.json({ message: 'Invalid token' }, { status: 401 }) + } + + if (!path) { + return NextResponse.json({ message: 'Missing path parameter' }, { status: 400 }) + } + + // This will revalidate the specific page + revalidatePath(path) + + return NextResponse.json({ revalidated: true, path }) } catch (error) { console.error('[Revalidate API] Error during revalidation:', error) - return res.status(500).json({ - message: 'Error revalidating', - error: error instanceof Error ? error.message : 'Unknown error', - }) + return NextResponse.json( + { + message: 'Error revalidating', + error: error instanceof Error ? error.message : 'Unknown error', + }, + { status: 500 } + ) } } diff --git a/apps/www/app/api-v2/ticket-og/route.tsx b/apps/www/app/api-v2/ticket-og/route.tsx index 6c9ba7807a3..46b216f66f4 100644 --- a/apps/www/app/api-v2/ticket-og/route.tsx +++ b/apps/www/app/api-v2/ticket-og/route.tsx @@ -24,7 +24,7 @@ const FONT_URLS = { const LW_TABLE = 'tickets' const LW_MATERIALIZED_VIEW = 'tickets_view' -export async function GET(req: Request, res: Response) { +export async function GET(req: Request) { const url = new URL(req.url) // Just here to silence snyk false positives diff --git a/apps/www/app/blog/[slug]/page.tsx b/apps/www/app/blog/[slug]/page.tsx index 7f142ae4026..dd3ce29f66f 100644 --- a/apps/www/app/blog/[slug]/page.tsx +++ b/apps/www/app/blog/[slug]/page.tsx @@ -28,7 +28,7 @@ async function getCMSPostFromAPI( const fetchOptions = isDraft ? { // For draft mode: always fresh data, no caching - cache: 'no-store' as const, + // cache: 'no-store' as const, next: { revalidate: 0 }, } : { diff --git a/apps/www/app/blog/categories/[category]/page.tsx b/apps/www/app/blog/categories/[category]/page.tsx index edafaa817a4..b0c8f9ab3be 100644 --- a/apps/www/app/blog/categories/[category]/page.tsx +++ b/apps/www/app/blog/categories/[category]/page.tsx @@ -15,6 +15,7 @@ export async function generateStaticParams() { return categories.map((category: string) => ({ category })) } +export const revalidate = 30 export const dynamic = 'force-static' export async function generateMetadata({ params }: { params: Params }): Promise { diff --git a/apps/www/app/blog/tags/[tag]/page.tsx b/apps/www/app/blog/tags/[tag]/page.tsx index ca270ca9d63..edda447f4a1 100644 --- a/apps/www/app/blog/tags/[tag]/page.tsx +++ b/apps/www/app/blog/tags/[tag]/page.tsx @@ -15,6 +15,7 @@ export async function generateStaticParams() { return tags.map((tag: string) => ({ tag })) } +export const revalidate = 30 export const dynamic = 'force-static' export async function generateMetadata({ params }: { params: Params }): Promise { diff --git a/apps/www/components/Nav/ProductDropdown.tsx b/apps/www/components/Nav/ProductDropdown.tsx index 85207759b10..83e0e586a76 100644 --- a/apps/www/components/Nav/ProductDropdown.tsx +++ b/apps/www/components/Nav/ProductDropdown.tsx @@ -14,10 +14,8 @@ import ComparisonsData from 'data/Comparisons' import CustomersData from 'data/CustomerStories' import MainProductsData from 'data/MainProducts' import ProductModulesData from 'data/ProductModules' -import { useRouter } from 'next/router' export const ProductDropdown = () => { - const { basePath } = useRouter() const isTablet = useBreakpoint(1279) return ( diff --git a/apps/www/lib/constants.ts b/apps/www/lib/constants.ts index b77ad67d3ea..61a17e59bbb 100644 --- a/apps/www/lib/constants.ts +++ b/apps/www/lib/constants.ts @@ -34,8 +34,8 @@ export const SITE_ORIGIN = export const CMS_SITE_ORIGIN = process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' - ? // In production, require env or fall back to localhost to avoid hitting supabase.com - 'http://localhost:3030' + ? // In production, use the actual CMS domain + process.env.CMS_SITE_ORIGIN || 'https://cms.supabase.com' : process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL && typeof process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL === 'string' ? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL.replace('zone-www-dot-com-git-', 'cms-git-')}` diff --git a/apps/www/lib/get-cms-posts.tsx b/apps/www/lib/get-cms-posts.tsx index 6507b71dd27..01dd212eeb8 100644 --- a/apps/www/lib/get-cms-posts.tsx +++ b/apps/www/lib/get-cms-posts.tsx @@ -288,7 +288,7 @@ export async function getCMSPostBySlug(slug: string, preview = false) { 'Content-Type': 'application/json', ...(CMS_API_KEY && { Authorization: `Bearer ${CMS_API_KEY}` }), }, - cache: 'no-store', + // cache: 'no-store', next: { revalidate: 0 }, }) @@ -300,7 +300,7 @@ export async function getCMSPostBySlug(slug: string, preview = false) { 'Content-Type': 'application/json', ...(CMS_API_KEY && { Authorization: `Bearer ${CMS_API_KEY}` }), }, - cache: 'no-store', + // cache: 'no-store', next: { revalidate: 0 }, }) } @@ -490,8 +490,8 @@ export async function getAllCMSPosts({ 'Content-Type': 'application/json', ...(CMS_API_KEY && { Authorization: `Bearer ${CMS_API_KEY}` }), }, - cache: 'no-store', // Ensure we always get fresh data - next: { revalidate: 0 }, // Disable caching for this fetch + // cache: 'no-store', // Ensure we always get fresh data + next: { revalidate: 30 }, // Disable caching for this fetch } ) diff --git a/apps/www/pages/launch-week/6/index.tsx b/apps/www/pages/launch-week/6/index.tsx index f7a2e94ea4a..22989cff44d 100644 --- a/apps/www/pages/launch-week/6/index.tsx +++ b/apps/www/pages/launch-week/6/index.tsx @@ -15,7 +15,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { Accordion, Badge } from 'ui' import { SITE_ORIGIN } from '~/lib/constants' -import { WeekDayProps } from './types6' +import type { WeekDayProps } from '~/types/launch-week-6' import styles from './styles/launchWeek6.module.css' import styleUtils from './styles/utils6.module.css' diff --git a/apps/www/pages/launch-week/6/types6.d.ts b/apps/www/types/launch-week-6.ts similarity index 100% rename from apps/www/pages/launch-week/6/types6.d.ts rename to apps/www/types/launch-week-6.ts diff --git a/turbo.json b/turbo.json index b1b8c12160f..64a448b08ab 100644 --- a/turbo.json +++ b/turbo.json @@ -116,6 +116,7 @@ "ANALYZE", "CMS_API_KEY", "CMS_PREVIEW_SECRET", + "CMS_SITE_ORIGIN", "NEXT_PUBLIC_MISC_USE_URL", "NEXT_PUBLIC_MISC_USE_ANON_KEY", "NEXT_PUBLIC_STUDIO_URL", From 83122eb4c620d1c56428152193b6fed272dd911a Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Tue, 16 Sep 2025 13:58:55 +0200 Subject: [PATCH 2/2] fix query performance links (#38738) * fix links * fix links * rm nextenv * fix formatting changes --- .../troubleshooting/all-about-supabase-egress-a_Sg_e.mdx | 2 +- .../canceling-statement-due-to-statement-timeout-581wFv.mdx | 2 +- ...ficient-privilege-when-accessing-pgstatstatements-e5M_EQ.mdx | 2 +- .../components/layouts/ReportsLayout/Reports.Commands.tsx | 2 +- apps/studio/next.config.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/docs/content/troubleshooting/all-about-supabase-egress-a_Sg_e.mdx b/apps/docs/content/troubleshooting/all-about-supabase-egress-a_Sg_e.mdx index a5b41236001..00bd84d90a8 100644 --- a/apps/docs/content/troubleshooting/all-about-supabase-egress-a_Sg_e.mdx +++ b/apps/docs/content/troubleshooting/all-about-supabase-egress-a_Sg_e.mdx @@ -20,7 +20,7 @@ You can read about Unified egress, included quota, and how to check the egress u While pointing out the exact cause for egress may not be straightforward, there are various steps you can take to determine the source of these issues: - Picking the "Top Paths" from the [log explorer](https://app.supabase.com/project/_/logs/explorer/templates) will help you identify heavily queried paths -- By finding the most requested queries from Query performance report: https://app.supabase.com/project/_/reports/query-performance +- By finding the most requested queries from Query performance report: https://app.supabase.com/project/_/advisors/query-performance - Supavisor Egress is independent of client. There is no direct relation between a single query and Supavisor egress, it is harder to debug and identify. But you can make use of frequent queries from the link in above step that also displays average number of rows which will help to identify queries with a large number of rows returned. While this does not display Supavisor queries specifically, it will give an overview of queries with lots of rows that can help. - For Storage Egress, all outgoing traffic for storage-related requests to download/view your Storage items are considered as Storage egress. We have a "Storage Egress Requests" template in logs explorer that you can use to get the number of requests for each Storage object - If you pull 1mb of data out of the database using the Supavisor connection in your edge function, but only sends 100kb back to the user, you will have the Egress from the Supavisor to your Edge function plus from the edge function to your user diff --git a/apps/docs/content/troubleshooting/canceling-statement-due-to-statement-timeout-581wFv.mdx b/apps/docs/content/troubleshooting/canceling-statement-due-to-statement-timeout-581wFv.mdx index 29fd5c7f98d..5986805ee2c 100644 --- a/apps/docs/content/troubleshooting/canceling-statement-due-to-statement-timeout-581wFv.mdx +++ b/apps/docs/content/troubleshooting/canceling-statement-due-to-statement-timeout-581wFv.mdx @@ -13,6 +13,6 @@ You can run this query to check the current settings set for your roles: `SELECT To increase the `statement_timeout` for a specific role, you may follow the instructions [here](/docs/guides/database/timeouts#changing-the-default-timeout). Note that it may require a quick reboot for the changes to take effect. -Additionally, to check how long a query is taking, you can check the Query Performance report which can give you more information on the query's performance: https://app.supabase.com/project/_/reports/query-performance. You can use the [query plan analyzer](https://www.postgresql.org/docs/current/sql-explain.html) on any expensive queries that you have identified: `explain analyze ;`. For supabase-js/ PostgREST queries you can use `.explain()`. +Additionally, to check how long a query is taking, you can check the Query Performance report which can give you more information on the query's performance: https://app.supabase.com/project/_/advisors/query-performance. You can use the [query plan analyzer](https://www.postgresql.org/docs/current/sql-explain.html) on any expensive queries that you have identified: `explain analyze ;`. For supabase-js/ PostgREST queries you can use `.explain()`. You can also make use of Postgres logs that will give you useful information like when the query was executed: https://app.supabase.com/project/_/logs/postgres-logs. diff --git a/apps/docs/content/troubleshooting/insufficient-privilege-when-accessing-pgstatstatements-e5M_EQ.mdx b/apps/docs/content/troubleshooting/insufficient-privilege-when-accessing-pgstatstatements-e5M_EQ.mdx index f0e4bd28065..14ce81ba1ba 100644 --- a/apps/docs/content/troubleshooting/insufficient-privilege-when-accessing-pgstatstatements-e5M_EQ.mdx +++ b/apps/docs/content/troubleshooting/insufficient-privilege-when-accessing-pgstatstatements-e5M_EQ.mdx @@ -10,7 +10,7 @@ database_id = "1da315be-38ac-487a-b178-f865a1a73c09" message = "insufficient privilege" --- -If you see the error "insufficient privilege" when accessing [pg_stat_statements](/docs/guides/platform/performance#postgres-cumulative-statistics-system) or when accessing [Query Performance Report](/dashboard/project/_/reports/query-performance), it means that the Postgres role does not have required permissions. +If you see the error "insufficient privilege" when accessing [pg_stat_statements](/docs/guides/platform/performance#postgres-cumulative-statistics-system) or when accessing [Query Performance Report](/dashboard/project/_/advisors/query-performance), it means that the Postgres role does not have required permissions. In this case, you can run the below command to allow the Postgres role to read all statistics from the system: diff --git a/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx b/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx index e1bc9f7b152..da329451b08 100644 --- a/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx +++ b/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx @@ -41,7 +41,7 @@ export function useReportsGotoCommands(options?: CommandOptions) { { id: 'nav-reports-query-performance', name: 'Query Performance Reports', - route: `/project/${ref}/reports/query-performance`, + route: `/project/${ref}/advisors/query-performance`, defaultHidden: true, }, ] diff --git a/apps/studio/next.config.js b/apps/studio/next.config.js index 4645351dbf4..c3846e9c7ca 100644 --- a/apps/studio/next.config.js +++ b/apps/studio/next.config.js @@ -304,7 +304,7 @@ const nextConfig = { }, { permanent: true, - source: '/project/:ref/reports/query-performance', + source: '/project/:ref/query-performance', destination: '/project/:ref/advisors/query-performance', }, {