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:
- Large payload size – A single query returns up to 1,000 language edges (100 repos × 10 languages), increasing network latency and JSON parsing time.
- Triple iteration – The fetcher runs three separate
reduceoperations (flatten, aggregate, weight) across every language edge, consuming CPU cycles proportional to repository count. - 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.jsfetcher, which processes up to 100 repositories and their language edges in a single request. - Exclude repositories using the
EXCLUDE_REPOSenvironment variable orexclude_repoquery parameter to reduce payload size before processing begins. - Extend cache TTLs via the
cache_secondsparameter or by modifyingCACHE_TTL.TOP_LANGUAGES.DEFAULTinsrc/common/cache.jsto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →