Merge remote-tracking branch 'origin/master' into feat/query-perf-labels-columns-update

This commit is contained in:
kemal committed 2025-09-16 13:10:33 +01:00
commit 032ef93721
22 files changed
+133 -103

No files matched your search

-4
View File
@@ -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'],
}
+1 -1
View File
@@ -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"
},
@@ -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)
@@ -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)
+1 -1
View File
@@ -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,
@@ -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
@@ -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 <query-statement-here>;`. 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 <query-statement-here>;`. 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.
@@ -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:
@@ -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,
},
]
+1 -1
View File
@@ -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',
},
{
+89 -46
View File
@@ -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 }
)
}
}
+25 -20
View File
@@ -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 }
)
}
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 },
}
: {
@@ -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<Metadata> {
+1
View File
@@ -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<Metadata> {
@@ -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 (
+2 -2
View File
@@ -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-')}`
+4 -4
View File
@@ -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
}
)
+1 -1
View File
@@ -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'
File renamed without changes.
+1
View File
@@ -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",