PHP Playbook: Yoast Video SEO for Rich Results
Yoast Video SEO Plugin — A Practical Video-Rich-Results Playbook for PHP/WordPress Engineers (LearnKu Edition)
Most sites treat video as decoration: drop an embed, center it, move on. Search engines don’t. They want structure (VideoObject), stable thumbnails, a video sitemap, chapters/seek actions, captions, and a page that won’t let the player crush LCP/CLS. The gap between “we embedded a player” and “we earned a video rich result” is where traffic leaks.
I rebuilt our pipeline around Yoast Video SEO Plugin and treated each video like a tiny product page: metadata, transcript, thumbnail discipline, crawl timing, and performance guardrails. For extras (captions, compression, cache headers), I curate from a small stack of WordPress Plugins. The playbook below is tuned for engineers: predictable discovery, consistent CTR, fewer “why didn’t it index?” mysteries. — gplpal
The five engineering responsibilities behind “video SEO”
Discoverability
- Fresh, split-friendly video sitemaps.
- Durable, high-contrast thumbnails (readable at tiny sizes).
Comprehension
- Outcome-first titles/descriptions.
- Human-edited transcript; chapters that map to real tasks.
Eligibility
- Complete VideoObject:
name
,description
,duration
,uploadDate
,thumbnailUrl
,contentUrl
/embedUrl
. - Clip/SeekToAction when you can.
- Complete VideoObject:
Consistency
- One canonical home; platform mirrors point back.
Speed
- Lazy-load the player; fixed dimensions to prevent CLS; captions as lightweight VTT.
End-to-end method (six stages)
1) Plan
- One-line search problem (“Troubleshoot X without special tools”).
- Decide the canonical page (stable URL, video-centric content).
- Draft chapters (time stamps + verbs).
2) Produce
- Audio first; scripts read like docs (short sentences, verbs up front).
- Record chapter breakpoints as you go.
3) Package
- Title ≤ 60–65 chars; promise + object.
- Description 2–3 lines; lead with outcome.
- Thumbnail legible at 160×90; 2–4 words; strong contrast.
- Transcript: fix nouns/verbs; keep error codes, settings, CLI flags.
4) Publish
- Place the video below the intro; poster first, player on interaction.
- Fixed width/height (or
aspect-ratio
) to avoid layout shift. - No autoplay;
preload="metadata"
.
5) Mark up
- Emit VideoObject JSON-LD (+ Clip/SeekToAction if suitable).
- Generate/refresh video sitemaps on publish/update.
6) Monitor
- Search Console: impressions on video surfaces, indexing latency.
- Web vitals: LCP/CLS/INP with and without the player.
- Engagement: chapter clicks, watch time, early drop-offs.
Where this plugin fits (automation “slots”)
Video sitemap
- Incremental refresh; split by post type for high-volume sites.
Schema fill/override
- Defaults from post fields; per-post overrides for
name
,description
,duration
,uploadDate
,thumbnailUrl
,contentUrl
/embedUrl
.
- Defaults from post fields; per-post overrides for
Provider-agnostic embeds
- Clean VideoObject for self-hosted or third-party players; standardize embed blocks.
Chapters (Key Moments)
- Editor maintains time + label; output Clip markup; align transcript headings.
Indexing hygiene
- Keep thin archives noindex; avoid splitting relevance.
PHP / WordPress practicals (LearnKu-friendly)
A. Safe lazy-load player (Blade-friendly sketch)
<?php
// $poster = poster image; $embed = iframe src (trusted)
// Use data-src to defer loading; mount on click or near-viewport
?>
<div class="video-slot" style="position:relative;aspect-ratio:16/9;">
<button class="video-play" aria-label="Play video"
style="position:absolute;inset:0;background:#000;">
<img src="<?= e($poster) ?>" alt="Video poster" width="1280" height="720"
style="width:100%;height:100%;object-fit:cover;" loading="lazy">
</button>
<template id="video-embed" data-src="<?= e($embed) ?>"></template>
</div>
<script>
(() => {
const slot = document.currentScript.previousElementSibling;
const btn = slot.querySelector('.video-play');
const tpl = slot.querySelector('#video-embed');
const mount = () => {
if (slot.dataset.loaded) return;
slot.dataset.loaded = '1';
const iframe = document.createElement('iframe');
iframe.src = tpl.dataset.src;
iframe.title = 'Video player';
iframe.width = '1280'; iframe.height = '720';
iframe.allow = 'accelerometer; encrypted-media; picture-in-picture';
iframe.allowFullscreen = true;
iframe.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;border:0;';
slot.appendChild(iframe); btn.remove(); tpl.remove();
};
btn.addEventListener('click', mount);
if ('IntersectionObserver' in window) {
new IntersectionObserver((es, io) => {
es.forEach(e => { if (e.isIntersecting) { mount(); io.disconnect(); }});
}, { rootMargin: '200px' }).observe(slot);
}
})();
</script>
B. JSON-LD: VideoObject with Clips/Seek
{
"@context": "https://schema.org",
"@type": "VideoObject",
"name": "Fix Slow LCP on WordPress in 10 Minutes",
"description": "Measure → placeholder + lazy-load → compress/cache. A practical, reversible workflow.",
"thumbnailUrl": ["https://example.com/media/wp-lcp-thumb.jpg"],
"uploadDate": "2025-05-09T10:00:00+08:00",
"duration": "PT8M36S",
"contentUrl": "https://example.com/media/wp-lcp.mp4",
"embedUrl": "https://example.com/embed/wp-lcp",
"potentialAction": {
"@type": "SeekToAction",
"target": "https://example.com/post#t={seek_to_second_number}",
"startOffset-input": "required name=seek_to_second_number"
},
"hasPart": [
{"@type": "Clip","name": "Measure baseline","startOffset": 0,"endOffset": 75},
{"@type": "Clip","name": "Placeholder & lazy-load","startOffset": 76,"endOffset": 240},
{"@type": "Clip","name": "Compress & cache","startOffset": 241,"endOffset": 516}
]
}
C. Nginx range/caching hints for scrubbable playback
location /media/ {
add_header Accept-Ranges bytes;
types { video/mp4 mp4; video/webm webm; }
expires 7d;
}
Page skeleton that earns the click
# H1: outcome promise (who / what / how fast)
Lead: two lines — who it’s for, what “success” looks like.
[Video slot: poster + lazy-loaded player]
## Section 1: Problem framing & metrics
- Baseline and tools
## Section 2: Steps (each actionable)
- Step A/B/C with code/config
## Section 3: Validate & rollback
- How to verify; safe rollback points
## Transcript (collapsible; still indexable)
## FAQ (3 short answers to next questions)
Titles, thumbnails, chapters — engineering constraints
- Titles: verbs + objects; drop filler; include constraints/numbers.
- Thumbnails: bold type, safe zones, grayscale check, tiny-size sanity test.
- Chapters: verb phrases that match transcript heads.
Performance guardrails (even with a player)
- LCP: poster first, iframe on interaction/near-viewport; avoid heavy hero above.
- CLS: fixed dimensions or aspect-ratio; no late layout jumps.
- INP: keep controls light; click-to-play under ~200 ms.
Team workflow (minimal roles)
- Author: title/lead/steps/transcript; chapter times.
- Editor: thumbnail legibility; tone/length polish.
- Engineer: template/lazy-load/sitemap/schema; vitals & Console checks.
- Ops: cadence, archival, quarterly review.
Weekly review metrics
- Video-surface CTR by query → rewrite titles/thumbnails for top two queries.
- Index latency → inspect sitemap freshness/internal links.
- First 15s drop-off → fix hook mismatch.
- Vitals with/without player → adjust placement/weights.
Pitfalls & fixes
- Auto-captions lie → human edit nouns/verbs.
- Many shorts → batch into thematic montage pages; avoid thin content.
- i18n → mirror structure; translate transcript/chapters; set hreflang.
- Bad thumbnails → enforce a template with contrast/size rules.
60-minute publish checklist
- 0–10: outcome title + two-line lead; chapter list.
- 10–20: 2–3 thumbnail candidates; tiny-size legibility test.
- 20–30: transcript pass; add time stamps.
- 30–40: embed (poster + lazy-load); fix dimensions; add captions.
- 40–50: fill VideoObject fields; verify; ensure video sitemap includes URL.
- 50–60: publish; request indexing; spot-check next day.
Principle: if any step feels heroic, your template is missing a rule.
本作品采用《CC 协议》,转载必须注明作者和本文链接