Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

141 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Visitor Badge Reloaded ✨

Visitor Badge Reloaded began as a faster, maintained, and more customizable replacement for visitor-badge.

It now provides configurable visitor-count badges, traffic analytics, and rate-limited web and API access while remaining compatible with the original service.


🏎️ Benchmarks

See benchmark/README.md for details.

Results:

Test Name Requests Success Rate Avg Response P95 P99 Throughput
Health Check 100 100.00% 72.19ms 539.55ms 672.36ms 27.97 req/s
Badge - Sequential Requests 50 100.00% 44.30ms 29.59ms 1046.52ms 18.36 req/s
Badge - With Hit Counting 100 100.00% 101.00ms 952.35ms 1291.92ms 32.41 req/s
Badge - Read-Only (No Hits) 100 100.00% 63.88ms 48.31ms 853.43ms 61.73 req/s
Badge - Low Concurrency 100 100.00% 161.17ms 1244.82ms 1244.87ms 30.96 req/s
Badge - High Concurrency 200 100.00% 468.04ms 1349.58ms 4102.70ms 31.26 req/s
Badge - Customized Styling 100 100.00% 144.87ms 1255.16ms 1257.63ms 63.15 req/s
Analytics API 50 100.00% 69.31ms 80.14ms 116.98ms 105.85 req/s
Landing Page 50 100.00% 23.15ms 39.84ms 48.45ms 236.99 req/s
Stress Test - Burst Traffic 500 100.00% 594.97ms 1753.77ms 4902.85ms 35.73 req/s

Performed on 2026-08-06 using a MacBook Pro (M4 Pro) against the public Cloudflare-hosted service.

Quick Start:

# Run a quick performance check (~30 seconds)
bun run benchmark/quick-bench.ts

# Run the full benchmark suite (~5 minutes)
bun run bench

# Generate visual HTML report
bun run bench:report

What's tested:

  • Response time (avg, p50, p95, p99)
  • Throughput (requests per second)
  • Cache effectiveness
  • Concurrent request handling
  • Error rates and reliability
  • Real-world usage patterns

Results are saved to benchmark/results/ and can be compared over time to track performance trends.

βš™οΈ Settings

Badge Options

To use the options, append these flags to the URL of the badge!

Option Flag Description
Colour &color=<colour> Background colour of the count, on the right-side of the badge. Takes a hex value without the #, one of Shields' names (blue, brightgreen, critical, ...), or any CSS colour.
Label Colour &lcolor=<colour> Background colour of the label, on the left-side of the badge. Same formats as &color=.
Style &style=<style> One of flat, flat-square, plastic, for-the-badge or social. Anything else falls back to flat.
Text &text=<label> Replaces the label on the left-side of the badge. A single underscore renders as a space (Total_Visits), __ as a literal underscore, and -- as a literal dash.
Label (legacy) &label=<label> Legacy alias for &text=. If both are present, &text= takes precedence.
Custom &custom=<format> Allows a custom string format to be set for the count. Replaces CNT with the view count, so &custom=CNT_visits renders "42 visits".
Logo &logo=<logo name> Adds a logo to the badge (refer to examples). Takes any Simple Icons slug (googlechrome), the icon title with spaces as dashes (google-chrome), or a data: URI of your own image.
Logo Colour &logoColor=<colour> Recolours the logo. Same formats as &color=. Defaults to white; pass an empty value to keep the icon's own brand colour.
hit &hit=off Enabled by default. Determines if the badge will update the count (useful for duplicated badges or badges for just viewing the count). Anything other than true or yes β€” including an empty value β€” is considered disabled. A disabled badge is cached for 15 seconds, so it is much cheaper to serve.

&cache= and &unique= are gone. Neither did anything: &cache= was never read by the code, and &unique= was rejected by request validation before it could be. Caching is no longer a knob β€” a badge that counts is never cached, and a badge that does not count always is. See Caching.

Defaults:

  • Colour: "blue"
  • Label Colour: "grey"
  • Style: "flat"
  • Text: "Visitors"
  • Logo: no logo
  • Logo Colour: "white"
  • Hit: enabled

Caching

