2025-04-13 Web Development, Design

A Random Hero Title Font Selector Component in Next.js

By O. Wolfson

OWolf

I wanted a simple way to add variety and personality to my website's hero titles:
show a different font every time a visitor loads the page.
Small changes like this make a design feel alive — dynamic without being chaotic.

After experimenting, I created a pure Server Component in Next.js that randomly picks a font at render time, adjusts the layout intelligently, and remains fully hydration-safe.

Here’s how it works.


Thanks to Cedilla Studio

Big thanks to Cedilla Studio for letting me use their incredible typefaces.
Their work brings character and unpredictability in the best sense.
Cedilla offers custom fonts, Lightroom presets, and design services.

Francis Bacon (the philosopher) once said:
"The job of the artist is always to deepen the mystery."
Cedilla Studio deepens the mystery for us.
Check out their work at cedilla.studio and follow @type.otf on Instagram.


How the Final Component Works

This project is built with React / Next.js using pure Server Components.
Here's the basic flow:

  1. Fonts: Import several custom fonts using Next.js’s localFont utility.
  2. Randomization: Each page render, the server picks a random font from the list.
  3. Layout Tuning: Based on the selected font, we dynamically adjust font size and spacing for clean visual balance.
  4. Rendering: The <h1> element is output with no client-side hydration mismatch.

Everything happens server-side.
No JavaScript randomness on the client. No flashing. No hydration errors.


Folder Structure

bash
random-font-hero
├── fonts
│   ├── CS-5uper.otf
│   ├── CS-Defiant2.woff2
│   ├── CS-Endless.woff2
│   ├── CS-Glare.otf
│   └── CS-Noire-Black.otf
├── fonts.ts
└── random-font-hero.tsx

fonts.ts

Each font is imported, and we define a font list including layout tweaks for each font.

ts
import localFont from "next/font/local";

export const csDefiant = localFont({
  src: "./fonts/CS-Defiant2.woff2",
  variable: "--font-cs-defiant",
  display: "swap",
});

export const csEndless = localFont({
  src: "./fonts/CS-Endless.woff2",
  variable: "--font-cs-endless",
  display: "swap",
});

export const cs5uper = localFont({
  src: "./fonts/CS-5uper.otf",
  variable: "--font-cs-5uper",
  display: "swap",
});

export const csGlare = localFont({
  src: "./fonts/CS-Glare.otf",
  variable: "--font-cs-glare",
  display: "swap",
});

export const fonts = [
  { name: "CS Defiant", font: csDefiant },
  { name: "CS Endless", font: csEndless },
  { name: "CS 5uper", font: cs5uper },
  { name: "CS Glare", font: csGlare },
];

random-font-hero.tsx

The main component picks a font, adjusts font size and spacing, and renders the hero title.

tsx
import { fonts } from "./fonts";

interface HeroTitleProps {
  children: React.ReactNode;
  className?: string;
}

export default function HeroTitle({
  children,
  className = "",
}: HeroTitleProps) {
  const fontsWithInter = [...fonts];
  const randomFont =
    fontsWithInter[Math.floor(Math.random() * fontsWithInter.length)];

  const fontSizeClass =
    randomFont.name === "CS Endless"
      ? "sm:text-[19rem] text-[7.5rem]"
      : randomFont.name === "CS Glare"
        ? "sm:text-[21rem]"
        : "sm:text-[22rem]";

  const paddingRightClass = randomFont.name === "CS Endless" ? "pr-8" : "pr-0";

  return (
    <div className="overflow-hidden">
      <h1
        className={`
          ${randomFont.font.className}
          ${fontSizeClass}
          text-[9rem]
          font-bold
          text-center
          m-0 p-0
          leading-[1]
          -mt-0 -mb-3
          pt-3
          ${paddingRightClass}
          ${className}
        `}
      >
        {children}
      </h1>
    </div>
  );
}

Why This Approach Works

  • Pure Server-Side Randomness:
    The font is picked once during server render, so the initial HTML matches exactly what React expects when hydrating the page. No hydration errors.

  • Dynamic Layout Tuning:
    Each font may have a different visual weight and width. We adjust the font size and padding based on the selected font for a clean and consistent look.

  • Zero Client-Side Flicker:
    There’s no flashing or popping because we never re-randomize after the page loads.

  • Safe for Static and Dynamic Pages:
    This Server Component works whether you use it in a static route (/home) or a dynamic MDX route (/[slug]).


A Note on Layout Cropping

Fonts naturally have invisible ascent and descent padding — meaning large text can have unexpected whitespace above and below.

To control this:

  • We remove margins and paddings with m-0 p-0.
  • We set leading-[1] to tighten the line height.
  • We use small negative margins (-mt-0 -mb-3) and padding (pt-3) to manually crop extra whitespace.

This keeps the hero text looking strong and balanced no matter which font is chosen.


Conclusion

Adding a random font on page load is a small detail, but it brings a sense of life to a design.
By handling randomness purely server-side and tuning layout carefully,
we can achieve this with no performance trade-offs and no visual instability.

This Server Component approach in Next.js is clean, fast, and production-ready.