Skip to main content
This guide walks you through setting up your own instance of the dynamic GitHub profile README generator. By the end, you’ll have a live Cloudflare Worker serving animated SVG graphics for your profile.

Prerequisites

Before you begin, make sure you have:

Node.js

Version 18 or higher

pnpm

Version 8 or higher (specified in package.json)

GitHub account

For contribution data and hosting your profile

Cloudflare account

Free tier works perfectly for this project
You’ll also need a GitHub personal access token with read:user scope to fetch contribution data.

Installation

1

Fork or clone the repository

Start by getting your own copy of the source code:
git clone https://github.com/aidrecabrera/aidrecabrera.git
cd aidrecabrera
If you fork the repository, you can enable GitHub Actions to automatically update your stats twice daily.
2

Install dependencies

The project uses pnpm as specified in package.json:
package.json
{
  "name": "aidrecabrera",
  "version": "1.0.0",
  "packageManager": "pnpm@10.6.5",
  "scripts": {
    "start": "wrangler dev",
    "deploy": "pnpm stats && wrangler deploy",
    "stats": "tsm --env-file .env scripts/stats.ts"
  },
  "devDependencies": {
    "@cloudflare/workers-types": "^4.20231218.0",
    "typescript": "^5.3.3",
    "wrangler": "^4.4.0"
  }
}
Install the packages:
pnpm install
3

Create environment variables file

Create a .env file in the project root with your credentials:
.env
# GitHub personal access token with read:user scope
GH_SECRET=ghp_your_token_here

# Optional: WhatPulse username for keyboard/mouse stats
WHATPULSE_USERNAME=your_username
Never commit your .env file to version control. It’s already in .gitignore.

Creating a GitHub token

  1. Go to GitHub Settings > Developer settings > Personal access tokens
  2. Click Generate new token (classic)
  3. Give it a descriptive name like “Profile README Stats”
  4. Select the read:user scope
  5. Click Generate token and copy it to your .env file
4

Update the worker configuration

Edit wrangler.toml to customize your worker name:
wrangler.toml
name = "aidre"
main = "src/worker.ts"
compatibility_date = "2023-11-21"
Change name to something unique for your Cloudflare Workers subdomain.
5

Customize the username in scripts

Open scripts/stats.ts and update the username:
scripts/stats.ts
const body = {
  query: `query ($username: String!, $from: DateTime, $to: DateTime) {
    user(login: $username) {
      contributionsCollection(from: $from, to: $to) {
        contributionCalendar {
          totalContributions
          // ...
        }
      }
    }
  }`,
  variables: {
    username: "aidrecabrera", // Change this to your GitHub username
    from: date.from?.toISOString(),
    to: date.to?.toISOString(),
  },
};
6

Fetch your contribution data

Run the stats script to fetch your GitHub contribution history:
pnpm stats
You should see output like:
...Fetching from 2024-03-04T00:00:00.000Z 2025-01-01T00:00:00.000Z
...Fetching from 2025-01-01T00:00:00.000Z 2026-03-04T00:00:00.000Z
...Done
This creates src/stats.json with your contribution data.

Local development

Test your worker locally before deploying:
pnpm start
This starts the Wrangler development server. Visit:
  • http://localhost:8787/?section=top&theme=light - Top header bar
  • http://localhost:8787/?section=main&theme=dark - Main contribution graph
  • http://localhost:8787/?section=link-website&theme=light&i=0 - Social link example
The worker uses query parameters to control which section to render and which theme to use.

Deploy to Cloudflare

1

Authenticate with Cloudflare

Log in to your Cloudflare account via Wrangler:
pnpm wrangler login
This opens a browser window for authentication.
2

Deploy the worker

Deploy your worker to Cloudflare’s edge network:
pnpm deploy
The deploy script runs pnpm stats first to ensure you have fresh data, then deploys with Wrangler.You’ll receive a URL like:
Published aidre (2.34 sec)
  https://aidre.your-subdomain.workers.dev
3

Test your deployment

Visit your worker URL with different sections:
https://aidre.your-subdomain.workers.dev/?section=top&theme=dark
https://aidre.your-subdomain.workers.dev/?section=main&theme=light
You should see SVG images rendering your contribution data.

Add to your GitHub profile

Now that your worker is live, add it to your GitHub profile README:
1

Create or edit your profile README

If you don’t have a profile README yet:
  1. Create a repository with the same name as your GitHub username
  2. Add a README.md file
  3. The README will automatically display on your profile
GitHub has a guide on managing your profile README.
2

Add the SVG images

Use the <picture> element with theme-specific sources:
README.md
<!-- Top header bar -->
<picture>
  <source media="(prefers-color-scheme: dark)" 
          srcset="https://aidre.your-subdomain.workers.dev/?section=top&theme=dark">
  <img src="https://aidre.your-subdomain.workers.dev/?section=top&theme=light" 
       width="100%" height="20" align="left">
</picture>
<img src="data:null;," width="100%" height="0" align="left" alt="">

<!-- Social link example -->
<a target="_blank" href="https://your-website.com">
  <picture>
    <source media="(prefers-color-scheme: dark)" 
            srcset="https://aidre.your-subdomain.workers.dev/?section=link-website&theme=dark">
    <img src="https://aidre.your-subdomain.workers.dev/?section=link-website&theme=light&i=0" 
         alt="visit my website" width="100" height="18px" align="left">
  </picture>
</a>
<img src="data:null;," width="100%" height="0" align="left" alt="">

<!-- Main contribution graph -->
<picture>
  <source media="(prefers-color-scheme: dark)" 
          srcset="https://aidre.your-subdomain.workers.dev/?section=main&theme=dark">
  <img src="https://aidre.your-subdomain.workers.dev/?section=main&theme=light" 
       alt="Contribution graph" width="100%" height="310" align="left">
</picture>
The empty <img src="data:null;,"> elements create spacing between sections. Adjust height values as needed.
3

Customize link sections

Available link sections in the source code:
  • link-resume - Resume/CV link
  • link-website - Personal website
  • link-linkedin - LinkedIn profile
  • link-twitter - Twitter/X profile
  • link-facebook - Facebook profile
  • link-instagram - Instagram profile
Each accepts an i parameter for animation stagger timing:
src/worker.ts
else if (section === "link-website") {
  const index = Number(searchParams.get("i")) ?? 0;
  content = link({ height: 18, width: 100, index, theme })("Website");
}
You can add custom sections by modifying src/worker.ts.

Set up automatic updates

Keep your stats fresh with GitHub Actions:
1

Add repository secrets

In your GitHub repository:
  1. Go to Settings > Secrets and variables > Actions
  2. Add these secrets:
    • GH_SECRET: Your GitHub personal access token
    • CLOUDFLARE_API_TOKEN: From Cloudflare dashboard
    • CLOUDFLARE_ACCOUNT_ID: From your Cloudflare Workers dashboard
2

Create a workflow file

Add .github/workflows/update-stats.yml:
.github/workflows/update-stats.yml
name: Update Stats

on:
  schedule:
    - cron: '0 0,12 * * *'  # Run at midnight and noon UTC
  workflow_dispatch:  # Allow manual trigger

jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: pnpm/action-setup@v2
        with:
          version: 10
      
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      
      - name: Install dependencies
        run: pnpm install
      
      - name: Fetch stats
        env:
          GH_SECRET: ${{ secrets.GH_SECRET }}
        run: pnpm stats
      
      - name: Commit stats
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add src/stats.json
          git diff --quiet && git diff --staged --quiet || git commit -m "Update stats"
          git push
      
      - name: Deploy to Cloudflare
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
        run: pnpm wrangler deploy
3

Verify the workflow

  • Push the workflow file to your repository
  • Go to the Actions tab
  • Manually trigger the workflow using the Run workflow button
  • Check that stats are updated and deployed successfully

Verification

Confirm everything is working:
curl -I https://aidre.your-subdomain.workers.dev/?section=top&theme=light
Should return 200 OK with content-type: image/svg+xml
Visit the same URL with theme=light and theme=dark - colors should change appropriately.
Check that your contribution graph shows recent activity. If not, run pnpm stats again.
Check the Actions tab in your repository to ensure the workflow runs successfully.

Troubleshooting

Error: Failed to fetch contributionsSolution: Check that:
  • Your GH_SECRET is valid and has the read:user scope
  • The username in scripts/stats.ts matches your GitHub username
  • Your token hasn’t expired
Error: Not logged inSolution: Run pnpm wrangler login and authenticate in the browser that opens.
Error: Image shows as brokenSolution:
  • Ensure your worker URL is publicly accessible
  • Check that the content-type header is image/svg+xml
  • Verify there are no CORS issues (shouldn’t be any for SVG)
Known issue: Firefox doesn’t support CSS animations in SVG foreignObject elements.Solution: The project includes a fallback section specifically for Firefox users:
<picture>
  <source media="(prefers-color-scheme: dark)" 
          srcset="https://aidre.your-subdomain.workers.dev/?section=fallback&theme=dark">
  <img src="https://aidre.your-subdomain.workers.dev/?section=fallback&theme=light" 
       alt="" width="420" align="left">
</picture>

Next steps

Now that your profile is live, explore these guides:

Customize themes

Change colors, fonts, and animations

Add custom sections

Create new SVG sections for your profile

API reference

Explore all available sections and parameters

Architecture

Understand how the system works in depth