Skip to content

A Taxonomy Is a Commitment, Not a Filing Convenience

Categories, subcategories and tags look like three flavours of the same idea. They are three different promises about permanence, and confusing them is how a site ends up with two answers to what a page is about.

Every content system eventually grows a second way to classify things. It starts reasonably: the categories are too coarse, so somebody adds tags. Then the tags are too flat, so somebody adds sections. Then there are three taxonomies, two of them own URLs, and no page can say what it is about without contradicting itself somewhere.

The way out is to be clear about what each layer actually promises.

Three layers, three promises

A category is a URL you are never allowed to break. It is the site’s structure, visible in navigation, indexed, linked from outside, and expensive to change. A category is a commitment on the order of years. The correct number of them is small, and the correct way to add one is reluctantly.

A subcategory is a curated refinement that also owns a page. It exists because a category got deep enough that readers need a second rung, and it earns its existence by having something to say — a name, a description, a page worth landing on. If you cannot write two sentences about it, it is not a subcategory.

A tag is an observation, not a decision. It appears because a piece needed one and disappears when the last piece carrying it is unpublished. It is cross-cutting by design: a tag is precisely the thing that does not fit the hierarchy, which is why forcing it into one destroys it.

The rule that follows: the first two are curated and modelled; the third is derived.

Why tags should be derived and the others should not

This is the decision people get backwards most often, usually by making all three the same kind of thing — three collections, three sets of records, three admin screens.

A category and a subcategory need to be records because they carry data beyond their own name. A description, a parent, a sort order, a colour, a page. Making them records also makes references to them checkable: a typo becomes a build failure rather than a second near-identical hub with one item on it, discovered months later by a reader.

A tag carries nothing beyond its own name. A tag record is a file whose entire content is the string already written in the article’s frontmatter. Build a collection for it and you get a directory that is mostly stubs plus a second job — pruning orphans — that nobody does.

So derive them from the content, and pay for the one thing you lose (typo protection) with a single assertion:

if (slugOf(a) === slugOf(b) && a !== b)
  fail(`tags "${a}" and "${b}" both slugify to /tags/${slugOf(a)}`)

That is the whole cost. Two spellings that would silently merge into one page now fail the build, and you did not have to maintain a file per tag to get it.

The parent relationship has to be data

If a subcategory exists, it belongs to exactly one category, and that fact belongs in the record rather than in a naming convention.

The reason is a specific bug. With a free-string subcategory, category: "ai" and subcategory: "technical-seo" is perfectly representable. It renders a breadcrumb that lies, a URL under the wrong parent, and a hub listing an article that does not belong to it. Nothing catches it, because there is nothing to catch it with.

With a modelled parent, the check writes itself:

if (article.subcategory && sub.parent !== article.category)
  fail(`${file}: subcategory "${sub.id}" belongs to "${sub.parent}", not "${article.category}"`)

The general principle: a relationship you can express is a relationship you can validate. Conventions cannot be validated, which is why they are eventually violated.

One taxonomy across every content type

The last piece, and the one with the largest long-term payoff.

If a site publishes several kinds of thing — articles, events, answers, tools — they should hang off the same categories. Not parallel taxonomies that happen to use similar words.

The temptation to fork is real, because each content type has slightly different natural groupings. Resisting it buys two things that are worth more than the fit:

  1. A hub becomes a real place. /ai can show articles, events and answers about AI, because all three point at the same record. With two taxonomies it can show one of them and link to the others.
  2. There is one answer to “what is this about”. Two taxonomies means two answers, which means two sets of URLs competing for the same queries, and a permanent reconciliation job for whoever notices they have drifted.

The cost is that some content sits in a category that is not a perfect fit. That is a much smaller problem than it feels like when you are designing the model — and considerably smaller than the alternative, which is discovering in year three that /ai and /artificial-intelligence are both live, both indexed, and both half-populated.

What to decide up front

Not the list of categories — that is the easy part and it will be roughly right.

Decide the rules: how many rungs, which rungs own URLs, which layer is curated and which is derived, and what makes a reference invalid. Those decisions are cheap now and structural later, because everything you publish from here on will be filed under them.

Keep reading

More on SEO

Every vertical on this site carries its own writing, its upcoming events and its answers on one page — and the whole archive is available as a feed with a real publication date on every item.