DEV Community

Daniel Pertu
Daniel Pertu

Posted on

We wrote our documentation twice: fifteen guide pages in the sitemap, and a 127-line help page that answers to a 307

Ask our app how a player joins a quiz and you get two answers from two files.

The one anybody can read is on the public site. Our FAQ carries sixteen questions, all sixteen emitted as FAQPage structured data, and the guides cluster is fifteen pages, every one of them in our sitemap alongside the other 57 URLs. Those pages are generated from a content registry: one node in one TypeScript array produces the page, its sitemap entry, its breadcrumbs, its footer link and the related links at the bottom. Twenty one tests check that none of it disagrees with itself.

The other one is app/dashboard/help/page.tsx. It is 127 lines, it is hardcoded, and it begins like this:

const faqs = [
    {
        q: 'How do I start a quiz night?',
        a: 'Go to Host, select a question pack and configure your session. Share the QR codes with your tables and press Start when everyone is ready.',
    },
    {
        q: 'How do players join?',
        a: 'Each table has a unique QR code. Players scan it with their phone to join the session, no app download required.',
    },
    // four more
]
Enter fullscreen mode Exit fullscreen mode

Plus four "quick guide" cards built from a second array, and a support email at the bottom. Six questions and four summaries, in a file with no relationship of any kind to the sixteen questions on the public page.

It cannot be read, linked, or found

Try it from a terminal:

curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" https://pub-trivia.app/dashboard/help
# 307 https://pub-trivia.app/login?next=%2Fdashboard%2Fhelp
Enter fullscreen mode Exit fullscreen mode

That is correct behaviour and three layers deep. /dashboard is in the Disallow list in our robots.txt, the dashboard layout carries robots: { index: false, follow: false } so that indexing is refused even if something crawls it anyway, and the auth gate answers an unauthenticated request with the 307 above.

So: it is not indexed, it is not crawlable, it cannot be linked in an email to someone who has not signed up, and nobody can read it while deciding whether to sign up. Which is a shame, because "how do players join" is the single question a landlord most wants answered before giving us a card number, and we have answered it in a place that requires the card number first.

The two copies have already diverged in tone

They have not contradicted each other yet, as far as I can tell, but they are no longer the same documentation.

The private page is written as reference, in the imperative, with proper nouns for our own navigation: "Go to Host", "Manage your tables under the Tables section", "The Packs section lets you browse public packs or build your own". It assumes you are inside the product, looking at the sidebar, and it describes where to click.

The public pages are written as answers to questions a stranger asked. The FAQ does not mention the Host screen at all. It answers what a venue needs on the night, whether a phone that cannot scan the code is a problem, what happens if the WiFi drops mid-question, and whether somebody can check a disputed score from last week. The hosting guide walks the evening in order rather than walking the interface.

Both are legitimate. They are different genres. The problem is that nothing says which genre owns which fact, so the next time a product detail changes there are two files that might need editing, and only one of them has a test suite, a sitemap entry and strangers reading it.

And then there are the email addresses

While I was in there I grepped for our own domain in outward-facing copy. Five addresses:

support@pub-trivia.app     FAQ page, help page, comparison hub
hello@pub-trivia.app       terms of service, checkout currency error
privacy@pub-trivia.app     privacy policy, cookie policy
noreply@pub-trivia.app     invite emails
forwarding@pub-trivia.app  inbound mail webhook
Enter fullscreen mode Exit fullscreen mode

Three of those are for humans to write to, and which one you are given depends on which page failed you. A host whose plan change is refused because their subscription is in a currency we cannot switch gets hello@. A host who reads the help page gets support@. Every address on the domain is a catch-all that forwards to one inbox, which is the only reason this has never lost an email, and "it works because there is one inbox" is a sentence worth noticing before there are two.

What I think the right shape is

Not deleting the help page. In-product help has a job the public site cannot do: it knows which plan you are on, which screen you are looking at and whether you have ever run a session.

What it should not do is restate facts that the public pages already state better and keep current. The version I would build is a help page that is almost entirely links out, with one line of context each, pointing at the public guides, plus the two or three things that genuinely need to know who you are. A reader loses nothing, because the public pages are the better writing, and the maintenance burden drops from two copies to one.

The other half of that is admitting what the hardcoded arrays are for. Six questions and four cards are a placeholder somebody shipped to fill a sidebar item called Help, and they have outlived the sidebar item by several months of content work on the public side. That is the normal fate of in-product docs: they are written once, under deadline, by whoever noticed the empty page, and then nothing ever brings a reader back to them with fresh eyes.

If you want the public half, the guides start from how to host a quiz night and run through formats, rounds, scoring and prizes, the FAQ is the short answers, and the session history feature is the one our private page describes in a sentence and the public one takes a page over, which tells you which of the two was written for a reader.

Top comments (0)