Edge Computing

Cloudflare Analytics Custom Dashboards: Complete Guide

Master Cloudflare Analytics custom dashboards with Workers Analytics. Learn to build powerful monitoring solutions for your edge infrastructure today.

· By PropTechUSA AI
26m
Read Time
5.0k
Words
5
Sections
13
Code Examples

When your edge infrastructure spans dozens of services and millions of requests, standard analytics views become insufficient. As technical leaders managing complex PropTech platforms, we need granular visibility into performance metrics, user behavior, and system health. Cloudflare Analytics offers powerful customization capabilities that most developers never fully leverage, leaving critical insights buried in data.

This comprehensive guide will transform how you approach monitoring and observability at the edge, giving you the tools to build dashboards that actually matter for your business.

Understanding Cloudflare Analytics Architecture

Cloudflare Analytics operates on a distributed data pipeline that processes billions of events daily across their global network. Unlike traditional analytics platforms that sample data, Cloudflare captures every request, providing complete visibility into your edge traffic.

The Analytics Data Flow

Every request through Cloudflare's network generates multiple data points that flow through their analytics pipeline:

  • Request metadata: HTTP status codes, response times, cache status
  • Geographic data: Country, city, data center location
  • Security events: Bot scores, firewall triggers, DDoS mitigation
  • Performance metrics: Time to first byte, total request duration
  • Custom dimensions: Worker execution data, custom headers

This data becomes available through multiple interfaces: the dashboard UI, GraphQL Analytics API, and Workers Analytics API. Each serves different use cases, with custom dashboards requiring deep integration with these APIs.

Analytics Data Retention and Granularity

Cloudflare maintains analytics data at different retention periods and granularities:

  • Real-time data: 1-minute granularity, available for 6 hours
  • Hourly aggregates: Available for 30 days
  • Daily aggregates: Available for 1 year
  • Raw logs: Available through Logpush for unlimited retention

Understanding these limitations shapes how you architect custom dashboards. For real-time monitoring, you'll work with high-granularity, short-retention data. For trend analysis, you'll need longer-term aggregates.

Workers Analytics Integration

Workers Analytics extends the standard analytics pipeline by allowing custom event tracking within your edge functions. This creates opportunities for business-specific metrics that traditional web analytics can't capture.

typescript
export default {

class="kw">async fetch(request: Request, env: Env, ctx: ExecutionContext) {

class="kw">const startTime = Date.now();

// Your application logic here

class="kw">const response = class="kw">await handleRequest(request);

// Custom analytics event

ctx.waitUntil(

fetch('https://api.cloudflare.com/client/v4/accounts/{account_id}/analytics/events', {

method: 'POST',

headers: {

'Authorization': Bearer ${env.CF_API_TOKEN},

'Content-Type': 'application/json'

},

body: JSON.stringify({

timestamp: new Date().toISOString(),

dimension1: 'property_search',

dimension2: request.cf?.city || 'unknown',

metric1: Date.now() - startTime,

metric2: response.status

})

})

);

class="kw">return response;

}

};

Building Custom Dashboard Foundations

Custom dashboards require a systematic approach to data collection, processing, and visualization. The foundation involves setting up proper data sources, defining key metrics, and establishing update mechanisms.

API Authentication and Setup

Cloudflare's Analytics API requires proper authentication and understanding of rate limits. Most custom dashboard implementations fail because they don't properly handle API constraints.

typescript
class CloudflareAnalytics {

private apiToken: string;

private accountId: string;

private zoneId: string;

constructor(config: AnalyticsConfig) {

this.apiToken = config.apiToken;

this.accountId = config.accountId;

this.zoneId = config.zoneId;

}

class="kw">async queryGraphQL(query: string, variables: Record<string, any> = {}) {

class="kw">const response = class="kw">await fetch(&#039;https://api.cloudflare.com/client/v4/graphql&#039;, {

method: &#039;POST&#039;,

headers: {

&#039;Authorization&#039;: Bearer ${this.apiToken},

&#039;Content-Type&#039;: &#039;application/json&#039;,

&#039;X-Auth-Email&#039;: process.env.CF_EMAIL || &#039;&#039;

},

body: JSON.stringify({ query, variables })

});

class="kw">if (!response.ok) {

throw new Error(GraphQL query failed: ${response.statusText});

}

class="kw">return response.json();

}

}

