Dev.to WebDev 🛠 Dev 👁 0 📖 8 min read

One 47,913 byte TypeScript file builds five pages, none of it reaches the browser, and one required field is rendered nowhere

lib/tests/test-types.ts is 960 lines and 47,913 bytes. It contains five objects. Each one becomes a page: cogniprep.app/tests/numerical-reasoning cogniprep.app/tests/verbal-reasoning cogniprep.app/tests/logical-reasoni

lib/tests/test-types.ts is 960 lines and 47,913 bytes. It contains five objects. Each one becomes a page:

So roughly 9.5 KB of editorial content per page, written as a TypeScript object literal rather than MDX, a CMS, or a database row. Three things about that are worth a post: one thing the type system does well, one thing it cannot see at all, and one invariant that holds today and is one keyword away from breaking.

The interface is an editorial checklist

export interface TestTypeEntry {
  slug: string;
  label: string;
  h1: string;
  metaTitle: string;
  metaDescription: string;
  ogTitle: string;
  keywords: string[];
  cardBlurb: string;
  intro: string;
  quickFacts: [string, string];
  gameIds: string[];
  measures: TestTypeCard[];
  formats: TestTypeCard[];
  tips: TestTypeCard[];
  // ...
}

Every field is required. Which means a sixth page cannot ship without a meta description, without a card blurb for the hub, or without its list of practice material. The compiler is doing the job a content brief would otherwise do badly: there is no "we will fill that in later" state, because later does not compile.

This is the main argument for typed content over a markdown folder. Frontmatter is optional by default. An interface is required by default. If your content has a fixed shape with mandatory parts, the second one is telling the truth about the thing you are modelling.

Note that there are three separate title fields, which looks redundant and is not:

/** Page <h1>. Kept identical to metaTitle so the SERP snippet matches the page. */
h1: string;
metaTitle: string;
/** Shorter variant for Open Graph, where long titles are truncated harder. */
ogTitle: string;

Three surfaces, three truncation budgets, one decision each. The h1 and metaTitle are deliberately the same string, so that someone clicking a search result lands on a page whose heading matches what they clicked. You can check it: the numerical page's h1 is "Numerical Reasoning Test Practice" and its <title> is the same string plus the site name suffix. ogTitle is shorter, because a social card clips harder than a SERP does.

Collapsing these into one field is the common move and it costs you the ability to make them differ when they should. Keeping them separate with a comment explaining that two of them are currently identical is strictly better than the comment being implicit in a single field.

The pages are an arrangement, not new content

The module's own header explains why this cluster exists:

Every other public page on the site is keyed to a brand: a provider hub or an employer. That captures candidates who already know which test they have been invited to. It misses the much larger group who only know the format. The practice content is not new: the pages assemble existing catalogue entries across providers. That also means every test-type page links out to several provider hubs, which is the point.

Two useful ideas in there, neither of which is about TypeScript.

The first is that the same catalogue supports two orthogonal URL families: one keyed by vendor, one keyed by format. Neither is a duplicate of the other, because they answer different questions a visitor arrives with. The entry lists gameIds with this constraint attached:

/**
 * Ordered by how representative the test is of the format, not by provider.
 */
gameIds: string[];

Ordering by provider would be the easy default and would make the page an index. Ordering by representativeness makes it an answer.

The second is that a cluster of derived pages is an internal linking structure as much as a content structure. Each format page points at several vendor pages, which is the direction you want authority flowing when the vendor pages are the ones already ranking.

A component that renders nothing

The link block between the two families is one small component, and its best property is that it has no conditional at its call sites:

const types = provider
  ? TEST_TYPES.filter((entry) =>
      entry.gameIds.some((id) => getGameById(id)?.provider === provider)
    )
  : TEST_TYPES;

if (types.length === 0) return null;

With the reason written on the prop:

Providers whose catalogue is entirely game-based match nothing and the section renders null, so the component is safe to drop into every provider page unconditionally.

Some providers publish no conventional format tests at all, so for those pages there is genuinely nothing to link to. The choice is between every call site asking "does this provider have any", or the component answering that itself and returning null.

