A Taxonomy Is a Commitment, Not a Filing Convenience
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:
- A hub becomes a real place.
/aican 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. - 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.
Read next
The whole archive-
SEO
The Canonical Tag Is a Claim About Identity
Most canonical bugs are not tagging mistakes. They are a site accidentally telling search engines that all of its pages are the same page — and the defaults in every framework make that the easy thing to do.
-
Development
Timezones Are Not a Formatting Problem
A calendar day and an instant in time are different types, and most date bugs are the moment one is silently converted into the other. The fix is not better formatting — it is refusing the conversion.
-
SEO
Structured Data Is a Contract, Not a Checkbox
Schema.org markup is usually treated as an SEO garnish you add at the end. It is better understood as a set of machine-readable promises about a page — which means the interesting question is what happens when the page stops keeping 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.