Defining Custom Metrics

Effective dashboards focus on metrics that drive business decisions. For PropTech applications, this might include property search performance, geographic user distribution, or booking conversion rates.

graphql
query PropertySearchMetrics($zoneTag: string!, $since: Time!, $until: Time!) {

viewer {

zones(filter: { zoneTag: $zoneTag }) {

httpRequests1hGroups(

filter: {

datetime_geq: $since

datetime_leq: $until

requestSource: "eyeball"

}

orderBy: [datetime_ASC]

limit: 1000

) {

dimensions {

datetime

clientCountryName

clientRequestPath

edgeResponseStatus

}

sum {

requests

bytes

}

avg {

edgeTimeToFirstByteMs

}

}

}

}

}

Real-Time Data Processing

Custom dashboards need efficient data processing to handle Cloudflare's high-volume analytics streams. This involves implementing proper caching, data aggregation, and update mechanisms.

typescript
class DashboardDataProcessor {

private cache = new Map<string, CachedMetric>();

private updateInterval = 60000; // 1 minute

class="kw">async processMetrics(rawData: CloudflareAnalyticsResponse) {

class="kw">const processed = {

totalRequests: this.aggregateRequests(rawData),

averageResponseTime: this.calculateAverageResponseTime(rawData),

topCountries: this.getTopCountries(rawData),

errorRates: this.calculateErrorRates(rawData),

cacheHitRatio: this.calculateCacheRatio(rawData)

};

// Update cache with processed metrics

this.cache.set(&#039;current_metrics&#039;, {

data: processed,

timestamp: Date.now(),

ttl: this.updateInterval

});

class="kw">return processed;

}

private aggregateRequests(data: CloudflareAnalyticsResponse): number {

class="kw">return data.viewer.zones[0].httpRequests1hGroups.reduce(

(total, group) => total + group.sum.requests, 0

);

}

private calculateAverageResponseTime(data: CloudflareAnalyticsResponse): number {

class="kw">const groups = data.viewer.zones[0].httpRequests1hGroups;

class="kw">const totalTime = groups.reduce(

(sum, group) => sum + (group.avg.edgeTimeToFirstByteMs * group.sum.requests), 0

);

class="kw">const totalRequests = groups.reduce(

(sum, group) => sum + group.sum.requests, 0

);

class="kw">return totalRequests > 0 ? totalTime / totalRequests : 0;

}

}

Advanced Dashboard Implementation

Building production-ready custom dashboards requires sophisticated data handling, visualization components, and real-time update mechanisms. This section covers advanced implementation patterns that scale with enterprise requirements.

Multi-Zone Analytics Aggregation

Enterprise PropTech platforms often manage multiple domains and zones. Custom dashboards need to aggregate metrics across these zones while maintaining granular visibility.

