diff --git a/CHANGELOG.md b/CHANGELOG.md index 2548d26..eb0eafb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added +- **Dedicated Performance tab.** Core Web Vitals (LCP, CLS, INP) have moved from the main dashboard into their own "Performance" tab. This gives you a full-page view with your overall performance score, individual metric cards, and a "Slowest pages by metric" table you can sort by LCP, CLS, or INP. The tab includes its own date range picker so you can analyze performance trends independently. + - **BunnyCDN integration.** Connect your BunnyCDN account in Settings > Integrations to monitor your CDN performance right alongside your analytics. A new "CDN" tab on your dashboard shows total bandwidth served, request volume, cache hit rate, origin response time, and error counts — each with percentage changes compared to the previous period. Charts show bandwidth trends (total vs cached), daily request volume, and error breakdowns over time. A geographic breakdown shows which countries consume the most bandwidth. Pulse only stores your API key encrypted and only reads statistics — it never modifies anything in your BunnyCDN account. You can disconnect and fully remove all CDN data at any time. - **Smart pull zone matching.** When connecting BunnyCDN, Pulse automatically filters your pull zones to only show the ones that match your tracked site's domain — so you can't accidentally connect the wrong pull zone. @@ -20,6 +22,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Fixed +- **Performance metrics no longer show "0 0 0" when no data exists.** Previously, if no visitors had reported Web Vitals data, the Performance section showed "LCP 0 ms, CLS 0, INP 0 ms" and rated everything as "Good" — which was misleading. It now clearly says "No data" when no metrics have been collected, and shows a helpful message explaining when data will appear. +- **Performance metrics no longer show inflated numbers from slow outliers.** A single very slow page load could skew the entire site's LCP or INP average to unrealistically high values. Pulse now uses the 75th percentile (p75) — the same methodology Google uses — so a handful of extreme outliers don't distort your scores. + - **BunnyCDN logo now displays correctly.** The BunnyCDN integration card in Settings previously showed a generic globe icon. It now shows the proper BunnyCDN bunny logo. - **Your BunnyCDN API key is no longer visible in network URLs.** When loading pull zones, the API key was previously sent as a URL parameter. It's now sent securely in the request body, just like when connecting. diff --git a/app/sites/[id]/page.tsx b/app/sites/[id]/page.tsx index bb3eae8..be5b210 100644 --- a/app/sites/[id]/page.tsx +++ b/app/sites/[id]/page.tsx @@ -5,7 +5,6 @@ import { logger } from '@/lib/utils/logger' import { useCallback, useEffect, useRef, useState, useMemo } from 'react' import { useParams, useRouter, useSearchParams } from 'next/navigation' import { - getPerformanceByPage, getTopPages, getTopReferrers, getCountries, @@ -32,7 +31,6 @@ import TopReferrers from '@/components/dashboard/TopReferrers' import Locations from '@/components/dashboard/Locations' import TechSpecs from '@/components/dashboard/TechSpecs' -const PerformanceStats = dynamic(() => import('@/components/dashboard/PerformanceStats')) const GoalStats = dynamic(() => import('@/components/dashboard/GoalStats')) const Campaigns = dynamic(() => import('@/components/dashboard/Campaigns')) const PeakHours = dynamic(() => import('@/components/dashboard/PeakHours')) @@ -556,20 +554,6 @@ export default function SiteDashboardPage() { /> - {/* Performance Stats - Only show if enabled */} - {site.enable_performance_insights && ( -
- -
- )} -
import('@/components/dashboard/PerformanceStats')) + +function getThisWeekRange(): { start: string; end: string } { + const today = new Date() + const dayOfWeek = today.getDay() + const monday = new Date(today) + monday.setDate(today.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1)) + return { start: formatDate(monday), end: formatDate(today) } +} + +function getThisMonthRange(): { start: string; end: string } { + const today = new Date() + const firstOfMonth = new Date(today.getFullYear(), today.getMonth(), 1) + return { start: formatDate(firstOfMonth), end: formatDate(today) } +} + +function PerformanceSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+ {[1, 2, 3].map(i => ( +
+ ))} +
+
+
+ ) +} + +export default function PerformancePage() { + const params = useParams() + const siteId = params.id as string + + const [period, setPeriod] = useState('30') + const [dateRange, setDateRange] = useState(() => getDateRange(30)) + const [isDatePickerOpen, setIsDatePickerOpen] = useState(false) + + const { data: dashboard, isLoading: loading } = useDashboard(siteId, dateRange.start, dateRange.end) + + const site = dashboard?.site ?? null + const showSkeleton = useMinimumLoading(loading && !dashboard) + const fadeClass = useSkeletonFade(showSkeleton) + + useEffect(() => { + const domain = site?.domain + document.title = domain ? `Performance \u00b7 ${domain} | Pulse` : 'Performance | Pulse' + }, [site?.domain]) + + if (showSkeleton) return + + if (site && !site.enable_performance_insights) { + return ( +
+
+

+ Performance insights are disabled +

+

+ Enable performance insights in your site settings to start collecting Core Web Vitals data. +

+
+
+ ) + } + + return ( +
+ {/* Header */} +
+
+

+ Performance +

+

+ Core Web Vitals from real user sessions +

+
+ - )} -
- - {loadingTable ? ( -
- ) : rows.length === 0 ? ( -
- No per-page metrics yet. Data appears as visitors are tracked with performance insights enabled. -
- ) : ( -
- - - - - - - - - - - - {rows.map((r) => ( - - - - - - - - ))} - -
PathSamplesLCPCLSINP
- {r.path || '/'} - {r.samples} - {formatMetric('lcp', r.lcp)} - - {formatMetric('cls', r.cls)} - - {formatMetric('inp', r.inp)} -
-
- )} -
-
+ {!hasData && ( +
+ No performance data collected yet. Core Web Vitals data will appear here once visitors browse your site with performance insights enabled.
- + )} + + {hasData && ( +
+ * 75th percentile (p75) calculated from real user sessions. Lower is better. +
+ )} + + {/* Worst pages by metric */} +
+
+

+ Slowest pages by metric +

+ {canRefetch && ( +