DEV Community

Dev Maya
Dev Maya

Posted on

How I built a developer portfolio template in Next.js 15 — typography-first design decisions

I've been building and selling Next.js templates as a side project. This is my fifth one: Craft, a developer portfolio template with a typography-first approach.

Here are all the decisions I made.

Why typography-first?

Most dev portfolio templates use gradients, animations, and card grids to grab attention. I wanted to try the opposite — let the type do the work, and keep everything else minimal.

Font pairing: Lora (serif) for headings, JetBrains Mono for labels and code, Inter for body text. Three families that complement each other without fighting.

The serif gives headings an editorial quality that stands out from the usual Inter/Geist sans-serif stack.

The blinking cursor

The hero has an animated blinking cursor after the tech stack line:

function Cursor() {
  const [visible, setVisible] = useState(true)
  useEffect(() => {
    const id = setInterval(() => setVisible(v => !v), 530)
    return () => clearInterval(id)
  }, [])
  return (
    _
  )
}
Enter fullscreen mode Exit fullscreen mode

530ms interval feels natural — not too fast, not too slow. Hidden from screen readers via aria-hidden.

The bento grid for projects

Same "1px gap trick" I use in all my templates: grid background = border color, gap = 1px, no borders on individual cards.

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--border-sub); /* this IS the border */
  border-radius: var(--r-lg);
  overflow: hidden;
}

/* First project gets more visual weight */
.wide { grid-column: span 2; }
Enter fullscreen mode Exit fullscreen mode

The experience timeline

Pure CSS — no JavaScript, no library:

.timeline {
  position: relative;
}

/* The vertical line */
.timeline::before {
  content: '';
  position: absolute;
  left: 56px;
  top: 6px; bottom: 6px;
  width: 1px;
  background: var(--border);
}

.item {
  display: grid;
  grid-template-columns: 56px 16px 1fr;
  gap: 0 20px;
}
Enter fullscreen mode Exit fullscreen mode

The three-column grid: year column (fixed width) → dot column (fixed width) → content. The ::before pseudo-element on the list creates the connector line behind all dots.

All content in one file

src/lib/constants.ts exports everything the buyer will want to edit:

  • Personal info (name, role, tagline, bio, email)
  • Social links
  • Stack (with highlight: true for primary skills)
  • Projects (with live URL, repo URL, metrics)
  • Experience
  • Services
  • SEO metadata

No need to touch any component file. Change constants.ts and the entire site updates.

Real favicon included

This is the thing most templates skip. I include:

  • public/favicon.ico — browser tab icon
  • public/favicon.svg — modern SVG version

And in layout.tsx:



Enter fullscreen mode Exit fullscreen mode

Both are required. Some browsers prefer .ico, modern browsers prefer .svg.


Live demo: https://craft-portfolio-rose.vercel.app

Available on Gumroad for $39: https://devmaya.gumroad.com/l/craft

Top comments (0)