Skip to main content

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​

AxisTaxonomyPurposeURL effect
Topiccategory (hierarchical, max 2 levels)What the post is about; drives hub/pillar/clusterYes — /%category%/%postname%/
Formatcontent_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:

PillarService page
linuxManaged Server, Shared Hosting to VPS Migration
devopsDocker & Self-Hosting, Self-Hosted App Installation
backupsBackup and Recovery Setup
automationDevOps Automation
wordpressWordPress Speed Optimization
cli, ai, databasesno service page yet — membership/funnel content

Governance rules​

  1. Maximum depth: pillar / sub-category / post-slug.
  2. One primary category per post; tools and entities go to tags, never to categories.
  3. 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.
  4. Every indexed category carries a description — that description is the hub copy that AEO/GEO engines read.
  5. 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.

TermUse it when
reviewHands-on verdict: scorecard, pros/cons, who it fits
newsRelease notes, advisories, version updates
comparisonA vs B with a decision table and trade-offs
benchmarkMeasured timings/resource use with the method disclosed
tutorialStep-by-step walkthrough ending in a working result
runbookOperational procedure for a real situation
checklistOrdered, tickable pre-flight or audit list
troubleshootingSymptom → cause → fix, quoting the error string
cheatsheetQuick key/command card
referenceExhaustive options/flags listing
case-studyBefore → action → measured outcome
faqShort 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, and security already exist as category slugs. WordPress would silently append -2 to a duplicate, so use the compound slugs above.
  • Assignment rule: topic pillar stays the primary category (the URL stays inside the topic cluster) and Review is added as a secondary category so reviews still collect in the /review/ hub. SEOPress's primary-category control decides which one drives %category%.
  • Structure: /%category%/%postname%/, category_base is 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 in Automation(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:

TermPostsPlanned action
data-analyst67reassign to cli/visidata, then delete
Tutorials14format, not topic — reassign then delete
Uncategorized7reassign (default category: change default first)
Themes2off-brand — trash the posts or reassign
Beginners Guide, Hosting, SEO1, 1, 0delete

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:

  1. 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.
  2. Hard paywall — SureMembers group 9572 redirects 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.
  3. Duplicate source of truth — the same docs are public at *.id86.net. Pick a canonical host and noindex the other.
  4. 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/.