Adding a GitHub Contribution Graph to Next.js
Software engineer and founder with a background in finance and tech. Currently building aVenture.vc, a platform for researching private companies. Based in San Francisco.

GitHub doesn't offer a direct embed for contribution graphs, so you'll need a custom solution. This guide shows how to fetch contribution data using GitHub's GraphQL API and display it in your Next.js app.
1: Fetching Contribution Data with GraphQL
Get contribution data via GitHub's GraphQL API. You'll need a personal access token with permission to read user contribution data. Store this securely, for example, as an environment variable (GITHUB_ACCESS_TOKEN_COMMIT_GRAPH).
The query to get the contribution calendar for a specific user and date range:
This function fetches the data and transforms the contributionLevel strings (like FIRST_QUARTILE) into simple numbers (0-4) for easier rendering.
2: Caching the Data
To avoid hitting the GitHub API on every request, cache the results server-side. An in-memory cache like node-cache works well for this. Set a reasonable Time-To-Live (TTL), like 24 hours. This example refreshes data only after the TTL expires; it does not expose a public cache-invalidation parameter.
Example API route in Next.js:
This API route first checks the cache. If data is missing or has expired, it fetches fresh data from GitHub using the function created in Step 1, caches it, and then returns it.
3: Rendering the Graph
With the data fetched (likely via a client-side fetch to your API route or passed as props from a server component), you can render the graph. A simple grid using CSS Grid and Tailwind CSS works well.
This component maps each ContributionDay to a colored square, using the level (0-4) to determine the background color via the getLevelColor helper.
Bonus: Fixing Hydration Errors
Sometimes when making components interactive, like wrapping them in links, you might encounter React hydration errors due to invalid HTML nesting (e.g., an <a> inside another <a>).
Problematic (can cause errors):
Solution (keep the outer link and use a noninteractive loading element):
The real anchor preserves native keyboard navigation, link semantics, and open-in-new-tab behavior without scripted navigation. Keep content free of nested links or other interactive controls too.



