DEV Community

Cover image for How I Built an Aesthetic Developer Portfolio with a Pixel-Perfect GitHub Graph
ankur
ankur

Posted on

How I Built an Aesthetic Developer Portfolio with a Pixel-Perfect GitHub Graph

When building a developer portfolio, we often fall into the trap of using a standard template or a minimum viable product. But first impressions count. If your site looks simple or generic, visitors click away.

I wanted to build a developer portfolio that felt premium, minimalist, and alive with clean animations and micro-interactions.

Here is how I designed my portfolio using React 19, Next.js 16 (App Router), and Tailwind CSS v4, and solved the absolute nightmare of contribution graph sizing.

🎨 1. Micro-Interactions: Audio-Feedback Theme Toggle

Most websites use a standard icon transition when toggling dark mode. I wanted a tangible feel, so I added instant audio feedback.

Instead of dealing with complex Web Audio API contexts or mobile tap delays, I implemented a pre-loaded HTML5 element that plays a light click sound seamlessly across both desktop and iOS Safari:

export default function ThemeToggle() {
  const { isDarkMode, toggleTheme } = useTheme();
  const audioRef = useRef(null);
  const handleClick = () => {
    try {
      const audio = audioRef.current;
      if (audio) {
        audio.currentTime = 0;
        audio.volume = 0.4;
        audio.play().catch(() => {});
      }
    } catch {}
    toggleTheme();
  };
  return (
    <>
      <audio ref={audioRef} src="/click.wav" preload="auto" aria-hidden="true" />
      <button onClick={handleClick}>
        {/* SVG Icon */}
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

πŸ“Š 2. The GitHub Contribution Graph Challenge

One of the centerpieces of a developer portfolio is the GitHub contribution graph.

My initial version computed cell sizes dynamically via ResizeObserver in JavaScript. However, this caused two major issues:

The month labels (JUL, AUG, SEP) were styled outside the SVG. As the screen size scaled, the text shifted, causing labels to misalign with the calendar columns below them.
The dynamic resize calculations caused a layout shift (CLS) during page loading.
The Fix: Fixed viewBox + Embedded Labels
To resolve this, I took inspiration from the pixel-perfect layout of siddz.com:

Set a fixed coordinate SVG with viewBox="0 0 791 120" and styled it with Tailwind's w-full h-auto. This lets the browser scale the entire graph natively as a vector image on mobile.
Set a fixed cell size of 11px and a gap of 4px.
Moved the month labels inside the SVG as elements with dominantBaseline="hanging".

<svg viewBox={`0 0 791 120`} className="w-full h-auto">
  {/* Month labels inside the SVG scale perfectly with the cells */}
  <g className="text-[10px] fill-zinc-400 dark:fill-zinc-500" style={{ letterSpacing: '0.05em' }}>
    {mLabels.map((m, i) => (
      <text key={i} x={m.x} y={0} dominantBaseline="hanging">
        {m.label}
      </text>
    ))}
  </g>

  {/* Contribution cells */}
  {weeks.map((wk, wi) =>
    wk.contributionDays.map((day, di) => (
      <rect
        key={`${wi}-${di}`}
        x={wi * 15}
        y={di * 15 + 19}
        width={11}
        height={11}
        rx={0}
        ry={0}
        className={levelClassMap[day.contributionLevel]}
      />
    ))
  )}
</svg>
Enter fullscreen mode Exit fullscreen mode

By nesting the month coordinates directly inside the SVG grid (x={wi * 15}), the labels are locked in place and never get misaligned, regardless of screen resolution or scaling!

πŸ” 3. Expert-Tier Technical SEO

A beautiful portfolio is useless if nobody can find it on Google. I implemented an advanced technical SEO pipeline:

Structured Data (JSON-LD Schemas): Injected formal Person, WebSite, and ProfilePage schemas directly into the to build Google Knowledge Graph authority for my developer brand.
Dynamic Sitemap with Crawl Budget Protection: Configured a dynamic sitemap.js that pulls all my custom showcase components and services. I used fixed modified dates (2025-11-15) rather than dynamic new Date(). This signals to search crawlers that content hasn't changed unless it actually has, preserving crawl budget.
IndexNow SEO Protocol: Set up a /api/indexnow route. Whenever I publish a new page or interactive component, my site automatically pings the IndexNow server to request immediate crawling from Bing and Yandex.

Top comments (1)

Collapse
 
ankurz profile image
ankur

Let me know what you think of this setup in the comments!