Skip to content
Dashboard

Building the Black Friday-Cyber Monday live dashboard

Software Engineer

Link to headingDashboard architecture

High-level BFCM dashboard architectureHigh-level BFCM dashboard architecture
High-level BFCM dashboard architecture

Link to headingBackend optimizations

Link to headingCost-efficiency: Handling expensive queries with a Vercel Marketplace Integration

SELECT count() FROM requests WHERE timestamp > '2024-11-29 00:00:00'

Initial SQL Query

SELECT count() FROM requests WHERE timestamp > now() - INTERVAL 5 MINUTE

Updated SQL query with rolling window

Using Upstash KV to track totals with incremental updatesUsing Upstash KV to track totals with incremental updates
Using Upstash KV to track totals with incremental updates

Link to headingSpeed: Using Incremental Static Regeneration (ISR)

Caching server responses to save database callsCaching server responses to save database calls
Caching server responses to save database calls
page.tsx
// Tell Next.js to render the page as a static page, despite having fetch calls
export const dynamic = 'force-static';
// Invalidate the cache after 5 seconds
export const revalidate = 5;
export default function Page() { ... }

Rendering the page as a static page and setting time based revalidation

Using ISR to cache pages for all usersUsing ISR to cache pages for all users
Using ISR to cache pages for all users
Completed backend architectureCompleted backend architecture
Completed backend architecture

Link to headingFrontend optimizations

Initial counting animationInitial counting animation
Initial counting animation

Link to headingUser experience: Dynamic visuals with rate of change

export function getRateOfChange() {
const lastCount = getCountFromKv();
const newCount = fetchLatestCount(); // only from last 5 minutes
const rateOfChange = newCount / (Date.now() - lastCount.timestamp)
return rateOfChange;
}

counter.tsx
'use client';
import { useEffect, useRef } from 'react';
export function Counter({ value, rateOfChange }) {
const ref = useRef(null);
useEffect(() => {
let id;
const increment = (ts) => {
if (!ref.current) return;
ref.current.textContent = value + rateOfChange * ts;
id = requestAnimationFrame(increment);
};
id = requestAnimationFrame(increment);
return () => {
cancelAnimationFrame(id);
};
}, [rateOfChange]);
return <span ref={ref}>{value}</span>;
}

Link to headingAccuracy: Preventing over/undercounting

counter.tsx
'use client';
import { useEffect, useRef, useState } from 'react';
export function Counter({ value, rateOfChange }) {
const [rate, setRate] = useState(rateOfChange);
const lastMetric = useRef({
value,
timestamp: null,
});
useEffect(() => {
const percentageDiff = lastMetric.current.value / value;
setRate(rateOfChange * percentageDiff);
}, [value, rateOfChange]);
const ref = useRef(null);
useEffect(() => {
let id;
const increment = (ts) => {
if (!ref.current) return;
const { timestamp, value } = lastMetric.current;
lastMetric.current.timestamp = ts;
const lastTime = timestamp ?? ts;
const diff = (ts - lastTime) * rate;
const newValue = value + diff;
lastMetric.current.value = newValue;
ref.current.textContent = newValue;
id = requestAnimationFrame(increment);
};
id = requestAnimationFrame(increment);
return () => {
cancelAnimationFrame(id);
};
}, [rate]);
return <span ref={ref}>{value}</span>;
}

Link to headingPerformance: Using React Server Components

const [data, setData] = useState(null);
useEffect(() => setData(getData()), []);

export async function GET() {
return getData();
}

async function Statistics() {
const { value, rateOfChange } = await getData();
return <Counter value={value} rateOfChange={rateOfChange} />;
}

Final counting animationFinal counting animation
Final counting animation

Link to headingSummary

Prepare for BFCM 2025

If you’re looking ahead to next year’s BFCM, watch the recording of our Black Friday 2025 virtual event hosted by Vercel CTO Malte Ubl.

Watch now

Ready to deploy?