DEV Community

Daniel Pertu
Daniel Pertu

Posted on

88 words on one page, 88 links out of it, 88 pages that could vanish without a sound

Munchable publishes 516 URLs and writes none of them by hand. One of them is a reference page, munchable.app/hidden-gluten-ingredients, which exists because "hidden gluten" and "what ingredients contain gluten" are searches that no page about one ingredient can answer: the thing being asked for is the vocabulary, graded.

Count what is on it:

curl -s https://munchable.app/hidden-gluten-ingredients | grep -oE 'href="/is-[^"]+"' | sort -u | wc -l
# 88
Enter fullscreen mode Exit fullscreen mode

Its three section headings print their own sizes, 68, 13 and 7, which come from the data rather than from a literal. And the number of URLs in the sitemap whose slug ends in -gluten-free is also 88. One head-term page, 88 long-tail pages, and a link from each word to its own page. Nothing in that arrangement was typed twice.

Which is the good news and the entire problem.

A derived page does not break. It disappears.

If a page is a file, a mistake in it is a build error or a 500. If a page is a row that a function produced from a data module, a mistake in it means the row is not produced, and a missing row is indistinguishable from a page that was never meant to exist. The build is green. The sitemap is smaller. Nothing logs. You find out when you notice that a search you used to rank for goes somewhere else.

The spine of this site is a list of label words, each of which is a promise that a page exists for it. That promise can go quietly dead in at least four ways, and each one has a test.

The word is not a real identifier. A typo, or an id renamed upstream, and the word no longer refers to anything. It does not throw, it resolves to nothing:

it('every promised label word is a real taxonomy id', () => {
  const failures = COELIAC_LABEL_WORDS.map((word) => canonicalizeTag(`en:${word}`)).filter(
    (id) => !isKnownTag(id),
  );
  assert.deepEqual(failures, []);
});
Enter fullscreen mode Exit fullscreen mode

assert.deepEqual(failures, []) rather than a loop of assertions, because a run that names every broken word at once is worth four runs that each stop at the first.

The word is real but reaches no data. Different failure, same symptom. There is a separate test for it, because a word can be a perfectly valid identifier that nothing has anything to say about.

The page exists but answers nothing. The strongest of the four, and the one I would not have written first:

const page = answerPage(`is-${entry.slug}-gluten-free`);
if (!page) failures.push(`${word} has no answer page`);
else if (page.asMain.verdict === 'unknown') failures.push(`${word} answers unknown`);
Enter fullscreen mode Exit fullscreen mode

A page that renders, canonicalises, sits in the sitemap and says nothing useful is worse than a 404, because a 404 is at least a signal.

The page exists and should not. My favourite test in the file is the negative one. Some words appear on packaging and are not questions. Asking whether gluten is gluten free is not a question, and neither is asking it about something whose name already contains the answer. So five slugs are asserted absent:

for (const slug of [
  'is-gluten-gluten-free',
  'is-cereals-containing-gluten-gluten-free',
  'is-gluten-free-oats-gluten-free',
  ...
]) {
  assert.equal(slugs.has(slug), false, `${slug} should not be published`);
}
Enter fullscreen mode Exit fullscreen mode

Thin pages are easy to generate by accident when the generator is a cross product. The only defence is to name the ones you refuse.

The apostrophe

One assertion in that file looks like pedantry and is not:

const entry = INGREDIENTS.find((i) => i.tag === 'en:brewer-s-yeast');
assert.equal(entry?.name, "Brewer's yeast");
// The slug is a published URL and must not follow the name.
assert.equal(entry?.slug, 'brewer-s-yeast');
Enter fullscreen mode Exit fullscreen mode

The display name has an apostrophe, because that is how a label prints it and the page should read like the packet. The slug is a URL that search engines have already crawled, so it must keep the shape it was published with, even though a slugifier run over the name today might produce brewers-yeast and be prettier. Two fields, one source word, deliberately not derived from each other. The live page is /is-brewer-s-yeast-gluten-free, apostrophe flattened to a hyphen, which is slightly ugly and permanently correct.

The dynamic segment that must not be dynamic

All 461 question pages live at the site root: app/[question]/page.tsx. That is one segment away from being the worst route on the internet, and two lines stop it:

export const dynamic = 'error';
export const dynamicParams = false;
Enter fullscreen mode Exit fullscreen mode

Without dynamicParams = false, that route answers every unmatched top-level path on the domain. /pricing-2, /wp-admin, every typo in every backlink anybody ever wrote, all of them rendering something and all of them indexable. With it, only the generated slugs exist and everything else is a real 404. (The risk of that segment was its own post; this is the half about what fills it.)

Counted, not written down

The reference page's word count is derived:

/** How many label words the page covers, counted rather than written down. */
export const GLUTEN_WORD_COUNT = GLUTEN_WORD_GROUPS.reduce((n, g) => n + g.words.length, 0);
Enter fullscreen mode Exit fullscreen mode

Which is the right instinct, and I know it is the right instinct because the same repository has three comments claiming the site is about 190 URLs when the live sitemap has 516. A number in prose rots. A number in a reduce cannot.

The counts in those three headings on the live page, 68, 13 and 7, are the same expression rendered three times. If the data grows tonight, the page says so tomorrow and nobody edits a sentence. That is the only part of this architecture I would defend without qualification: the pages are a function of the data, the tests assert the function's domain rather than its output, and the one number a human types is the one in a test that says "this slug must never be published".

Start at /answers if you want to see the long tail, or /hidden-gluten-ingredients for the head term, and count the links yourself.

Top comments (0)