Content Taxonomy
This is the live categorization model for opshell.dev content. It exists to give every pillar a clear cluster boundary (SEO), a definitional hub (AEO/GEO), and a monetization destination — without letting content types leak into the topic tree.
Two axes, never one
| Axis | Taxonomy | Purpose | URL effect |
|---|---|---|---|
| Topic | category (hierarchical, max 2 levels) | What the post is about; drives hub/pillar/cluster | Yes — /%category%/%postname%/ |
| Format | content_format (flat) | What kind of post it is (review, tutorial, runbook…) | None in v1 (archives off) |
A third category level is deliberately forbidden: it would break the 2-level URL rule and
create orphaned thin archives. Content types (review, news, tutorials) belong to the
format axis, not the category axis.
Category axis — 8 pillars
Assign only the deepest sub-category to a post. WordPress expands %category% to the
full path, so a single assignment yields /pillar/sub-category/post-slug/.
linux (7) vps, ssh, security, systemd, storage, monitoring, networking
devops (6) docker, nginx, openlitespeed, cloudflare, syncthing, cloud (planned)
backups (4) rclone, restic, rsync, scp
automation (6) cron, n8n, ansible, python, git, api
cli (8) bash, tmux, fzf, search-tools, neovim, yazi, visidata, kitty
ai (1) opencode
wordpress (4) wp-cli, performance, php, migration
databases (4) mysql, postgresql, sqlite, duckdb
Each pillar should map to a monetization destination:
| Pillar | Service page |
|---|---|
linux | Managed Server, Shared Hosting to VPS Migration |
devops | Docker & Self-Hosting, Self-Hosted App Installation |
backups | Backup and Recovery Setup |
automation | DevOps Automation |
wordpress | WordPress Speed Optimization |
cli, ai, databases | no service page yet — membership/funnel content |
Governance rules
- Maximum depth:
pillar / sub-category / post-slug. - One primary category per post; tools and entities go to tags, never to categories.
- A sub-category needs 3+ planned posts before it is created. It may be created in advance for a documented content cluster, but its empty archive must remain noindex (or otherwise excluded from indexing) until it has 3+ useful published posts.
- Every indexed category carries a description — that description is the hub copy that AEO/GEO engines read.
webboulder_authority_topic(the reader-path sidebar taxonomy) must mirror these branch names rather than invent its own.
Planned category: devops/cloud
Cloud (slug cloud) is reserved under the DevOps pillar for a future, distinct cluster
of cloud infrastructure content. It was created ahead of publication to support content
planning; it is not a synonym for Cloudflare and should not be used for Cloudflare-specific
posts. Keep its archive noindex until it contains at least 3 useful posts. Before the archive
is indexed, add its category description/hub copy and confirm the planned posts form a
coherent cluster mapped to the DevOps service destination.
Format axis — content_format
Registered by snippet 6-opshell-content-format.php. Flat, non-hierarchical, admin column
enabled, available in the block editor. Archives are off in v1 (publicly_queryable
false, rewrite false) so no thin /format/... URLs are created.
| Term | Use it when |
|---|---|
review | Hands-on verdict: scorecard, pros/cons, who it fits |
news | Release notes, advisories, version updates |
comparison | A vs B with a decision table and trade-offs |
benchmark | Measured timings/resource use with the method disclosed |
tutorial | Step-by-step walkthrough ending in a working result |
runbook | Operational procedure for a real situation |
checklist | Ordered, tickable pre-flight or audit list |
troubleshooting | Symptom → cause → fix, quoting the error string |
cheatsheet | Quick key/command card |
reference | Exhaustive options/flags listing |
case-study | Before → action → measured outcome |
faq | Short direct Q&A for People Also Ask and AI citations |
Each term's description is the editorial rule for when it may be used — do not create format terms without one.
The Review category (commercial hub)
Review is a root-level category (term 144), separate from content_format's
review term: the category is the commercial review hub, the format term labels a post's
type.
- Intent: commercial queries — "VPS review", "restic vs rclone", "best cheap VPS".
- Children are added manually in wp-admin (Posts → Categories → Parent:
Review), one segment per buyer decision:hosting-providers,vps-providers,backup-tools,devops-tools,cli-tools,database-engines,wordpress-plugins,self-hosted-apps,ai-tools,security-tools. - Slug collisions:
hosting,vps,databases, andsecurityalready exist as category slugs. WordPress would silently append-2to a duplicate, so use the compound slugs above. - Assignment rule: topic pillar stays the primary category (the URL stays inside
the topic cluster) and
Reviewis added as a secondary category so reviews still collect in the/review/hub. SEOPress's primary-category control decides which one drives%category%.
Permalink facts (verified)
- Structure:
/%category%/%postname%/,category_baseis empty. - Category archives therefore resolve at root level:
/linux/,/review/— there is no/category/prefix in practice. %category%resolves to the lowest term ID unless SEOPress's primary category overrides it (verified: post 1 inAutomation(8)CLI(7)Linux(6) renders/linux/ops-from-the-shell/)./category/linux/also returns 200 for the same archive — a soft duplicate that needs a 301 (/category/*→/category/*stripped) before launch.
Legacy cleanup backlog (not yet done)
92 of 93 published posts still sit in legacy terms. Nothing has been reassigned:
| Term | Posts | Planned action |
|---|---|---|
data-analyst | 67 | reassign to cli/visidata, then delete |
Tutorials | 14 | format, not topic — reassign then delete |
Uncategorized | 7 | reassign (default category: change default first) |
Themes | 2 | off-brand — trash the posts or reassign |
Beginners Guide, Hosting, SEO | 1, 1, 0 | delete |
Also pending: post 9512 is a duplicate of 9511 (slug carries a
sync-conflict-20260618 suffix); no post has been assigned a content_format yet.
Launch blockers (SEO)
The site is in development behind Cloudflare Access, so these must be resolved before any authority accrues:
- No crawler access — the Access app has only an email allow policy and an IP bypass. Public content needs a bot bypass or Access removed at launch.
- Hard paywall — SureMembers group
9572redirects logged-out visitors away from all 67 VisiData posts (excerpt=0,in_content=0). Those posts cannot be indexed in that state; switch to partial preview. - Duplicate source of truth — the same docs are public at
*.id86.net. Pick a canonical host and noindex the other. - Duplicate category URLs — add the
/category/*301s above.
WP-CLI reference
SITE=/home/wp_opshell_dev/sites/opshell.dev
TAX=category
# resolve a parent BY SLUG (never omit --by=slug: default lookup is by ID and returns empty)
wp term get "$TAX" linux --by=slug --field=term_id --path="$SITE"
# create with a resolved parent
wp term create "$TAX" "Restic" --slug=restic --parent=<id> --porcelain --path="$SITE"
# audit the tree
wp term list "$TAX" --fields=term_id,name,slug,parent,count --format=table --path="$SITE"
# formats
wp term list content_format --fields=term_id,name,slug,count --format=table --path="$SITE"
Reusable scripts for rebuilding the tree and the format terms live in the record
ai-records/20261003-02--opshell-taxonomy-backup-page/scripts/.