typescript
class MultiZoneAnalytics extends CloudflareAnalytics {

private zones: ZoneConfig[];

class="kw">async aggregateMultiZoneMetrics(timeRange: TimeRange): Promise<AggregatedMetrics> {

class="kw">const zonePromises = this.zones.map(class="kw">async (zone) => {

class="kw">const query =

query ZoneMetrics($zoneTag: string!, $since: Time!, $until: Time!) {

viewer {

zones(filter: { zoneTag: $zoneTag }) {

httpRequests1hGroups(

filter: {

datetime_geq: $since

datetime_leq: $until

}

limit: 10000

) {

dimensions {

datetime

clientCountryName

edgeResponseStatus

}

sum {

requests

bytes

cachedRequests

}

avg {

edgeTimeToFirstByteMs

}

}

}

}

}

;

class="kw">const result = class="kw">await this.queryGraphQL(query, {

zoneTag: zone.tag,

since: timeRange.start.toISOString(),

until: timeRange.end.toISOString()

});

class="kw">return {

zoneName: zone.name,

data: result.data.viewer.zones[0].httpRequests1hGroups

};

});

class="kw">const zoneResults = class="kw">await Promise.all(zonePromises);

class="kw">return this.mergeZoneMetrics(zoneResults);

}

private mergeZoneMetrics(zoneResults: ZoneMetrics[]): AggregatedMetrics {

// Complex aggregation logic class="kw">for combining zone data

class="kw">const merged = {

totalRequests: 0,

totalBytes: 0,

averageResponseTime: 0,

cacheHitRatio: 0,

errorRate: 0,

zoneBreakdown: new Map<string, ZoneMetrics>()

};

zoneResults.forEach(zone => {

class="kw">const zoneTotal = zone.data.reduce((sum, group) => {

class="kw">return {

requests: sum.requests + group.sum.requests,

bytes: sum.bytes + group.sum.bytes,

cachedRequests: sum.cachedRequests + group.sum.cachedRequests,

totalResponseTime: sum.totalResponseTime +

(group.avg.edgeTimeToFirstByteMs * group.sum.requests)

};

}, { requests: 0, bytes: 0, cachedRequests: 0, totalResponseTime: 0 });

merged.totalRequests += zoneTotal.requests;

merged.totalBytes += zoneTotal.bytes;

merged.zoneBreakdown.set(zone.zoneName, zoneTotal);

});

class="kw">return merged;

}

}

Workers Analytics Integration

Workers Analytics provides custom event tracking capabilities that enable business-specific metrics collection. This is particularly valuable for PropTech applications tracking user interactions, search queries, and booking flows.

typescript
// Worker script class="kw">for custom analytics export default {

