Skip to content

seo: blog index H1 + HowTo schema for tutorial posts#383

Open
slayerjain wants to merge 7 commits into
mainfrom
seo/audit-fixes
Open

seo: blog index H1 + HowTo schema for tutorial posts#383
slayerjain wants to merge 7 commits into
mainfrom
seo/audit-fixes

Conversation

@slayerjain
Copy link
Copy Markdown
Member

@slayerjain slayerjain commented May 4, 2026

Summary

Two fixes from the SEO/GEO/AEO audit on keploy.io/blog.

Blog index H1 + title rewrite

  • /blog was rendering zero H1 (only H2/H3 cards). Title ran 25 characters: Engineering | Keploy Blog — too short, missing primary keywords like "API testing" or "test automation".
  • New H1: Keploy Engineering Blog. New title: Keploy Blog — API Testing, Test Automation & eBPF Deep-Dives (57c).
  • Both the rendered <title> (in <Head> on pages/index.tsx) and Layout's Title prop — which components/meta.tsx uses to build og:title/twitter:title — now use this same canonical string, so social/SEO metadata and the document title agree.

HowTo schema for tutorial posts

  • New lib/howToSchema.ts builds a HowTo JSON-LD object from a post when one of its tags normalizes to howto / how-to / tutorial (the normalize step lowercases + replaces whitespace with - so a WordPress display tag like "How To" matches).
  • Steps are extracted from the post body — h2/h3 headings paired with the first <p> that follows. Capped at 110 chars for name, 480 for text.
  • The helper takes pre-sanitized safeTitle/safeDescription from the caller (the same values used for BlogPosting), so encoded WordPress entities and inline markup never end up in the JSON-LD.
  • Returns null for non-tutorial posts (and for posts where fewer than 2 steps could be derived) — no invalid schema is emitted.
  • Wired into pages/community/[slug].tsx and pages/technology/[slug].tsx: appended to the existing structuredData[] array.

Note: the helper does not consume custom WordPress meta (howto_steps / is_tutorial). Exposing those over WPGraphQL would need a server-side register_graphql_field registration that doesn't exist on this site today; if/when WP adds it, the helper can be extended in a follow-up to read authored steps.