Pushing the emptiness check inside is almost always right, and the reason is not tidiness. A condition at the call site is a condition that can be wrong at one call site. Forty pages each deciding whether to render a section is forty chances to decide differently; one component deciding is one.

The field that is rendered nowhere

/**
 * Two short format facts, shown as chips beside the derived test and provider
 * counts. They carry the detail the lead paragraph used to spell out in prose.
 */
quickFacts: [string, string];

There are no chips. The pill badges this field was written for were removed from the site, and the data was not.

This is checkable from outside, which is why I am comfortable asserting it. One of the values is the string Calculator usually allowed. Open cogniprep.app/tests/numerical-reasoning, view source, and search for it. It is not there. It is not in the server-rendered HTML at all, which rules out it being hidden by CSS.

So every entry still carries two short strings that nobody will ever read, and the type makes them mandatory. [string, string] is a tuple, so it is not merely required, it requires exactly two. Any sixth test-type page added next year will have to invent two facts about a format in order to compile, and they will go nowhere.

Three things worth taking from that:

A required field is a standing obligation on every future author. Deleting a UI is a one-file change. Deleting the data the UI consumed is a decision someone has to make separately, and the diff that removes a component does not look like it is leaving anything behind.

The type system cannot see this. Nothing about quickFacts is unused in the TypeScript sense. It is assigned in five places and declared once, so it is read, written and perfectly typed. "Declared, populated, never rendered" is invisible to the compiler and to lint, because the gap is between the data layer and the JSX, and no tool is looking across that gap.

The comment is the most wrong part of the file. It describes chips that existed, beside counts that are still derived, carrying detail from a lead paragraph that was rewritten. Every clause of it was true once. A stale comment is worse than no comment, because it will be believed, and the next person to read it will go looking for a rendering bug.

If you want to catch this class of thing, the check is not a type and not a lint rule. It is a test that each content field appears somewhere in the rendered output of the page that owns it. That is a slightly odd test to write and it is the only one that would have caught it.

47,913 bytes that never reach a browser

The last property is the one I actually went and measured, because the interesting claim about a large content module is always whether it ships to the client.

It does not. On cogniprep.app/tests/numerical-reasoning there are 25 script chunks totalling 2,308,866 bytes of JavaScript, and not one of them contains Calculator usually allowed, Scored on fit, not accuracy, or the string quickFacts.

You can run the same check on any page of your own, from the console:

const needle = 'some string only in your data module';
const srcs = [...document.querySelectorAll('script[src]')].map(s => s.src);
const hits = [];
for (const src of srcs) {
  const text = await (await fetch(src)).text();
  if (text.includes(needle)) hits.push(src);
}
console.log({ chunks: srcs.length, hits });

Now, the uncomfortable part. Why does it stay out?

Not because of a bundler setting. Not because of server-only. Not because of a build-time check. It stays out because all five files that import the module happen to be Server Components, and none of them has 'use client' at the top.

That is the entire mechanism. Add 'use client' to the component that renders the format links, for something as small as wanting a useState for a collapse toggle, and 47,913 bytes of editorial copy joins the client bundle of every page that renders it. Nothing fails. No warning appears. The page works perfectly and is 48 KB heavier.

The fix for that is the thing we do elsewhere in this codebase and do not do here: split the module so that the parts a client component could possibly need live in a file that imports nothing else, and the bulk lives behind it. Then a stray 'use client' is a compile error or a tiny import rather than a silent payload.

An invariant that holds by coincidence is not an invariant. It is a measurement with a date on it, and the date is today.

Summary

property enforced by how it breaks
every page has full metadata the interface cannot, it will not compile
h1 matches the search snippet a comment silently, on any edit
the section hides itself when empty the component cannot, no call site decides
quickFacts reaches a page nothing already broken
the data stays server-side nothing one 'use client'

The useful read of that table is the right-hand column. Two of these five properties are guaranteed, and three are currently true. Knowing which is which is the whole value of having looked.

The five pages are public, and the hub listing them is at cogniprep.app/tests.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.