Building a Live Download Counter

31.05.2026...5 min read

How to build a live telemetry tracker using decoupled React components, atomic PostgreSQL upserts, and non-blocking Next.js API routes to prevent CLI race conditions.

reactnextjsdrizzle-ormdatabase

When building the component registry for this portfolio, I wanted a way to track how many times a component was installed via the CLI (npx shadcn add).
While standard web applications often just render a raw metric string (e.g., 10,420), I wanted something that felt more tactile and hardware-inspired. By segmenting the digits into distinct monospaced blocks, we create a highly legible, aesthetic counter that feels right at home on an engineering dashboard.
But building a live counter isn't just about CSS. We have to solve a few structural problems:
  1. Separation of Concerns: The UI component itself must remain pure and stateless so it can be easily distributed.
  2. Concurrency: If a popular component drops, hundreds of developers might run the CLI command simultaneously. We need to prevent database race conditions.
  3. Performance: Tracking telemetry shouldn't slow down the actual CLI download speed for the end user.
Here is how I engineered the full stack architecture using React, Next.js Server Actions, and Drizzle ORM.

1. The Pure UI Component

Following the Shadcn philosophy, we start by building a completely isolated, stateless UI component. It doesn't know anything about databases or server fetching.
It simply accepts a value prop, pads it with leading zeros to maintain a consistent width (e.g., 00420), and maps over each digit to render it inside a dedicated tile.
// src/components/ui/download-counter.tsx
'use client'

import * as React from 'react'
import { cn } from '@/utils/utils'

export interface DownloadCounterProps extends React.HTMLAttributes<HTMLDivElement> {
  value: number
  padLength?: number
}

/**
 * Renders a stylized digital counter, padding the provided value with leading zeros.
 */
export function DownloadCounter({ 
  value, 
  padLength = 5,
  className,
  ...props 
}: DownloadCounterProps) {
  const displayCount = (value ?? 0).toString().padStart(padLength, '0')

  return (
    <div 
      className={cn('flex items-center gap-1 sm:gap-1.5', className)}
      {...props}
    >
      {displayCount.split('').map((digit, i) => (
        <span 
          key={i} 
          className="flex h-10 w-8 items-center justify-center rounded-md bg-muted/50 font-mono text-lg font-medium tracking-tight text-foreground"
        >
          {digit}
        </span>
      ))}
    </div>
  )
}
Because it's decoupled, you can use it anywhere immediately with static data: <DownloadCounter value={420} padLength={5} />

2. Telemetry Database Schema

To make the counter live, we need a place to store the metrics. We create a minimalist PostgreSQL table that tracks the total installs for each unique component slug.
// src/lib/db/schema.ts
import { pgTable, varchar, integer } from 'drizzle-orm/pg-core'

export const component_metrics = pgTable('component_metrics', {
  slug: varchar('slug', { length: 255 }).primaryKey(),
  installs: integer('installs').default(0).notNull(),
})

3. Atomic Server Actions

Next, we need a secure way to interface with the database. We write two Next.js Server Actions: one to fetch the data for the UI, and one to increment the data when a CLI installation occurs.
The increment action is where things get interesting. If we run a standard SELECT followed by an UPDATE count + 1, we run the risk of a race condition. If two users install the component at the exact same millisecond, they might both read 10, and both write 11, effectively losing a metric.
To solve this, we rely on Drizzle's onConflictDoUpdate to perform an Atomic Upsert. We push the math down to the PostgreSQL engine itself, ensuring the increment is perfectly transactional.
// src/lib/actions/views.ts
'use server'

import { db } from '@/lib/db/drizzle'
import { component_metrics } from '@/lib/db/schema'
import { eq, sql } from 'drizzle-orm'

// Fetches the live count for the UI
export async function getInstallsAction(slug: string) {
  if (!slug) return { installs: null }
  try {
    const [result] = await db
      .select({ installs: component_metrics.installs })
      .from(component_metrics)
      .where(eq(component_metrics.slug, slug))
    return { installs: result?.installs ?? 0 }
  } catch (error) {
    return { installs: null }
  }
}

// Atomically increments the count during an installation
export async function incrementInstallAction(slug: string) {
  if (!slug) return { installs: null }
  try {
    const [result] = await db
      .insert(component_metrics)
      .values({ slug, installs: 1 })
      .onConflictDoUpdate({
        target: component_metrics.slug,
        set: { installs: sql`${component_metrics.installs} + 1` }, // Atomic increment
      })
      .returning({ installs: component_metrics.installs })
    return { installs: result?.installs ?? 0 }
  } catch (error) {
    return { installs: null }
  }
}

4. Hydration & The Frontend Wrapper

Now we need to bridge the server and the client. We build a React client component that mounts, fires off the getInstallsAction, and handles the loading state.
While waiting for the network response, we return a smooth animate-pulse skeleton. Once the database integer arrives, we pass it into our pure DownloadCounter UI.
// src/components/live-counter.tsx
'use client'

import { useEffect, useState } from 'react'
import { DownloadCounter } from '@/components/ui/download-counter'
import { getInstallsAction } from '@/lib/actions/views'

export function LiveCounter({ slug }: { slug: string }) {
  const [installs, setInstalls] = useState<number | undefined>(undefined)

  useEffect(() => {
    getInstallsAction(slug).then(res => {
      if (res.installs !== null) setInstalls(res.installs)
    })
  }, [slug])

  if (installs === undefined) {
    return <div className="h-10 w-48 animate-pulse rounded-md bg-muted" />
  }

  return <DownloadCounter value={installs} />
}

5. Non-Blocking API Triggers

Finally, we need to actually trigger the incrementInstallAction whenever someone uses the CLI.
In our Next.js API route that serves the component JSON payload, we invoke the server action, but we do not await it. By using .catch() instead of await, we create a "fire-and-forget" telemetry ping. The CLI payload is returned to the user instantly, while the database upsert happens silently in the background.
// src/app/r/[slug]/route.ts
import { incrementInstallAction } from '@/lib/actions/views'
import { NextResponse } from 'next/server'

export async function GET(
  request: Request,
  { params }: { params: { slug: string } }
) {
  // Fire and forget the telemetry tracker (non-blocking)
  incrementInstallAction(params.slug).catch(console.error)

  // Return the component JSON payload instantly
  return NextResponse.json({ ...registryPayload })
}
By decoupling the UI from the database logic, utilizing atomic upserts to prevent race conditions, and leveraging non-blocking API triggers, we've built an extremely robust, edge-ready metrics system that scales effortlessly.
This is where the text ends and the thinking begins.
blogs/
cd ..