I used to reach for moment.js for things like “5 minutes ago”. Then date-fns. Now I just use the browser’s Intl.RelativeTimeFormat and call it a day.

const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });

rtf.format(-1, "day"); // "yesterday"
rtf.format(-3, "day"); // "3 days ago"
rtf.format(2, "hour"); // "in 2 hours"

The numeric: "auto" is the magic — it gives you “yesterday” instead of “1 day ago” when the language has a nicer phrasing.

Wrap it up to take a Date and figure out the unit for you:

const UNITS = [
  { unit: "year", seconds: 31_536_000 },
  { unit: "month", seconds: 2_592_000 },
  { unit: "week", seconds: 604_800 },
  { unit: "day", seconds: 86_400 },
  { unit: "hour", seconds: 3_600 },
  { unit: "minute", seconds: 60 },
  { unit: "second", seconds: 1 },
];

export function timeAgo(date, locale = "en") {
  const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
  const diff = (date.getTime() - Date.now()) / 1000;

  for (const { unit, seconds } of UNITS) {
    if (Math.abs(diff) >= seconds || unit === "second") {
      return rtf.format(Math.round(diff / seconds), unit);
    }
  }
}
timeAgo(new Date(Date.now() - 90_000)); // "2 minutes ago"
timeAgo(new Date(Date.now() + 3_600_000)); // "in 1 hour"

Locale-aware out of the box — pass 'fr', 'hi', 'ja' and you get the right phrasing for that language. Zero dependencies.