We shipped a reference page last week: the label words that mean a gluten grain, graded into three groups, each word linking to its own page. It exists because "hidden gluten" and "what ingredients contain gluten" are searches that no page about a single ingredient answers. What they want is the vocabulary, which we happen to hold as data.
It is also, structurally, a marketing page about a condition where a wrong impression has consequences. That combination is the whole subject of this post.
Copy may be clearer than the app, never kinder
The app puts a standing line on every verdict for this condition, stating what it read and what it will not claim. The rules change, the line changes with them, and it is maintained next to the code that produces it.
A page like this one is the single most likely place for a reader to meet a softer version of that line, for a reason that has nothing to do with carelessness: page copy is written once, at launch, by somebody holding the marketing brief, and every subsequent paraphrase drifts in the optimistic direction, because optimistic copy reads better. Nobody rewrites a caveat to make it heavier.
So the page does not have its own version. It asks:
/**
* The standing line the app puts on every coeliac verdict, read off the engine
* instead of retyped. A reference page that stated the limits of the data more
* softly than the app does would be the one place a reader met the optimistic
* version.
*/
export const COELIAC_BANNER: string = (() => {
const result = fitCheck(
{
barcode: '0000000000000',
ingredientsTags: ['en:rice'],
statesTags: ['en:ingredients-completed'],
} as never,
{ conditions: ['coeliac'] },
);
return result.perCondition[0]?.banner ?? '';
})();
That is a real call into the rules engine, with a fabricated product, at module scope. The page is dynamic = 'error', so this runs once during the build and the result is baked into static HTML. The last paragraph of the page's "Where this list stops" section is a string the product produced, not a string anybody wrote for the web.
Two things I do not like about it
I am not going to pretend this is clean.
?? '' is a lie detector that has been switched off. If the engine ever stops emitting that line for this condition, the page renders an empty paragraph and the build is perfectly happy. The only honest fallback at build time is to throw: a page whose job is to state a limitation must not be publishable without the limitation. That is a two-line change and it should be made.
as never is worse. It is there because the engine takes a normalised product and I am handing it a hand-written object with three fields, so the cast is doing the work a test double would do, in content code. The real fix is for the engine to expose the standing line for a condition as something you can ask for without inventing a product. Until then, a cast with a comment beats a paraphrase without one, which is the trade I actually made.
Both of those are worth writing down because the pattern is good and the implementation is the first draft. Pulling a sentence out of the running product at build time is the right instinct. Pulling it out through a fake product and swallowing the failure is just the quickest way to get there.
Nothing on the page says the same thing twice
Two smaller instances of the same rule on the same file.
The lede says how many words the page covers. That number is GLUTEN_WORD_COUNT, reduced over the groups, which are themselves filtered out of the ingredient index. Nobody types it, so adding a word to the data updates the sentence, the three group headings and the list in one commit. (We learned this one the hard way with a different count, which I wrote about separately: the word "seven" was typed into twenty places and a test asserted it.)
The structured data does the same with its own description:
{
'@type': 'Article',
headline: 'Hidden gluten: the label words that mean a grain',
// The same sentence the metadata carries, read back off it rather
// than written twice, so the two cannot drift.
description: String(metadata.description),
url: absoluteUrl('/hidden-gluten-ingredients'),
}
A JSON-LD description and a meta description that disagree is a small thing, and it is also two statements about one page, one of which will be stale. Reading the exported metadata back into the graph costs one line.
The one place where DRY loses
Which makes the exception interesting. The page's metadata is deliberately written out as string literals:
// Written out here rather than pulled from a constant: lib/__tests__/seo-meta
// reads these literals out of the source to check them against what a search
// result can show, and a constant is invisible to it.
export const metadata: Metadata = pageMetadata({
title: 'Hidden gluten on a food label',
description: 'The label words that mean a gluten grain, graded: ...',
path: '/hidden-gluten-ingredients',
type: 'article',
});
Every page on the site is written to a title and description budget, because a title over about 60 characters and a description over about 160 get cut mid-sentence, and on these pages the tail is the part carrying the answer. The generated pages are checked through the functions the routes call. The hand-written ones cannot be: importing a server component under node --test pulls in React, next/navigation and the database client, and none of that loads.
So the test reads the page files as text and regexes the first string literal assigned to title and description inside the metadata block. Which means a constant, the thing you would normally reach for, makes the value invisible to the checker. The literal is the testable form.
The part that makes it safe rather than merely clever is that a page with no literal metadata at all is not quietly skipped. There is a named set of routes that are known to have none, each with a comment saying where its metadata actually comes from, and a page that matches neither case fails:
assert.ok(
ROUTES_WITHOUT_LITERAL_META.has(route),
`${route}: no literal title or description, and not listed as expected`,
);
A grep-based check that silently passes on anything it cannot parse is not a check. Making the unparseable case an explicit allowlist is what turns it into one.
Go and read the bottom of the page
The reference page is here. The last paragraph before "Three different reasons to avoid wheat" is the sentence the engine produced. The condition guide says the same thing in its own words, which is the one place I have accepted a paraphrase, because a guide that only quoted the app would not be a guide.
Top comments (0)