DEV Community

Daniel Pertu
Daniel Pertu

Posted on

Five places a page names itself, and the one hub where the breadcrumb and the nav disagree

A content page needs a name. It turns out it needs five places to put one, and they are not all allowed to say the same thing. The day that became obvious was the day a footer column came out three times the width of the column next to it.

Here are the five, on one page of pub-trivia.app. Load the FAQ and read them off:

Where What it says
<title> Frequently Asked Questions | PubTrivia
og:title Frequently Asked Questions | PubTrivia
<h1> Frequently asked questions
breadcrumb and footer link FAQ

Three distinct strings across four slots, and a fifth slot exists for pages inside a cluster, which I will come to.

None of that is sloppiness. Each one has a different job:

  • The <title> is what a search result shows and, more to the point, it should contain the phrase people actually type. "Frequently Asked Questions" is that phrase. "FAQ" is shorter and worse.
  • The og:title has to carry the site name even when the <title> template would have added it, because Next does not apply title.template to openGraph.title. A link preview that says only "Pricing" is a link preview nobody can place.
  • The <h1> is read by somebody who has already arrived, so it is allowed to be longer, or in this case simply sentence case, because a heading in title case reads like a form field.
  • The link label has to fit in a footer column next to five other labels.

The field that exists for the fourth row

/**
 * What to call this page in a breadcrumb or a footer column, when the title
 * is too long to sit in one.
 *
 * "Frequently Asked Questions" is the right `<title>`, it is the phrase
 * people search, and the wrong footer link, where it is three times the
 * width of everything around it. Defaults to the title, so only the handful
 * of pages with that problem carry one.
 */
shortLabel?: string
Enter fullscreen mode Exit fullscreen mode

And the whole of the resolver:

/** What to call a page in a link. Its short label if it has one, else its title. */
export function labelFor(node: ContentNode): string {
    return node.shortLabel ?? node.title
}
Enter fullscreen mode Exit fullscreen mode

"Only the handful of pages with that problem" turned out to be optimistic. Of the 69 content nodes, 64 carry an explicit shortLabel. Five do not, and they are exactly the five whose titles were already short: the homepage, /pricing, /about, /features and /solutions.

The biggest gaps between a title and its label, in characters saved:

title shortLabel
Frequently Asked Questions FAQ
Free Quiz Questions and Answers Questions
Compare Pub Quiz Software Compare
Best Pub Quiz Apps and Software Best quiz apps
Free Quiz Night Tools Tools
Team and Individual Scoring Team scoring

Every one of those long forms is a phrase somebody searches and a disaster in a nav bar. The optional field with a fallback is the right shape for that: it is not two parallel lists that can drift, it is one list with an override on the rows that need it.

The fifth name

Clusters have their own label, declared on the cluster rather than on any page:

export type Cluster = {
    key: ClusterKey
    /** The hub's path. Every spoke in the cluster lives beneath it. */
    path: string
    /** Breadcrumb, header and footer label. Two words at most. */
    label: string
    ...
}
Enter fullscreen mode Exit fullscreen mode

So a hub page can be named twice over: once by its own node, and once by the cluster whose front door it is. The header and the footer link it by the cluster's label. The breadcrumb trail links it by labelFor().

For five of the six clusters those two agree, because the hub's short label was written to match. For the solutions hub they do not, and that page is the one where four of the five slots hold different strings:

Where What it says
<title> Solutions by Venue | PubTrivia
og:title Solutions by Venue | PubTrivia
<h1> Quiz nights, by the kind of place you run
breadcrumb Solutions by Venue
header and footer link Solutions

Open the page and you can see the last two in the same viewport: the footer heading says "Solutions", the breadcrumb at the top of the page says "Solutions by Venue". Both are links to pages in the same cluster, generated by the same registry, disagreeing about what the cluster is called.

The cause is the fallback. /solutions has no shortLabel, because its title is only eighteen characters and nothing about it is too long for a column. So labelFor() returns the title, and the title is the one written for a search result, while the nav uses the cluster label written for a nav bar. The field was given a sensible default and the default is wrong in the one case where a second name for the same page already existed.

The fix is one line, shortLabel: 'Solutions', and the more useful observation is about the shape of the bug: an optional field with a fallback cannot be forgotten, it can only be forgotten quietly. Nothing throws. Nothing fails a test. You get a reasonable-looking string in the wrong register, in a place you were not looking, on 1 page out of 69.

Where the long title is correct on purpose

The tools hub shows this from the other side. The visible cards say "Team name generator", "Scoresheet generator", "QR code generator", "Round planner". The ItemList in its structured data says:

{ "@type": "ListItem", "position": 1,
  "name": "Pub Quiz Team Name Generator",
  "url": "https://pub-trivia.app/tools/team-name-generator" }
Enter fullscreen mode Exit fullscreen mode

The markup uses node.title, not labelFor(node). A ListItem is a reference to a page rather than a rendering of a link, so the name it carries should be the page's name, which is the thing in the <title> of the page it points at. The visible card is a link in a grid of four and gets the short form.

It is defensible and I still find it slightly uncomfortable, because the rule everywhere else on this site is that structured data only states what is on the page, and the name in that ListItem is not the text a human sees. The defence is that the url is unambiguous and the title it quotes is the title the destination actually has. If you want to check: open the markup with view-source on the hub, then follow any of those four URLs and read its <title>. They match.

While you have the tools hub open, those four are genuinely useful if you ever run a quiz: the round planner does the arithmetic on how long a night will actually take, and the scoresheet and QR code pages produce printable PDFs in the browser, with no account and nothing uploaded.

What I would do differently

Name the fields after their job rather than their length. shortLabel describes the string, so the question it invites is "is the title too long", and the answer for /solutions was no. If it had been called navLabel, the question would have been "what should the nav say", and the answer would have been "Solutions", which is what the nav already said somewhere else.

Top comments (0)