Scroll to the bottom of how to host a pub quiz and you get a short closing section: a heading, one sentence, one button. Do the same on the round planner and on quiz night for pubs. The button says the same eight words all three times. The heading above it is different every time.
$ for p in guides/how-to-host-a-pub-quiz tools/round-planner solutions/pubs; do
curl -s https://pub-trivia.app/$p \
| grep -o 'Set your first one up\|Run the order you just planned\|Run your first quiz night free'
done | sort -u
Run the order you just planned
Run your first quiz night free
Set your first one up
That block comes from one component, and the component carries a default for pages that do not supply their own. No page does not supply its own.
The component
Sixty-seven pages render inside one shell. It owns the header, the breadcrumb up to the hub, the related-links block and the footer, so that a page cannot forget any of them, and it owns the closing call to action as well:
export function ContentPage({ path, children, cta, width = 'prose', article = false }: {
path: string
children: React.ReactNode
/** The single contextual call to action. Defaults to the free trial. */
cta?: { heading: string; body: string; label: string; href: string }
...
}) {
const node = requireNode(path)
const action = cta ?? {
heading: "Ready for quiz night?",
body: "Set up your venue, print the QR codes, and you're live tonight.",
label: "Start free, no card needed",
href: "/signup",
}
An optional prop with a ?? behind it. The reading is obvious: most pages get the generic trial pitch, and the few with something sharper to say override it. That is how I wrote it, and it is wrong about its own codebase.
$ grep -rl "ContentPage" app --include="page.tsx" | wc -l
67
$ grep -rl "cta={" app --include="page.tsx" | wc -l
67
Sixty-seven of 67. So those four lines of copy have never rendered, never been read in context, and never been seen by anybody outside the repository. That is checkable from outside, because the heading is a string nothing else on a content page would produce:
$ for u in /pricing /faq /features /guides /compare /tools /solutions /quiz-questions; do
curl -s https://pub-trivia.app$u | grep -c 'Ready for quiz night'
done | sort -u
0
With one exception, which is the interesting part:
$ curl -s https://pub-trivia.app/ | grep -c 'Ready for quiz night'
1
The homepage says it. The homepage is not a content page and does not use the component, it has its own closing section, and all four of the default's fields are that section copied across: same heading, same sentence, same button label, same destination. The "default" is not a neutral fallback at all. It is the landing page's closing pitch, pasted into a component the landing page does not render, where it waits for a page that never arrives.
What the 67 pages actually say
I pulled the four fields out of every page and counted the distinct values.
| Field | Distinct values across 67 pages |
|---|---|
heading |
67 |
body |
67 |
label |
1 |
href |
1 |
Sixty-seven headings, no two alike. Sixty-seven bodies, no two alike, between 82 and 172 characters, averaging 124. One button label on all of them, Start free, no card needed. One destination on all of them, /signup.
The headings are instructions more than questions. Thirteen begin with "Run", nine with "Try", six with "Build". Exactly one ends in a question mark, on the FAQ, where "Still deciding?" is a fair description of the reader. The pattern nobody planned is that the heading finishes the page's own argument: the question timer page closes with "Feel the pacing", the Kahoot comparison with "Built for a table of four", the music questions page with "Build a music round".
The split between what varies and what does not is the part I would defend. Heading and body are about the page, so they move with the page. Label and destination are about the offer, and the offer does not change because somebody arrived through a comparison table rather than a guide. A button whose wording shifts from page to page teaches a returning visitor nothing. Fifty-three of the 67 bodies mention the trial anyway, in the page's own words, which is where that variation belongs.
The site does not hold that line everywhere. The homepage has six links to /signup with four different labels on them:
$ curl -s https://pub-trivia.app/ | grep -o 'href="/signup">[^<]*' | sort | uniq -c
1 href="/signup">Get started
1 href="/signup">Sign up
1 href="/signup">Start free, no card needed
3 href="/signup">Start free trial
Header, footer, hero, two plan cards and the closing block. Five labels in total across the site for one destination. The 67 pages are the disciplined part by accident: they agree because the label sits in each page's props and got copied, not because anything enforces it.
What to do with a default that never fires
Three options, and I do not think the choice is obvious.
Keep it. It costs four lines, and page 68 gets something reasonable if its author forgets. That is the case for defaults in general, and it is why I wrote it.
Delete it and make cta required. The type loses its ?, and the compiler asks page 68 the question instead. This is what the code already believes: 67 out of 67 is not a majority, it is a rule. The closing line is the highest-value piece of copy on a marketing page, and "whatever the component says" should not be a decision anybody can make by omission.
Keep it, but not that copy. If the fallback is a safety net, it should say the thing that is true on any page, not the thing that works on the landing page. "Ready for quiz night?" assumes the reader came in through a page about running a quiz. Somebody who landed on the scoring systems guide from a search for scoring systems is not ready for anything yet, and that is the one case the net is for.
My own preference is the second, because the default's real status is that of a commented-out block: it sits in the repository, it reads as though it runs, and the first time anybody sees it in context would be on a live page. It is not changed yet, so for now the repository still carries a fifth copy of the homepage's closing pitch.
There is a smaller lesson underneath. Writing cta?: with a ?? behind it is a claim about how often the fallback gets used, and I never checked the claim. One grep -rl settled it in a second. Any optional prop with a fixed, countable set of call sites can be audited the same way, and the answer is either "this default is load bearing" or "this default is fiction".
To see the 67 of them, the hubs are the quickest route: guides, features, solutions, comparisons and the free tools. Bottom of every page, same button, different sentence above it.
Top comments (0)