A badge that counts a view (&hit= enabled, the default) is never cached, by anything. Caching it would let a cache answer GitHub's image proxy on the service's behalf and silently swallow the view.

A badge that does not count (&hit=false) is cached at the Cloudflare location serving it for 15 seconds, and says so with Cache-Control: public, max-age=15. Within that window it costs no storage access at all. Several differently styled badges for one page_id also share a single count lookup, so a README full of them is one read rather than one per badge.

Responses carry X-VBR-Cache: HIT, MISS, or BYPASS (for the counted badges that are never cacheable). Cache entries do not replicate between Cloudflare locations, so a MISS simply means you are the first to ask in that location.

Rate Limits

Limits exist to bound cost, not to police badges, so they are keyed on the caller rather than on the badge β€” a popular badge is not throttled for being popular.

  • Counting a view is limited per client, per badge. Over the limit the badge still renders normally; the view just is not counted. A rate limit never turns a README into a broken image.
  • Badge requests overall are limited per client, and only checked when the cache missed. Far over that limit a client gets a 429 and a "Rate Limit Exceeded" badge.
  • Analytics (/api/analytics/:pageId and /info/:id) is limited per client and returns 429 with Retry-After.

Limits are enforced per Cloudflare location and are eventually consistent, so they are approximate by design.

Examples πŸ§ͺ

Different text background

Different label background

Different style

Different text

Different logo

🚒 Migrating From Visitor Badge

If you use the original Visitor Badge, just replace the domain name:

ie. 'visitor-badge.glitch.me/badge?page_id=YOURPAGEID' --> 'https://vbr.nathanchung.dev/badge?page_id=YOURPAGEID'

Options (Old Option) (add as a HTTP query parameter)

IMPORTANT: Other than the page_id option, there exists additional styling options and functional options (in the future). Badges are rendered with the same library Shields.io uses, so the styling options carry over from Shields.io. Also, all the options are specified as HTTP parameters!!!

  • page_id=<your id> --> identifies your badge, make this unique to yourself. eg. <your username.visitor.badge.reloaded or <username>-<username>
  • color=<colour here> --> the hex colour of the text background, do NOT include the #
  • lcolor=<colour here> --> the hex colour of the label background, do NOT include the #
  • style=<style name> --> one of flat, flat-square, plastic, for-the-badge or social
  • text=<Some text other than "Visitors"> --> put a customizable label on your badge
  • logo --> logo to put beside the badge, go to https://simpleicons.org/ for the available names
  • logoColor --> refer to color for the formatting
  • cache --> removed, and never actually did anything. Caching is automatic now; see Caching

πŸ₯³ Contributing

  1. Star ⭐ this project.
  2. Create an Issue on Github if there are any issues of feature requests.
  3. Submit PRs. Refer to Contributing

πŸ“¦ Deploying your own instance (self-hosting)

Cloudflare

# 1. install dependencies
bun i

# 2. use own workers kv
# update `wrangler.toml` with your own kv namespace id (avoid changing binding name to avoid regenerating types)
# @see https://developers.cloudflare.com/kv/get-started/

# 3. set the visitor hashing secret (any long random string)
# used to derive per-badge, per-day keys for the estimated unique visitor
# metric. It is never persisted, so a storage dump cannot be probed for
# whether a given address visited. Skip this and hit counting still works,
# but the estimate is reported as null rather than falling back to something
# weaker.
wrangler secret put VISITOR_HASH_KEY

# 4. rate limiting needs no setup, but the `namespace_id`s under [[ratelimits]]
# in `wrangler.toml` must be unique within your Cloudflare account. If you run
# other Workers that use rate limiting, renumber them. Leaving the bindings out
# entirely is supported - the limiters fail open and badges keep working.

# 5. deploy to cloudflare
# the Simple Icons set is generated into src/generated/ as part of the build,
# so this needs devDependencies installed (`bun i` above covers it)
bun run deploy

πŸ’» Software Used

  • Cloudflare Workers
  • Typescript
  • Hono
  • badge-maker (the renderer behind shields.io) and Simple Icons, both bundled into the Worker

About

An extensible and modular visitor counter with a focus on performance and reliability. Serverless architecture.

Topics

Resources

Stars

85 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages