Skip to content

Add JSON-LD structured data to docs pages#934

Open
AvdLee wants to merge 5 commits intomasterfrom
docs/seo-structured-data
Open

Add JSON-LD structured data to docs pages#934
AvdLee wants to merge 5 commits intomasterfrom
docs/seo-structured-data

Conversation

@AvdLee
Copy link
Copy Markdown
Owner

@AvdLee AvdLee commented Mar 2, 2026

Summary

  • Adds TechArticle JSON-LD schema to all documentation pages for better search engine understanding
  • Adds BreadcrumbList JSON-LD schema derived from the URL path hierarchy
  • Adds FAQPage JSON-LD schema on the FAQ page to enable rich results in Google Search

Changes

  • Modified docs/src/components/starlight/Head.astro to generate and inject structured data

Test plan

Inject TechArticle and BreadcrumbList schemas on all docs pages.
Add FAQPage schema on the FAQ page for rich search results.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6f1bf1a971

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.split("-")
.map((w: string) => w.charAt(0).toUpperCase() + w.slice(1))
.join(" ");
breadcrumbItems.push({ name, url: `${baseUrl}/docs${currentPath}` });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Build breadcrumb links only for routable docs paths

The breadcrumb schema currently creates an item URL for every slug segment via "/docs${currentPath}", but this repo has only a top-level docs index (docs/src/content/docs/docs/index.mdx) and no intermediate index pages like /docs/getting-started or /docs/features/capturing; those generated breadcrumb links therefore point to 404s on many pages. Because BreadcrumbList is meant to describe real navigable hierarchy, these dead links can cause search engines to ignore or distrust the structured data.

Useful? React with 👍 / 👎.

@AvdLee AvdLee force-pushed the docs/seo-structured-data branch from c328289 to 6f1bf1a Compare March 2, 2026 09:05
Skip intermediate URL segments that don't have index pages.
Breadcrumbs now link: Home > Docs > Current Page.
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 JSON-LD structured data to documentation pages to improve SEO and eligibility for rich results.

Changes:

  • Injects TechArticle JSON-LD on (non-home, non-404) docs pages.
  • Injects BreadcrumbList JSON-LD for docs pages.
  • Injects FAQPage JSON-LD on the FAQ page.

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

Comment on lines +12 to +18
const breadcrumbItems = [
{ name: "Home", url: baseUrl },
{ name: "Docs", url: `${baseUrl}/docs` },
];
if (title !== "RocketSim Docs" && title !== "404") {
breadcrumbItems.push({ name: title, url: pageUrl });
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

The PR description says the BreadcrumbList is derived from the URL path hierarchy, but the implementation always emits only Home → Docs → <current title> and ignores intermediate segments (e.g. /docs/features/networking/... will still be a direct child of Docs). Either update the PR description or build breadcrumbs from the actual path/navigation hierarchy so the JSON-LD matches the intended behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +60
if (breadcrumbItems.length > 1) {
schemas.push({
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: breadcrumbItems.map((item, index) => ({
"@type": "ListItem",
position: index + 1,
name: item.name,
item: item.url,
})),
});
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

BreadcrumbList schema is still emitted on the docs 404 page because breadcrumbItems always contains at least Home and Docs, so breadcrumbItems.length > 1 is always true. Error pages generally shouldn’t publish structured data; consider skipping BreadcrumbList when title === "404" (or based on slug) alongside the TechArticle guard.

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +123
if (isFaqPage && faqEntries.length > 0) {
schemas.push({
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faqEntries.map((faq) => ({
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

The FAQ JSON-LD is populated from a hard-coded list that does not match the actual FAQ page content (src/content/docs/docs/support/faq.md). This can lead to structured data being flagged as misleading by search engines. Consider sourcing the FAQ entries from the same content (e.g., frontmatter data, a shared JSON/data module imported by both the page and Head, or generating from the markdown headings/content) so the JSON-LD stays in sync.

Suggested change
if (isFaqPage && faqEntries.length > 0) {
schemas.push({
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faqEntries.map((faq) => ({
const faqData = Array.isArray(entry?.data?.faq) ? entry.data.faq : [];
if (isFaqPage && faqData.length > 0) {
schemas.push({
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faqData.map((faq) => ({

Copilot uses AI. Check for mistakes.
Comment on lines +150 to +158
{
schemas.map((schema) => (
<script
type="application/ld+json"
is:inline
set:html={JSON.stringify(schema)}
/>
))
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

set:html={JSON.stringify(schema)} injects raw JSON into a <script> tag. If any field (e.g. title/description) ever contains </script> or certain < sequences, it can break out of the script context and become an XSS vector. Consider escaping < (commonly replacing it with \u003c in the JSON string) before injecting, or using a safer serialization helper.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +10
const baseUrl = "https://www.rocketsim.app";
const { entry } = Astro.props;
const title = entry?.data?.title ?? "RocketSim Docs";
const description = entry?.data?.description ?? "";
const slug = entry?.slug ?? "";
const pageUrl = `${baseUrl}/${slug}`;
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

baseUrl is hard-coded here even though the project already centralizes the canonical site URL in src/config/config.json (and astro.config.ts sets site from it). This creates a second source of truth and makes structured data wrong in preview/staging environments. Prefer using Astro.site (configured via site) or importing @/config/config.json and using config.site.base_url for URL construction.

Copilot uses AI. Check for mistakes.
Skip BreadcrumbList structured data on 404 pages since error pages
should not publish structured data. Escape '<' as \u003c in JSON-LD
to prevent potential XSS if page titles ever contain </script>.
Copy link
Copy Markdown
Collaborator

@ngnijland ngnijland left a comment

Choose a reason for hiding this comment

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

We should re-use the existing StructuredData component and type the schema's properly (not unknown). The faq comment is an optional suggestion, since I'm not 100% if it is possible in a nice way/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Issue:

We already have an StructuredData component where the schema objects are typed using TypeScript. You can find it here. We should add new schema types there and use that component for consistency.

Comment on lines +62 to +117
const faqEntries = isFaqPage
? [
{
q: "Does RocketSim offer out-of-the App Store distribution?",
a: "Yes. Get in touch via support@rocketsim.app.",
},
{
q: "Are there non-recurring subscriptions?",
a: "Yes, you can consider buying Team Licenses at rocketsim.app/team-insights.",
},
{
q: "Can I reimburse my App Store subscription?",
a: "Yes, RocketSim offers Team Licenses as an alternative at rocketsim.app/team-insights.",
},
{
q: "Do you provide commercial or team licenses?",
a: "Yes, check out Team Licenses at rocketsim.app/team-insights.",
},
{
q: "Can I buy a lifetime RocketSim license?",
a: "No, but you can earn a lifetime license through SwiftLee Weekly's referral program. Join the newsletter and follow the instructions.",
},
{
q: "Why is my video not accepted by App Store Connect?",
a: "Videos are optimized for App Previews following Apple's specifications. Make sure to use a Simulator matching a device from the App Preview specifications.",
},
{
q: "Why wouldn't I just use xcrun simctl?",
a: "RocketSim uses xcrun simctl under the hood but enhances the output with touches, device bezels, and a visual interface for quicker access.",
},
{
q: "Why does RocketSim need screen recording permissions?",
a: "RocketSim is sandboxed and needs screen recording permissions to read Simulator window titles, which it uses to determine the currently active Simulator.",
},
{
q: "Where can I report bugs or feature requests?",
a: "Issues and feature requests are managed on GitHub at github.com/AvdLee/RocketSimApp/issues.",
},
{
q: "How can I get PNG images instead of JPEG?",
a: "Disable App Store Connect (ASC) Optimization. ASC requires JPEG images without alpha layer.",
},
{
q: "Why are my iPad captures upside-down?",
a: "RocketSim cannot detect landscape-left or landscape-right and defaults to one rotation. Rotate your Simulator twice and restart the recording.",
},
{
q: "Can I create transparent captures?",
a: "Yes, disable App Preview Optimized and set your background color to transparent.",
},
{
q: "Network Speed Control isn't working, what can I do?",
a: "Quit Xcode, all Simulators, and RocketSim, then reopen them. Check System Settings → Privacy & Security for pending permissions. On macOS Sequoia, enable the Network Extension in System Settings → General → Login Items & Extensions.",
},
]
: [];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion:

Having these questions and answers here as a duplicate to the faq in the markdown files is bound to diverge in the future. Maybe we can come up with a way to have the questions and answers declared in one place and then automatically placed in both schema values and the markdown file. I'm not sure how, we could do some research there.

AvdLee added 2 commits March 4, 2026 15:10
Move TechArticle, BreadcrumbList, and FAQPage structured data from
inline logic in Head.astro into the shared StructuredData component
with proper TypeScript types. Extract FAQ entries into a shared JSON
data file to avoid duplication with the markdown FAQ page. Apply XSS
escaping to all JSON-LD output globally.

Addresses reviewer feedback on PR #934.
Resolve conflict in Head.astro by keeping both the structured data
delegation from our branch and the OG image meta tags from master.
@AvdLee AvdLee requested a review from ngnijland March 4, 2026 14:42
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