-
-
Notifications
You must be signed in to change notification settings - Fork 292
seo: HowTo schema, glossary DefinedTermSet, docs landing fixes #857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
198b7b2
docs(seo): add HowTo schema, glossary DefinedTermSet, docs landing fixes
nehagup 6f9c366
docs(seo): address PR #857 review comments
nehagup 0a41d75
docs(seo): address remaining PR #857 review comments
nehagup 13c879f
docs(seo): drop HowTo li id, fix glossary typo + guard JSON-LD URLs
nehagup ed1e52d
Merge branch 'main' into seo/audit-fixes
nehagup 0477d2f
fix(seo): drop wrong HowTo step anchor, rephrase Go SDK description
nehagup eec4a73
fix(seo): prefix sitemap ignorePatterns with /docs/, validate HowTo s…
nehagup f200ec4
style(seo): apply prettier 2.8.8 to changed quickstart docs
nehagup db024c0
chore(vale): skip <HowTo> MDX blocks when linting prose
nehagup 9bdf01f
chore(vale): match <HowTo> across newlines and `>` inside JSX values
nehagup 08248a7
chore(vale): accept "Sanic" — Python web framework name
nehagup 6e9f7ff
fix(seo): align about-page metadata, JSON-LD, and visible H1
nehagup 6e4df27
fix(seo): drop unused imports flagged by Copilot on /docs landing
nehagup d2208b3
Merge branch 'main' into seo/audit-fixes
nehagup de7fb45
fix(seo): correct k8s-proxy HowTo, express casing, about.js URL source
nehagup 2e81054
Merge branch 'main' into seo/audit-fixes
nehagup File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| import React from "react"; | ||
| import Head from "@docusaurus/Head"; | ||
|
|
||
| /** | ||
| * HowTo schema.org wrapper for Docusaurus MDX pages. | ||
| * | ||
| * Emits valid schema.org/HowTo JSON-LD into <head> and (optionally) renders a | ||
| * matching numbered <ol> of visible steps. Authors can pass `visible={false}` | ||
| * when the prose below already renders the steps so the JSON-LD is the only | ||
| * change to the page. | ||
| * | ||
| * Required HowTo fields per Google: name, step (array of HowToStep with name + text). | ||
| * Optional: totalTime (ISO 8601 duration), estimatedCost (MonetaryAmount), tool, supply. | ||
| * | ||
| * Example: | ||
| * <HowTo | ||
| * name="Install Keploy on Linux" | ||
| * totalTime="PT5M" | ||
| * estimatedCost={{currency: "USD", value: "0"}} | ||
| * tools={["bash", "curl"]} | ||
| * supplies={["Linux machine with kernel >= 5.10"]} | ||
| * steps={[ | ||
| * {name: "Download", text: "Run: curl ...", url: "#download"}, | ||
| * {name: "Install", text: "Run: sudo install ...", url: "#install"}, | ||
| * ]} | ||
| * visible={false} | ||
| * /> | ||
| */ | ||
| export default function HowTo({ | ||
| name, | ||
| description, | ||
| totalTime, | ||
| estimatedCost, | ||
| tools, | ||
| supplies, | ||
| image, | ||
| steps, | ||
| visible = true, | ||
| }) { | ||
| if (!name || !Array.isArray(steps) || steps.length === 0) { | ||
| // Component is a no-op without the minimum required fields. | ||
| return null; | ||
| } | ||
|
|
||
| // Filter to steps that carry both `name` and `text` per Google's HowTo | ||
| // requirements. Auto-generating "Step N" placeholders or emitting empty | ||
| // `text` produces low-quality structured data that the rich-results test | ||
| // flags. If the author gave us nothing usable, drop the schema entirely | ||
| // rather than ship a hollow HowTo. | ||
| const validSteps = steps.filter( | ||
| (s) => | ||
| typeof s.name === "string" && | ||
| s.name.trim() && | ||
| typeof s.text === "string" && | ||
| s.text.trim() | ||
| ); | ||
| if (validSteps.length === 0) { | ||
| return null; | ||
| } | ||
|
|
||
| const schema = { | ||
| "@context": "https://schema.org", | ||
| "@type": "HowTo", | ||
| name, | ||
| step: validSteps.map((s, i) => { | ||
| const step = { | ||
| "@type": "HowToStep", | ||
| position: i + 1, | ||
| name: s.name, | ||
| text: s.text, | ||
| }; | ||
| if (s.url) step.url = s.url; | ||
| if (s.image) step.image = s.image; | ||
| return step; | ||
| }), | ||
| }; | ||
|
slayerjain marked this conversation as resolved.
|
||
|
|
||
| if (description) schema.description = description; | ||
| if (totalTime) schema.totalTime = totalTime; | ||
| if (image) schema.image = image; | ||
| if (estimatedCost && estimatedCost.value !== undefined) { | ||
| schema.estimatedCost = { | ||
| "@type": "MonetaryAmount", | ||
| currency: estimatedCost.currency || "USD", | ||
| value: String(estimatedCost.value), | ||
| }; | ||
| } | ||
| if (Array.isArray(tools) && tools.length > 0) { | ||
| schema.tool = tools.map((t) => | ||
| typeof t === "string" ? {"@type": "HowToTool", name: t} : t | ||
| ); | ||
| } | ||
| if (Array.isArray(supplies) && supplies.length > 0) { | ||
| schema.supply = supplies.map((s) => | ||
| typeof s === "string" ? {"@type": "HowToSupply", name: s} : s | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| <Head> | ||
| <script type="application/ld+json">{JSON.stringify(schema)}</script> | ||
| </Head> | ||
| {visible && ( | ||
| <section | ||
| aria-label={name} | ||
| style={{ | ||
| border: "1px solid var(--ifm-color-emphasis-200)", | ||
| borderRadius: "12px", | ||
| padding: "1rem 1.25rem", | ||
| margin: "1rem 0 1.5rem", | ||
| background: "var(--ifm-background-surface-color)", | ||
| }} | ||
| > | ||
| <h3 style={{marginTop: 0}}>{name}</h3> | ||
| {description && <p>{description}</p>} | ||
| <ol> | ||
| {/* Don't derive an `id` from `s.url`. In docs usage `step.url` | ||
| often points at an existing heading anchor on the page (e.g. | ||
| `#capturing-testcases`), so reusing that as a list-item id | ||
| would produce duplicate ids in the DOM whenever `visible` | ||
| is enabled. The list is the readable view; `step.url` in | ||
| the JSON-LD already covers the schema linkage. */} | ||
| {validSteps.map((s, i) => ( | ||
| <li key={i}> | ||
| <strong>{s.name}</strong> | ||
| <div>{s.text}</div> | ||
| </li> | ||
| ))} | ||
| </ol> | ||
| </section> | ||
| )} | ||
| </> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,103 @@ | ||
| import React from "react"; | ||
| import Layout from "@theme/Layout"; | ||
| import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; | ||
| import useBaseUrl from "@docusaurus/useBaseUrl"; | ||
| import Head from "@docusaurus/Head"; | ||
|
|
||
| // Custom React pages under src/pages/ are not covered by the docs schema | ||
| // plugin — add Article + BreadcrumbList JSON-LD inline so the page is | ||
| // machine-readable for search engines and AI crawlers. | ||
| // | ||
| // Site config sets `trailingSlash: true`, so canonical URLs in the JSON-LD | ||
| // must carry the trailing slash to match the actual emitted href and avoid | ||
| // duplicate URL variants in structured data. | ||
| // | ||
| // Single source of truth for the page's title and description: the Layout | ||
| // `title`/`description` props, the visible H1, and the Article JSON-LD | ||
| // `headline`/`description` all read from these constants. Previously the | ||
| // page shipped Layout title "About the docs" / description "User General | ||
| // Information about..." while the JSON-LD claimed headline "About the | ||
| // Keploy Documentation" / a different description, which confuses snippet | ||
| // generators and leaves rich-result text out of sync with the meta tags. | ||
| const ABOUT_TITLE = "About the Keploy Documentation"; | ||
| const ABOUT_DESCRIPTION = | ||
| "Information about Keploy's documentation, contribution guidelines, and licensing."; | ||
|
|
||
| // Derive every canonical URL from a single `SITE` + path constants instead | ||
| // of hardcoding `https://keploy.io/docs/...` in each field — mirrors the | ||
| // pattern in concepts/reference/glossary.js. If the domain or docs baseUrl | ||
| // ever changes, the Article/BreadcrumbList structured data updates in one | ||
| // place instead of going stale field-by-field. | ||
| // | ||
| // Site config sets `trailingSlash: true`, so paths that map to a page carry | ||
| // a trailing slash to match the canonical href and avoid duplicate-URL | ||
| // variants in structured data. | ||
| const SITE = "https://keploy.io"; | ||
| const HOME_URL = `${SITE}/`; | ||
| const DOCS_URL = `${SITE}/docs/`; | ||
| const ABOUT_URL = `${SITE}/docs/about/`; | ||
| const LOGO_URL = `${SITE}/docs/img/favicon.png`; | ||
|
|
||
| const aboutStructuredData = [ | ||
| { | ||
| "@context": "https://schema.org", | ||
| "@type": "Article", | ||
| headline: ABOUT_TITLE, | ||
| description: ABOUT_DESCRIPTION, | ||
| url: ABOUT_URL, | ||
| publisher: { | ||
| "@type": "Organization", | ||
| name: "Keploy", | ||
| logo: { | ||
| "@type": "ImageObject", | ||
| url: LOGO_URL, | ||
| }, | ||
| }, | ||
| mainEntityOfPage: { | ||
| "@type": "WebPage", | ||
| "@id": ABOUT_URL, | ||
| }, | ||
| }, | ||
| { | ||
| "@context": "https://schema.org", | ||
| "@type": "BreadcrumbList", | ||
| itemListElement: [ | ||
| { | ||
| "@type": "ListItem", | ||
| position: 1, | ||
| name: "Home", | ||
| item: HOME_URL, | ||
| }, | ||
| { | ||
| "@type": "ListItem", | ||
| position: 2, | ||
| name: "Docs", | ||
| item: DOCS_URL, | ||
| }, | ||
| { | ||
| "@type": "ListItem", | ||
| position: 3, | ||
| name: "About", | ||
| item: ABOUT_URL, | ||
| }, | ||
| ], | ||
| }, | ||
| ]; | ||
|
|
||
| function About() { | ||
| const context = useDocusaurusContext(); | ||
| const {siteConfig = {}} = context; | ||
| return ( | ||
|
Comment on lines
85
to
86
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 6e4df27 — dropped both |
||
| <Layout | ||
| title="About the docs" | ||
| title={ABOUT_TITLE} | ||
| permalink="/about" | ||
| description="User General Information about Keploy's Documentation" | ||
| description={ABOUT_DESCRIPTION} | ||
| > | ||
| <Head> | ||
| {aboutStructuredData.map((schema, i) => ( | ||
| <script key={i} type="application/ld+json"> | ||
| {JSON.stringify(schema)} | ||
| </script> | ||
| ))} | ||
| </Head> | ||
| <main className="margin-vert--lg container"> | ||
| <h1>About the docs</h1> | ||
| <h1>{ABOUT_TITLE}</h1> | ||
| <div className="margin-bottom--lg"> | ||
| <h2 id="latest">Documentation SLA</h2> | ||
| <p> | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.