Test plan

  • npx tsc --noEmit reports no new errors — verified (1 pre-existing PNG-import error unchanged)
  • tests/e2e/SeoMeta.spec.tsog:title regex updated to match the new title; suite still passes
  • Tag the 5 candidate WordPress posts with the howto (or "How To") tag (their slugs):
    • community/how-to-do-java-unit-testing-effectively
    • community/how-to-generate-test-cases-with-automation-tools
    • community/how-to-mock-backend-of-selenium-tests-using-keploy
    • community/getting-started-with-keploy
    • community/how-to-do-frontend-test-automation-using-selenium
  • Spot-check rendered HTML of one tagged post for "@type":"HowTo" JSON-LD
  • Google Rich Results test passes for one tagged post (HowTo)
  • /blog page source shows <h1>Keploy Engineering Blog</h1> and <title>Keploy Blog — API Testing, Test Automation & eBPF Deep-Dives</title>
  • Companion PRs in keploy/landing (feat: add skeleton loaders for improved UX on slow networks (#3293) #207) and keploy/docs (#857)

🤖 Generated with Claude Code

Audit findings on /blog (index page):
- Missing H1 (only H2/H3 cards present).
- Title ran 25 characters: "Engineering | Keploy Blog" — too short, missed
  primary keywords. Replaced with H1 "Keploy Engineering Blog" and title
  "Keploy Blog — API Testing, Test Automation & eBPF Deep-Dives" (57c).

HowTo schema integration:
- New lib/howToSchema.ts builds a HowTo JSON-LD object from a post when:
  1. The post has an explicit `howto_steps` post-meta JSON array, OR
  2. The post is tagged howto / how-to / tutorial OR has `is_tutorial: true`,
     in which case steps are extracted from h2/h3 + first <p> in the body.
- Returns null for non-tutorial posts and for posts where fewer than 2 steps
  could be derived (avoids invalid HowTo emission).
- Wired into both community and technology post templates: appended into the
  existing structuredData[] array which the Meta component already maps to
  JSON-LD <script> tags. No template plumbing changes.
- Stable selection of 5 candidate posts to tag on the WordPress side — see PR
  description.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 4, 2026 17:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds two SEO-focused changes to the blog: a stronger homepage heading/title and automatic HowTo JSON-LD generation for tutorial posts on article pages.

Changes:

  • Replaces the homepage hero heading with an H1 and updates the browser <title> copy.
  • Introduces lib/howToSchema.ts to derive HowTo structured data from post content or tutorial metadata.
  • Appends the new HowTo schema to community and technology post pages when applicable.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
pages/index.tsx Updates homepage title tag and hero heading level/copy.
pages/community/[slug].tsx Adds HowTo schema generation to community post structured data.
pages/technology/[slug].tsx Adds HowTo schema generation to technology post structured data.
lib/howToSchema.ts New helper that detects tutorial posts and builds HowTo JSON-LD.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pages/index.tsx
Comment thread pages/community/[slug].tsx Outdated
Comment thread pages/technology/[slug].tsx Outdated
Comment thread lib/howToSchema.ts Outdated
Comment thread lib/howToSchema.ts
Comment thread pages/index.tsx
slayerjain added a commit that referenced this pull request May 4, 2026
- pages/index.tsx: pass the canonical SEO title via Layout's Title
  prop (was still passing "Blog - Keploy") so og:title and
  twitter:title match the rendered <title> instead of the stale
  marketing string. Drops the duplicate <Head><title> override.
- lib/howToSchema.ts:
    * remove dead activation paths. The `is_tutorial` and
      `howto_steps` fields are not exposed by WPGraphQL on this
      site, so the two custom-meta branches never fired. Detection
      is now tag-based only (which rides on the existing fragment).
    * drop step.url. Heading anchor ids are attached client-side in
      components/post-body.tsx — the SSR HTML crawlers see has no
      such ids, so any `#slug` we emitted was an invalid anchor.
@nehagup nehagup requested a review from Copilot May 4, 2026 17:53
- pages/index.tsx: pass the canonical SEO title via Layout's Title
  prop (was still passing "Blog - Keploy") so og:title and
  twitter:title match the rendered <title> instead of the stale
  marketing string. Drops the duplicate <Head><title> override.
- lib/howToSchema.ts:
    * remove dead activation paths. The `is_tutorial` and
      `howto_steps` fields are not exposed by WPGraphQL on this
      site, so the two custom-meta branches never fired. Detection
      is now tag-based only (which rides on the existing fragment).
    * drop step.url. Heading anchor ids are attached client-side in
      components/post-body.tsx — the SSR HTML crawlers see has no
      such ids, so any `#slug` we emitted was an invalid anchor.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pages/index.tsx
Comment thread lib/howToSchema.ts Outdated
Comment thread lib/howToSchema.ts Outdated
- pages/index.tsx: restore <Head><title> for /blog. Removing it earlier
  was a regression — components/meta.tsx renders og/twitter title from
  the Layout `Title` prop but does NOT emit a <title> tag (same gap
  the LIVE-11 author-page comment documents). Both sources now use the
  same canonical SEO string.
- lib/howToSchema.ts: fix tag matching — slug isn't queried, only
  `tags.edges.node.name`. The `name || slug` fallback was dead and
  WordPress display-name tags like "How To" never matched the candidate
  bucket. Tag names are now normalized (lowercase + whitespace→`-`)
  before checking against {howto, how-to, tutorial}.
- lib/howToSchema.ts: HowTo `name` and `description` now come from
  caller-sanitized values (sanitizeTitle / getSafeDescription) instead
  of raw WordPress fields, matching the BlogPosting schema on the same
  pages so encoded entities and inline markup don't ship in JSON-LD.
- pages/{community,technology}/[slug].tsx: pass safeTitle/safeDescription
  through to the new helper signature.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pages/index.tsx
Comment thread lib/howToSchema.ts
The Layout `Title` prop now feeds og:title with the canonical
`Keploy Blog — API Testing, Test Automation & eBPF Deep-Dives`
SEO string, replacing the prior `Blog - Keploy`. The Playwright
homepage assertion is updated to match.
@slayerjain
Copy link
Copy Markdown
Member Author

@copilot-pull-request-reviewer review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/howToSchema.ts Outdated
stripHtml() was only decoding the named entity set (&amp;, &lt;, etc.)
plus &#39;, so curly apostrophes (&#8217;), en/em dashes (&#8211; /
&#8212;), and curly quotes (&#8220; / &#8221;) leaked into HowTo step
text as literal entity strings. Search engines and the rich-results test
treat that as broken markup. Mirror the decode list from utils/seo.ts
so the JSON-LD and the meta description stay consistent.
@nehagup nehagup requested a review from Copilot May 5, 2026 13:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/howToSchema.ts Outdated
Comment thread lib/howToSchema.ts Outdated
The HowTo schema is serialized via JSON.stringify and injected into a
<script type="application/ld+json"> via dangerouslySetInnerHTML. Decoding
&lt;/&gt; back to raw < / > inside stripHtml meant a tutorial paragraph
containing &lt;/script&gt; would emit a literal </script> in the JSON-LD
body and prematurely close the script tag — a markup-integrity hole that
defeats the page's XSS defences. Stop decoding those two entities; tags
were already stripped upstream so any remaining angle brackets came from
content and are safer left as &lt; in the script payload.

Also collapse the two parallel decode lists. utils/seo.ts already owns
the WordPress entity sanitizer used by titles and descriptions; export
it and reuse it from lib/howToSchema.ts so future entity changes don't
have to be made in two places.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/e2e/SeoMeta.spec.ts Outdated
Comment thread lib/howToSchema.ts
The Playwright SEO suite was asserting `og:title` matches /^Keploy
Blog\b/ — a prefix that would still pass if the keyword-rich suffix
were dropped or the new "Keploy Engineering Blog" H1 reverted. Replace
with exact-string assertions on og:title, document title, and the
visible H1 so a regression in the homepage SEO rewrite trips CI.

Add tests/lib/howToSchema.test.ts covering getHowToSchema directly:
tutorial-tag detection (case/whitespace variants), non-tutorial null,
< 2-step null, WordPress numeric-entity decoding, mainEntityOfPage,
and missing-input null handling. The e2e suite couldn't reach this
path because there are no tutorial-tagged posts in WP today, so the
helper could silently stop emitting (or start emitting on every post)
without anything failing. Wire `npm run test:unit` to run the new
tsx-based tests via node:test.

Signed-off-by: Neha Gupta <gneha21@yahoo.in>
@nehagup nehagup requested a review from Copilot May 7, 2026 16:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment on lines +103 to +106
// &#8217; (curly apostrophe), &#8211; (en dash), and the structural
// &lt;/&gt; pair WP emits for inline code blocks. Assert the entities
// are replaced with proper Unicode and that no `&lt;`/`&gt;` literals
// leak into the JSON-LD payload.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants