How to Optimize GitHub Readme Stats Performance for Users with Many Repositories

Optimize GitHub Readme Stats performance by increasing cache TTLs, excluding large repositories via environment variables or query parameters, and implementing GraphQL pagination to reduce payload size for users with hundreds of repositories.

The anuraghazra/github-readme-stats service generates SVG cards by querying the GitHub GraphQL API and processing the response on the fly. For users with many repositories, the top-languages fetcher in src/fetchers/top-languages.js becomes the primary bottleneck because it retrieves up to 100 repositories and iterates over every language edge to compute weighted sizes. Tuning the caching strategy and request parameters keeps response times under one second and prevents GitHub API rate limit exhaustion.

Understanding the Performance Bottleneck

The critical path for high-volume users lies in the data fetching layer. When api/top-langs.js receives a request, it invokes fetchTopLanguages in src/fetchers/top-languages.js, which constructs a GraphQL query requesting first: 100 repositories with languages(first: 10) per repository.

This design creates three performance challenges for users with extensive repositories:

  1. Large payload size – A single query returns up to 1,000 language edges (100 repos × 10 languages), increasing network latency and JSON parsing time.
  2. Triple iteration – The fetcher runs three separate reduce operations (flatten, aggregate, weight) across every language edge, consuming CPU cycles proportional to repository count.
  3. No native pagination – The current implementation fetches a static 100-repo block; users with 200+ repos lose visibility into newer repositories without manual exclusion of older ones.

Key Optimization Strategies

Leverage Repository Exclusion Lists

The most immediate performance gain comes from reducing the dataset before processing. The codebase supports exclusion at two levels: global environment variables and per-request query parameters.

In src/common/envs.js, the EXCLUDE_REPOS environment variable defines a comma-separated list of repositories to ignore across all requests:


# .env configuration

EXCLUDE_REPOS=large-monorepo,auto-generated-docs,legacy-archive

For individual customization, users can pass the exclude_repo query parameter in the URL. In src/fetchers/top-languages.js, these arrays merge into repoToHide:

const allExcludedRepos = [...exclude_repo, ...excludeRepositories];

Excluding even five large repositories can reduce the language edge count by 50+, cutting processing time by 20-30%.

Increase Cache TTL for High-Traffic Users

The service uses src/common/cache.js to manage Cache-Control headers. By default, CACHE_TTL.TOP_LANGUAGES.DEFAULT sets a conservative TTL, but high-repo users should extend this to minimize redundant GraphQL calls.

You can override the default at the request level using the cache_seconds parameter:


# Set cache to 12 hours (43200 seconds)

https://github-readme-stats.vercel.app/api/top-langs?username=bigdev&cache_seconds=43200

The resolveCacheSeconds function in src/common/cache.js clamps the value between MIN and MAX bounds, ensuring you cannot accidentally set an invalid duration. For self-hosted instances, modify the CACHE_TTL object directly:

// src/common/cache.js
const CACHE_TTL = {
  TOP_LANGUAGES: {
    DEFAULT: 43200, // 12 hours
    MIN: 3600,
    MAX: 86400,
  },
};

Implement GraphQL Pagination

For users with hundreds of repositories, replace the single first: 100 query with paginated requests using the after cursor. This reduces per-request payload size and avoids GitHub's 5-second query timeout.

In src/fetchers/top-languages.js, replace the static fetcher with a paginated version:

