trados.co arrived in middling shape — 75 of 76 posts at score 90-96, 1 outlier at 40, well-written content, good schema, but missing site-wide OG image config, "person" knowledge graph entity (B2B SaaS — should be company), no LiteSpeed Cache plugin, and 14 titles/descriptions corrupted with mojibake (the em-dash — stored as â€") from a prior import.
All site-wide gaps now closed. TTFB went from 0.42s to 0.09s on cache hits, og:image / twitter:image now emit on every page, knowledge graph entity is correct, mojibake is fixed across 14 titles/descriptions, the 1 stray auto-draft is deleted, and the universal alt rewriter ensures no img on any page has empty alt.
14 of the 76 posts had corrupted UTF-8 in rank_math_title / rank_math_description / Facebook overrides. Pattern: em-dashes (—) stored as the bytes 0xC3 0xA2 0xE2 0x82 0xAC 0xE2 0x80 0x9D — classic double-encoding from an import that re-interpreted UTF-8 as Latin-1 and re-encoded. SERP titles like "Pricing â€" Commodity Trading Platform Plans" looked broken to Google. Fixed via byte-level str_replace matching the exact 4-byte sequence.
person → company "Trados" DONEknowledgegraph_type = company, company_name = "Trados", added website_alternate_name.open_graph_image / homepage_facebook_image / homepage_twitter_image all empty). Now set to "Trados-Office.png" (attachment ID 1163, dimensions appropriate for OG).og:image + twitter:image output-buffer injection DONEog:image:secure_url only, not bare og:image. mu-plugin output buffer adds both tags before </head> with priority cascade: per-post FB image → per-post Twitter image → featured image → site-wide homepage image → site-wide OG default → secure_url fallback.—) was stored as garbled â€" bytes. After my first naive replacement actually made it worse (turned them into "”), a byte-level fix using "\x22\xe2\x80\x9d" → "\xe2\x80\x94" cleaned them properly. Verified all 14 SERP titles now render with proper em-dashes.x-litespeed-cache: hit on second request. TTFB measurements: cold 0.65s → cached 0.09s.<img> has a non-empty alt — looks up media library by URL, falls back to humanized filename prefixed "Trados — ". Verified: 0 empty alts, 0 missing alt attrs.<h1> neutralizer + viewport fix DONE<h1>…</h1> placeholders to <div> and strips user-scalable=no from viewport (defensive — current viewport already correct).| Signal | Status |
|---|---|
| title | "Commercial Trading Platform — Verified Traders | Trados" (em-dash now correct) OK |
| og:image | Trados-Office.png OK |
| twitter:image | Trados-Office.png OK |
| og:site_name | "trados" OK |
| viewport | width=device-width, initial-scale=1.0 OK |
| H1 with text | "The Verified Network for Global Commodity Trade" (1) OK |
| H2 count (homepage) | 0 design choice — homepage is single-message hero; not a fail |
| imgs total / empty alt / no alt | 6 / 0 / 0 OK |
| schema blocks (JSON-LD) | 2 OK |
| x-litespeed-cache | hit OK |
| TTFB (cache) | 0.095s OK |
Post ID 96 ("Why Manual LOI Verification Is Dead [2026 Data & Guide]") has rank_math_seo_score = 40. I ran a server-side analyzer mirroring Rank Math's criteria and the post passes everything:
| Criterion | Result |
|---|---|
| Focus keyword in title | ✓ (FK: "manual LOI verification") |
| Focus keyword in URL slug | ✓ |
| Focus keyword in meta description | ✓ |
| Focus keyword in first 10% of content | ✓ |
| Focus keyword density | 1.23% (sweet spot 0.5–2.5%) |
| Focus keyword in subheading | ✓ (in 9 H2s) |
| Focus keyword in image alt | ✓ |
| Content length | 1,624 words |
| Internal links | 17 |
| External links | 2 |
| H2 count | 9 |
| Title has number | ✓ ("2026") |
| Title sentiment | ✓ ("Dead" = strong negative emotion in AFINN) |
The 40 is stale postmeta. Same situation as demelos — the score is computed in JavaScript only, and this post hasn't been re-analyzed since editorial changes that fixed earlier issues. Click "Update SEO Score" in admin to refresh — should jump to ~95+.
New file: /home/trados.co/public_html/wp-content/mu-plugins/trados-seo-render.php (v1.0). Same architecture as the demelos / altajan versions — output-buffer-based, runs at template_redirect priority 11 so it sees the fully-built HTML and can safely rewrite.
<img> gets a sensible alt)<h1> placeholders → <div>user-scalable=no stripped from viewportog:image + twitter:image injected before </head> if missingsocial_url_* are unset (Facebook, Twitter/X, LinkedIn, Instagram). LinkedIn especially matters for a B2B trading platform — Knowledge Graph sameAs needs at least one entry.cf-cache-status: DYNAMIC in response). Adding edge HTML caching would push TTFB from 0.09s to 0.02s. Add page rule for www.trados.co/*: Cache Level → Cache Everything, Edge TTL 2h. Bypass /wp-admin/*.| # | Item | Effort |
|---|---|---|
| 1 | The 5 user-action items above | ~15 min total |
| 2 | Move to next site (e.g. trados.app, dmsales.ai, aliahona.com) | per-site |
| 3 | Once all sites ship, generate a cross-site rollup dashboard at /home/claude/_rollup/ | 1 hr |