---
name: seo-content-optimization
title: SEO Content Optimization
description: Audit and optimize content sites for search — page title audits, schema injection (Organization, FAQ, LocalBusiness), meta description rewrites, URL hygiene, local landing pages, and measurement. Complements /content-strategy (what to write next) by optimizing what already exists.
domain: devops
---
# SEO Content Optimization
Audit-and-fix workflow for improving search rankings on Magnus's content sites. This is the "optimize what exists" counterpart to /content-strategy's "what to write next."
**Sites in scope:** magnus919.com (Hugo), rdumesh.org (Ghost), groktop.us (Ghost Pro), southeastme.sh (Ghost)
## Signals to Run This
- User says "we're not ranking for X" or "SEO is bad"
- User asks to audit a site's search presence
- User mentions poor Google visibility for local terms
- As part of site launch / relaunch
## Process
### 1. Audit page titles and meta descriptions
Fetch the site's sitemap to get all published URLs (see content-strategy skill for sitemap-fetching protocol — try bare domain, www subdomain, Ghost split-sitemaps).
For each published page:
- Check the `
` tag via curl
```bash
curl -s https://example.com/page/ | grep -i '
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Site Name",
"description": "Description with city and keyword signals.",
"url": "https://example.com",
"areaServed": [
{"@type": "City", "name": "City1", "sameAs": "https://en.wikipedia.org/wiki/City1,_NC"},
{"@type": "City", "name": "City2"}
],
"sameAs": [
"https://discord.gg/..."
]
}
```
**FAQ schema** — page-level. Enables rich FAQ results in search. For Ghost sites, use the ghost-cli `schema inject` command instead of post-level code injection — it's cleaner and doesn't require editing the post HTML:
```bash
ghost-cli --site rdumesh schema validate --json '{"@context":"...","@type":"FAQPage",...}'
ghost-cli --site rdumesh schema inject my-post --file /tmp/faq-schema.json
```
FAQPage JSON-LD structure:
```json
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Question text?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Answer text."
}
}
]
}
```
**TechArticle schema** — for hardware/software/technical blog posts. More specific than `Article` (which Ghost outputs by default), gives Google better content classification. Include `proficiencyLevel` and `about` for deeper signals:
```json
{
"@type": "TechArticle",
"headline": "Post Title",
"proficiencyLevel": "Beginner|Intermediate|Advanced",
"about": {
"@type": "Thing",
"name": "Topic name"
}
}
```
**Hybrid @graph pattern** — when a post benefits from multiple schema types (FAQPage + HowTo + TechArticle), wrap them in a `@graph` array in a single injection:
```json
{
"@context": "https://schema.org",
"@graph": [
{"@type": "TechArticle", "headline": "..."},
{"@type": "FAQPage", "mainEntity": [...]},
{"@type": "HowTo", "name": "...", "step": [...]}
]
}
```
This is cleaner than multiple separate JSON-LD blocks and ensures all types are processed together by Google.
**When to use each type:**
| Type | Article Content Signal | SERP Impact |
|---|---|---|
| `TechArticle` | Technical hardware/software content | Better classification, no visual rich result |
| `FAQPage` | 3+ natural Q&A pairs (headings + paragraph answers) | Expandable FAQ rich result (high CTR) |
| `HowTo` | Step-by-step procedures, CLI commands, build instructions | Step preview rich result |
**Fallback for non-Ghost sites:** For Hugo sites (magnus919.com), schema is baked into static HTML at build time via Hugo partials — see the magnus919.com section below for implementation approaches. For Ghost sites, always prefer `schema inject` via ghost-cli over manual code injection.
### 4. Rewrite page titles (meta_title, not H1)
In Ghost CMS, page titles and H1s are independent. Set `meta_title` via the Admin API or Ghost post settings — it controls the `` tag and what shows in search results. The visible H1 on the page stays unchanged.
**Pattern:** Primary keyword near front | pipe-separated site name suffix. For local sites, include city names. For Ghost sites, use the ghost-cli to set these:
```bash
ghost-cli --site rdumesh meta set \
--meta-title "Primary Keyword Context | Site Name" \
--meta-description "150-160 char summary with keyword and call to action."
```
| Type | Format | Example |
|------|--------|---------|
| Local landing | Keyword + city + site | "Free Mesh Network for Raleigh, Durham & Chapel Hill, NC | RDUMesh" |
| Technical guide | Keyword + site | "915 MHz Pine Tree Attenuation: What LoRa Mesh Operators Need to Know | RDUMesh" |
| Hardware guide | Product + keyword + site | "Heltec V4.3 Solar Repeater: Power Consumption & EasySkyMesh Firmware | RDUMesh" |
| FAQ | Keyword + cities + site | "Mesh Radio Network FAQ: RDUMesh for Raleigh, Durham, Chapel Hill" |
| Announcement | Topic + site | "Chapel Hill Mesh Network Expansion — RDUMesh Backhaul Case Study" |
Set via ghost-cli `meta set` for Ghost sites (see ghost-cli skill) or via Hugo template variables for magnus919.com.
### 5. Audit and optimize URL slugs
In Ghost CMS, slugs can be changed independently of titles and the ghost-cli doesn't have a dedicated slug command — use the Admin API directly (see ghost-cli skill's "Slug Optimization" section for the pattern). Issues to flag:
- `-2` suffixes (signal duplicate content) — fix slug, ensure 301 redirect from old URL
- Excessively long slugs (>55 chars) — shorten by removing filler words (why, what, the, a, of, and, how, for)
- Ghost auto-generates slugs from the initial post title, so if the title changes later, the slug doesn't update
- Target 30-45 characters, leading with the primary keyword
- OK to change before publish. Changing after publish requires a 301 redirect.
### 6. Create local landing pages (if applicable)
For sites serving specific geographic areas, create dedicated short pages (500-800 words) per city/region. These don't need to be deep essays — they exist to tell Google "this site serves this place."
**Ghost CMS: always create as Pages, not Posts.** Pages don't trigger email notifications to subscribers. Posts do.
Each should include:
- City name in H1 and first paragraph
- 2-3 paragraphs of substantive local content (not keyword stuffing)
- Link to the relevant coverage map or router page
- CTA to join/participate
- Internal links to 2-3 other relevant posts
**Ghost Admin API pitfall — Ghost v6 uses Lexical format, not raw HTML.** When creating pages via the API, sending `"html"` in the payload does NOT store the content. You must provide `"lexical"` as a JSON string in Ghost's Lexical editor format. The `html` field is read-only — derived from Lexical on the backend.
Minimal Lexical page structure for a short page with paragraphs and headings:
```python
lexical = json.dumps({
"root": {
"children": [
{
"children": [{"detail": 0, "format": 0, "mode": "normal", "style": "", "text": "Paragraph text.", "type": "text", "version": 1}],
"direction": "ltr", "format": "", "indent": 0, "type": "paragraph", "version": 1
},
{
"children": [{"detail": 0, "format": 0, "mode": "normal", "style": "", "text": "Section Heading", "type": "text", "version": 1}],
"direction": "ltr", "format": "", "indent": 0, "type": "heading", "tag": "h2", "version": 1
}
],
"direction": "ltr", "format": "", "indent": 0, "type": "root", "version": 1
}
})
```
For ordered lists, use `"type": "list", "listType": "number"` with `"type": "listitem"` children.
### 7. Measure
- **Immediate:** `curl | grep ` to confirm titles deployed
- **Schema validation:** Google Rich Results Test (https://search.google.com/test/rich-results)
- **Crawl:** Google Search Console after 1-2 weeks
- **Ranking:** Manual spot-checks for targeted queries (incognito browser)
## Site-Specific Notes
### rdumesh.org
- Self-hosted Ghost on Ubuntu. Admin API at same domain as public.
- Biggest gap: city-specific landing pages and schema with areaServed.
- FAQ schema on the FAQ page is a high-impact copy-paste.
### groktop.us
- Ghost Pro behind custom domain. Admin API at admin domain `groktopus-hybrid-workforce.ghost.io`.
- Enterprise AI audience — local SEO doesn't apply.
- Focus on meta title optimization and Article schema completeness.
### magnus919.com
- Hugo static site (Terminal theme v4). **Currently emits zero JSON-LD.** The Terminal theme's `head.html` partial does not include any schema markup.
- Hugo has no built-in runtime schema injection (unlike Ghost's `ghost_head`), but several zero-cost approaches exist:
| Approach | Effort | Output |
|----------|--------|--------|
| **Hugo internal template** (`{{ template "_internal/schema.html" . }}`) | Add one line to `head.html` | Basic WebSite (home) + Article (pages) with Organization, dates, author |
| **Custom partial** (`layouts/partials/schema.html`) | Write a Handlebars partial with per-page-kind conditionals | Full control over @type, fields, and which pages get which schema |
| **PaperMod-style partial** (see `_vendor` for reference) | Adapt existing schema_json.html from vendored PaperMod theme | Organization + BreadcrumbList + BlogPosting with articleBody, wordCount, keywords |
Unlike Ghost's injection-based approach, Hugo schema is baked into static HTML at build time — no runtime overhead, but changes require a rebuild.
- Site already has good organic content depth; focus on page title optimization and internal linking.
### southeastme.sh
- Nascent site — SEO work is premature. Focus on content strategy first.
## LLM-Friendly Content (/llms.txt)
An emerging complement to traditional SEO: making your site consumable by LLM agents during inference. While SEO targets Google's crawler, llms.txt targets the agents that visit your site looking for context.
**The standard:** A markdown file at `/llms.txt` providing a curated, concise overview — H1 title, blockquote summary, and H2 sections with link lists. The `## Optional` section can be skipped for tighter context.
**How it differs from SEO:**
- SEO optimizes for ranking in search results (traffic goal)
- llms.txt optimizes for LLM comprehension (utility goal)
- Both require clear, concise content architecture
- llms.txt is far simpler — one markdown file vs. structured data + meta + backlinks
**Quick check when evaluating a site:**
- `/llms.txt` exists? Read it first.
- No `/llms.txt`? Check `/sitemap.xml` for full page list.
- Neither? The site forces LLMs to parse HTML — opportunity to create one.
See `references/llmstxt-standard.md` for the full standard reference — format spec, best practices, implementation approaches for Hugo/Ghost/Next.js, the `llms_txt2ctx` CLI tool, and content negotiation via `Accept: text/markdown`.
## See Also
- `references/llmstxt-standard.md` — full /llms.txt standard reference
- `ghost-webhooks` skill reference `admin-api-content-editing.md` — Admin API auth and update patterns
- `content-strategy` skill — what to write next (complements this skill)
- `hugo-blog` skill — Hugo-specific llms.txt implementation details (custom output formats, content negotiation)