class="kw">async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {

class="kw">const analytics = new WorkerAnalytics(env.ANALYTICS_TOKEN);

class="kw">const startTime = performance.now();

try {

// Extract business context from request

class="kw">const url = new URL(request.url);

class="kw">const userAgent = request.headers.get(&#039;user-agent&#039;) || &#039;&#039;;

class="kw">const referer = request.headers.get(&#039;referer&#039;) || &#039;&#039;;

// Handle the request

class="kw">const response = class="kw">await this.handlePropertySearch(request, env);

class="kw">const endTime = performance.now();

// Track custom business metrics

ctx.waitUntil(

analytics.trackEvent({

eventType: &#039;property_search&#039;,

searchQuery: url.searchParams.get(&#039;query&#039;) || &#039;&#039;,

location: url.searchParams.get(&#039;location&#039;) || &#039;&#039;,

priceRange: url.searchParams.get(&#039;price_range&#039;) || &#039;&#039;,

resultCount: parseInt(response.headers.get(&#039;x-result-count&#039;) || &#039;0&#039;),

responseTime: endTime - startTime,

userCountry: request.cf?.country || &#039;unknown&#039;,

userCity: request.cf?.city || &#039;unknown&#039;,

deviceType: this.detectDeviceType(userAgent),

timestamp: new Date().toISOString()

})

);

class="kw">return response;

} catch (error) {

// Track errors

ctx.waitUntil(

analytics.trackEvent({

eventType: &#039;search_error&#039;,

errorMessage: error.message,

responseTime: performance.now() - startTime,

timestamp: new Date().toISOString()

})

);

throw error;

}

},

class="kw">async handlePropertySearch(request: Request, env: Env): Promise<Response> {

// Your property search logic here

class="kw">const searchResults = class="kw">await this.queryPropertyDatabase(request, env);

class="kw">return new Response(JSON.stringify(searchResults), {

headers: {

&#039;content-type&#039;: &#039;application/json&#039;,

&#039;x-result-count&#039;: searchResults.length.toString()

}

});

}

};

class WorkerAnalytics {

constructor(private token: string) {}

class="kw">async trackEvent(event: CustomAnalyticsEvent): Promise<void> {

class="kw">await fetch(&#039;https://api.cloudflare.com/client/v4/accounts/{account_id}/workers/analytics/events&#039;, {

method: &#039;POST&#039;,

headers: {

&#039;Authorization&#039;: Bearer ${this.token},

&#039;Content-Type&#039;: &#039;application/json&#039;

},

body: JSON.stringify({

events: [event]

})

});

}

}

Dashboard Visualization Components

Custom dashboards require efficient rendering of complex data visualizations. This implementation shows how to build reusable components for common PropTech metrics.

typescript
class PropertyAnalyticsDashboard {

private analytics: MultiZoneAnalytics;

private updateInterval: NodeJS.Timeout;

constructor(analyticsClient: MultiZoneAnalytics) {

this.analytics = analyticsClient;

}

class="kw">async renderDashboard(): Promise<DashboardData> {

class="kw">const [trafficMetrics, searchMetrics, conversionMetrics] = class="kw">await Promise.all([

this.getTrafficMetrics(),

this.getSearchMetrics(),

this.getConversionMetrics()

]);

class="kw">return {

traffic: this.formatTrafficData(trafficMetrics),

searches: this.formatSearchData(searchMetrics),

conversions: this.formatConversionData(conversionMetrics),

lastUpdated: new Date().toISOString()

};

}

private class="kw">async getSearchMetrics(): Promise<SearchMetrics> {

class="kw">const query =

query SearchAnalytics($since: Time!, $until: Time!) {

viewer {

accounts(filter: { accountTag: "{account_id}" }) {

workersInvocationsAdaptive(

filter: {

datetime_geq: $since

datetime_leq: $until

scriptName: "property-search-worker"

}

limit: 1000

) {

dimensions {

datetime

status

}

sum {

requests

duration

}

}

}

}

}

;

class="kw">const timeRange = {

start: new Date(Date.now() - 24 60 60 * 1000), // 24 hours ago

end: new Date()

};

class="kw">const result = class="kw">await this.analytics.queryGraphQL(query, {

since: timeRange.start.toISOString(),

until: timeRange.end.toISOString()

});

class="kw">return this.processSearchMetrics(result.data.viewer.accounts[0].workersInvocationsAdaptive);

}

}

💡
Pro Tip
When building custom dashboards, implement proper error handling and fallback mechanisms. Cloudflare's APIs can experience brief outages, and your dashboard should gracefully handle these scenarios.

Best Practices and Optimization

Production custom dashboards require careful attention to performance, reliability, and maintainability. These best practices ensure your analytics infrastructure scales with your PropTech platform's growth.

Efficient Data Caching Strategies

Cloudflare Analytics APIs have rate limits and latency considerations. Implementing intelligent caching reduces API calls while maintaining data freshness for critical metrics.

typescript
class AnalyticsCache {

private redis: RedisClient;

private cacheTTL = {

realtime: 60, // 1 minute class="kw">for real-time data

hourly: 900, // 15 minutes class="kw">for hourly aggregates

daily: 3600, // 1 hour class="kw">for daily summaries

trends: 21600 // 6 hours class="kw">for trend analysis

};

class="kw">async getCachedMetrics(cacheKey: string, dataType: keyof typeof this.cacheTTL): Promise<any | null> {

try {

class="kw">const cached = class="kw">await this.redis.get(cacheKey);

class="kw">if (cached) {

class="kw">const parsed = JSON.parse(cached);

class="kw">if (Date.now() - parsed.timestamp < this.cacheTTL[dataType] * 1000) {

class="kw">return parsed.data;

}

}

} catch (error) {

console.warn(&#039;Cache read failed:&#039;, error);

}

class="kw">return null;

}

class="kw">async setCachedMetrics(cacheKey: string, data: any): Promise<void> {

try {

class="kw">await this.redis.set(cacheKey, JSON.stringify({

data,

timestamp: Date.now()

}));

} catch (error) {

console.warn(&#039;Cache write failed:&#039;, error);

}

}

}

Error Handling and Resilience

Robust dashboards handle API failures gracefully and provide meaningful feedback to users when data is unavailable.

typescript
class ResilientAnalyticsClient {

private maxRetries = 3;

private baseDelay = 1000;

class="kw">async queryWithRetry<T>(queryFn: () => Promise<T>): Promise<T | null> {

class="kw">let lastError: Error;

class="kw">for (class="kw">let attempt = 0; attempt < this.maxRetries; attempt++) {

try {

class="kw">return class="kw">await queryFn();

} catch (error) {

lastError = error as Error;

class="kw">if (this.isRetryableError(error)) {

class="kw">const delay = this.baseDelay * Math.pow(2, attempt);

class="kw">await new Promise(resolve => setTimeout(resolve, delay));

continue;

}

// Non-retryable error, fail immediately

break;

}

}

console.error(&#039;Analytics query failed after retries:&#039;, lastError);

class="kw">return null;

}

private isRetryableError(error: any): boolean {

class="kw">if (error.status >= 500) class="kw">return true; // Server errors

class="kw">if (error.status === 429) class="kw">return true; // Rate limiting

class="kw">if (error.code === &#039;ETIMEDOUT&#039;) class="kw">return true; // Timeouts

class="kw">return false;

}

}

Performance Monitoring

Monitor your custom dashboard's own performance to ensure it doesn't become a bottleneck. Track API response times, cache hit rates, and rendering performance.

typescript
class DashboardMetrics {

private metrics = {

apiResponseTimes: [] as number[],

cacheHitRate: 0,

errorRate: 0,

activeConnections: 0

};

trackApiCall(duration: number, success: boolean): void {

this.metrics.apiResponseTimes.push(duration);

class="kw">if (this.metrics.apiResponseTimes.length > 100) {

this.metrics.apiResponseTimes.shift();

}

class="kw">if (!success) {

this.metrics.errorRate = this.calculateErrorRate();

}

}

getPerformanceReport(): PerformanceReport {

class="kw">const responseTimes = this.metrics.apiResponseTimes;

class="kw">return {

averageApiResponseTime: responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length,

p95ApiResponseTime: this.percentile(responseTimes, 95),

cacheHitRate: this.metrics.cacheHitRate,

errorRate: this.metrics.errorRate,

activeConnections: this.metrics.activeConnections

};

}

}

⚠️
Warning
Be mindful of Cloudflare's API rate limits. Enterprise accounts have higher limits, but aggressive polling can still trigger throttling. Implement exponential backoff and respect rate limit headers.

Scaling and Production Deployment

Deploying custom Cloudflare Analytics dashboards in production environments requires careful consideration of architecture, monitoring, and maintenance strategies. This section covers enterprise-grade deployment patterns.

Microservices Architecture

Large-scale analytics dashboards benefit from a microservices approach, separating data collection, processing, and presentation concerns.

typescript
// Data collection service class AnalyticsCollectorService {

private queue: MessageQueue;

private collectors: Map<string, DataCollector>;

class="kw">async startCollection(): Promise<void> {

// Start multiple collectors class="kw">for different data sources

class="kw">const collectors = [

new CloudflareTrafficCollector(),

new WorkersAnalyticsCollector(),

new CustomEventsCollector()

];

collectors.forEach(collector => {

collector.on(&#039;data&#039;, (data) => {

this.queue.publish(&#039;analytics.raw&#039;, data);

});

collector.start();

});

}

}

// Data processing service class AnalyticsProcessorService {

class="kw">async processRawData(rawData: RawAnalyticsData): Promise<ProcessedMetrics> {

class="kw">const processors = [

new TrafficProcessor(),

new ConversionProcessor(),

new PerformanceProcessor()

];

class="kw">const processed = class="kw">await Promise.all(

processors.map(processor => processor.process(rawData))

);

class="kw">return this.combineMetrics(processed);

}

}

Real-Time Updates with WebSockets

Provide real-time dashboard updates using WebSocket connections, enabling immediate visibility into critical metrics changes.

typescript
class RealTimeDashboardServer {

private wss: WebSocketServer;

private clients = new Set<WebSocket>();

private metricsCache: DashboardMetrics;

constructor() {

this.wss = new WebSocketServer({ port: 8080 });

this.setupWebSocketHandlers();

this.startMetricsUpdates();

}

private setupWebSocketHandlers(): void {

this.wss.on(&#039;connection&#039;, (ws) => {

this.clients.add(ws);

// Send current metrics on connection

ws.send(JSON.stringify({

type: &#039;initial_data&#039;,

data: this.metricsCache.getCurrentMetrics()

}));

ws.on(&#039;close&#039;, () => {

this.clients.delete(ws);

});

});

}

private startMetricsUpdates(): void {

setInterval(class="kw">async () => {

try {

class="kw">const updatedMetrics = class="kw">await this.fetchLatestMetrics();

this.broadcastToClients({

type: &#039;metrics_update&#039;,

data: updatedMetrics,

timestamp: new Date().toISOString()

});

} catch (error) {

console.error(&#039;Failed to update metrics:&#039;, error);

}

}, 60000); // Update every minute

}

private broadcastToClients(message: any): void {

class="kw">const messageStr = JSON.stringify(message);

this.clients.forEach(client => {

class="kw">if (client.readyState === WebSocket.OPEN) {

client.send(messageStr);

}

});

}

}

At PropTechUSA.ai, we've implemented similar real-time analytics architectures for property management platforms, enabling property managers to monitor tenant portal usage, maintenance request flows, and booking patterns in real-time. This level of visibility has proven crucial for optimizing user experience and operational efficiency.

Automated Alerting and Monitoring

Implement intelligent alerting based on your custom metrics to proactively identify issues before they impact users.

typescript
class AlertingEngine {

private rules: AlertRule[];

private notificationChannels: NotificationChannel[];

class="kw">async evaluateMetrics(metrics: DashboardMetrics): Promise<void> {

class="kw">const triggeredAlerts = this.rules

.filter(rule => rule.evaluate(metrics))

.map(rule => this.createAlert(rule, metrics));

class="kw">if (triggeredAlerts.length > 0) {

class="kw">await this.sendAlerts(triggeredAlerts);

}

}

private createAlert(rule: AlertRule, metrics: DashboardMetrics): Alert {

class="kw">return {

id: generateId(),

ruleName: rule.name,

severity: rule.severity,

message: rule.generateMessage(metrics),

timestamp: new Date(),

metrics: rule.extractRelevantMetrics(metrics)

};

}

}

Custom Cloudflare Analytics dashboards represent a powerful opportunity to gain unprecedented visibility into your edge infrastructure performance. By implementing the patterns and practices outlined in this guide, you'll build monitoring solutions that scale with your PropTech platform's growth while providing actionable insights for technical and business decisions.

The combination of Cloudflare's comprehensive analytics APIs, Workers Analytics custom event tracking, and well-architected dashboard implementations creates a monitoring foundation that traditional analytics platforms simply cannot match. Whether you're optimizing property search performance, tracking user engagement across geographic regions, or monitoring conversion funnels, custom dashboards put the most relevant metrics at your fingertips.

Start by implementing basic dashboard components using the code examples provided, then gradually add advanced features like real-time updates, multi-zone aggregation, and intelligent alerting. Your future self will thank you when critical issues are identified and resolved before impacting your users' property search and booking experiences.

Need This Built?
We build production-grade systems with the exact tech covered in this article.
Start Your Project
PT
PropTechUSA.ai Engineering
Technical Content
Deep technical content from the team building production systems with Cloudflare Workers, AI APIs, and modern web infrastructure.