acusti/github-readme

★ 0Forks 0TypeScriptGitHub ↗Compare

README

GitHub Readme — Cloudflare Workers

A self-hosted GitHub stats card generator deployed on Cloudflare Workers (free tier). Generates dynamic SVG cards showing your GitHub stats and most-used languages, designed to be embedded in your GitHub profile README.

Inspired by github-readme-stats, rebuilt from scratch for Cloudflare Workers with zero external dependencies beyond Hono (router).

Prerequisites

  • Bun (used as package manager; Node/npm/yarn work too)
  • A Cloudflare account (free tier is fine)
  • A GitHub Personal Access Token (classic) — fine-grained tokens don't support the GraphQL API yet, so a classic PAT is required. Recommended scopes: read:user and repo (needed for private repo stats/languages)

Setup

# Install dependencies
bun install

# Set your GitHub token as a Cloudflare secret
bunx wrangler secret put GITHUB_TOKEN
# Paste your token when prompted

# Deploy
bunx wrangler deploy

Your worker will be live at https://github-readme.<your-subdomain>.workers.dev.

Local development

# Create a .dev.vars file with your token for local dev
echo 'GITHUB_TOKEN=ghp_your_token_here' > .dev.vars

# Start local dev server
bun run dev
# → http://localhost:8787

.dev.vars is gitignored. Never commit tokens.

Usage

Stats card

![GitHub Stats](https://your-worker.workers.dev/api?username=YOUR_USERNAME)
Parameter Description Default
username GitHub username (required) —
theme Card theme (see Themes) default
hide Comma-separated stats to hide: stars, commits, prs, issues, contribs —
show_icons Show icons next to stats true
hide_border Hide card border false
hide_title Hide card title false
hide_rank Hide rank circle false
include_all_commits Count all lifetime commits (slower, extra API call) false
custom_title Override card title —
line_height Space between stat rows (px) 25
border_radius Card corner radius (px) 4.5

Top languages card

![Top Langs](https://your-worker.workers.dev/api/top-langs?username=YOUR_USERNAME)
Parameter Description Default
username GitHub username (required) —
theme Card theme (see Themes) default
layout normal or compact normal
langs_count Number of languages (1–20) 5
exclude Comma-separated languages to exclude —
card_width Card width in pixels 300
hide_border Hide card border false
hide_title Hide card title false
custom_title Override card title —
border_radius Card corner radius (px) 4.5

Example (GitHub profile README)

![Stats](https://your-worker.workers.dev/api?username=octocat&theme=tokyonight)
![Top Langs](https://your-worker.workers.dev/api/top-langs?username=octocat&layout=compact&theme=tokyonight)

Themes

default, dark, radical, tokyonight, dracula, gruvbox, catppuccin-mocha, nord, solarized, github-dark, rose_pine, onedark, cobalt, transparent

Themes are defined in src/common/themes.ts — add new ones by adding entries to the themes object.

Project structure

src/
  index.ts                 Hono router — endpoints: /, /api, /api/top-langs
  common/
    card.ts                Base SVG card wrapper (title, border, background)
    icons.ts               GitHub octicon SVG paths
    themes.ts              Theme color definitions
    utils.ts               kFormatter, measureText, escapeHtml
  cards/
    stats-card.ts          Stats card renderer (rank circle, stat rows)
    top-langs-card.ts      Top languages card (normal + compact layouts)
    error-card.ts          Error display card
  fetchers/
    stats.ts               GitHub GraphQL/REST → user stats + rank calc
    top-langs.ts           GitHub GraphQL → language breakdown
wrangler.toml              Cloudflare Workers config

How it works

  1. A request hits the worker (e.g. /api?username=octocat)
  2. The fetcher calls the GitHub GraphQL API (authenticated with your PAT)
  3. Data is passed to a card renderer that builds an SVG string
  4. The SVG is returned with Cache-Control headers (4-hour TTL) so Cloudflare's CDN caches it

No database, no KV, no external services beyond the GitHub API.

Updating

Add a new theme: Add an entry in src/common/themes.ts and redeploy.

Add a new stat: Update the GraphQL query in src/fetchers/stats.ts, add the field to UserStats, and add a row in src/cards/stats-card.ts.

Add a new card type: Create a fetcher in src/fetchers/, a renderer in src/cards/, and a route in src/index.ts.

Change caching: The svgResponse() function in src/index.ts sets the Cache-Control max-age (default 14400s = 4 hours).

Rotate your PAT: Run bunx wrangler secret put GITHUB_TOKEN and paste the new token. No redeploy needed. Note: when creating your classic PAT, you can choose "No expiration" — the token is stored as an encrypted Cloudflare secret with read-only scopes, so the risk is minimal. If you prefer an expiring token, set a calendar reminder to rotate it before it expires or your cards will silently break.

Limits

  • Cloudflare Workers free tier: 100,000 requests/day, 10ms CPU per invocation
  • GitHub API: 5,000 requests/hour per PAT. With 4-hour CDN caching, this is more than enough for personal use.
  • Bundle size: ~22KB gzipped (well under the 3MB limit)

Contributors

acusti

Issues