const fetcherPaginated = async (variables, token) => {
  const perPage = 30; // Smaller pages reduce timeout risk
  let after = null;
  let allNodes = [];

  do {
    const res = await request(
      {
        query: `
          query ($login: String!, $after: String) {
            user(login: $login) {
              repositories(first: ${perPage}, after: $after, ownerAffiliations: OWNER, isFork: false) {
                pageInfo { hasNextPage endCursor }
                nodes {
                  name
                  languages(first: 5, orderBy: {field: SIZE, direction: DESC}) {
                    edges { size node { color name } }
                  }
                }
              }
            }
          }`,
        variables: { login: variables.login, after },
      },
      { Authorization: `token ${token}` },
    );

    const repoPage = res.data.data.user.repositories.nodes;
    allNodes = allNodes.concat(repoPage);
    after = res.data.data.user.repositories.pageInfo.hasNextPage
      ? res.data.data.user.repositories.pageInfo.endCursor
      : null;
  } while (after && allNodes.length < 200); // Stop after ~200 repos

  return { data: { user: { repositories: { nodes: allNodes } } } };
};

This approach fetches 30 repositories per request, stopping after accumulating 200 repos or reaching the end of the list. It prevents timeouts and reduces memory pressure during JSON parsing.

Configuration Examples

Exclude Specific Repositories via URL


# Exclude 'monorepo' and 'legacy-docs' from the calculation

https://github-readme-stats.vercel.app/api/top-langs?username=poweruser&exclude_repo=monorepo,legacy-docs

Set Extended Cache Duration


# Cache the result for 24 hours to minimize API calls

https://github-readme-stats.vercel.app/api/top-langs?username=poweruser&cache_seconds=86400

Global Environment Exclusions


# .env file for self-hosted deployments

EXCLUDE_REPOS=large-generated-client,automated-documentation

Implement Pagination in Self-Hosted Instances

Replace the default fetcher in src/fetchers/top-languages.js with the fetcherPaginated implementation shown above, then redeploy. This modification requires no changes to the card rendering logic in src/cards/top-languages.js.

Summary

  • The bottleneck for users with many repositories is the src/fetchers/top-languages.js fetcher, which processes up to 100 repositories and their language edges in a single request.
  • Exclude repositories using the EXCLUDE_REPOS environment variable or exclude_repo query parameter to reduce payload size before processing begins.
  • Extend cache TTLs via the cache_seconds parameter or by modifying CACHE_TTL.TOP_LANGUAGES.DEFAULT in src/common/cache.js to minimize redundant GraphQL calls.
  • Implement pagination by replacing the static fetcher with a cursor-based approach that requests smaller repository batches, preventing timeouts and reducing memory usage.
  • Pre-compute static assets for extremely high-traffic users by running a scheduled job that saves the SVG output, bypassing runtime computation entirely.

Frequently Asked Questions

How does the exclude_repo parameter improve performance?

The exclude_repo parameter filters out specified repositories before the language aggregation logic runs in src/fetchers/top-languages.js. By removing large or irrelevant repositories from the dataset, you reduce the number of language edges processed during the three reduce operations (flatten, aggregate, weight), which directly decreases CPU usage and response time.

What is the default cache duration for top languages cards?

According to src/common/cache.js, the default cache duration is controlled by CACHE_TTL.TOP_LANGUAGES.DEFAULT, which is typically set to a conservative value (often 2 hours). You can override this per-request using the cache_seconds query parameter, or modify the default value in the source code for self-hosted instances to reduce GraphQL API calls for frequently requested users.

Can I use pagination with the public Vercel instance?

No, the public Vercel instance (github-readme-stats.vercel.app) uses the standard static fetcher that requests up to 100 repositories in a single GraphQL query. To implement pagination, you must deploy your own instance and modify src/fetchers/top-languages.js to use a cursor-based fetcher that requests smaller batches (e.g., 30 repos per request) and aggregates the results before passing them to the card renderer.

Why does the top-languages card timeout for some users?

The timeout occurs because the GitHub GraphQL API has a 5-second query timeout limit. For users with hundreds of repositories, the single first: 100 query in src/fetchers/top-languages.js becomes too expensive to execute within this window, especially when calculating language statistics across many edges. Solutions include excluding large repositories to reduce query complexity, implementing pagination to split the workload across multiple smaller requests, or pre-computing the SVG to bypass runtime generation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →