Skip to content

Conversation

@richiemcilroy
Copy link
Member

@richiemcilroy richiemcilroy commented Sep 16, 2025

This pull request adds structured data (schema.org JSON-LD) to the Home Page for SEO purposes.

Summary by CodeRabbit

  • New Features

    • Added JSON-LD structured data to the home page: Organization, Website, Software App (with curated reviews), Product, Breadcrumbs, Video-ready schema, and a six-item FAQ—improves SEO and eligibility for rich results (ratings, FAQs, breadcrumbs).
    • Centralized schema generation for consistent site metadata and future expansions.
  • Chores

    • Added a TypeScript path alias to simplify data imports.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 16, 2025

Walkthrough

Adds a new HomePageSchema client component that injects combined JSON-LD into the home page, a web-schema utility exporting multiple schema factory functions, integrates the component into HomePage rendering, and adds a "@/data/*" tsconfig path alias used for testimonials import.

Changes

Cohort / File(s) Summary
Structured data utilities
apps/web/utils/web-schema.ts
New module exporting factory functions: Organization, WebSite, SoftwareApplication (constructs reviews from testimonials with prioritization), BreadcrumbList, VideoObject, FAQPage (strips HTML), Product, Comparison Table, and LocalBusiness; reuses organization @id where applicable.
Home page schema component
apps/web/components/pages/HomePage/HomePageSchema.tsx
New client component composing multiple schemas (organization, website, software application with testimonials, product, breadcrumbs, FAQs) and injecting combined JSON-LD via a <Script id="homepage-structured-data" type="application/ld+json">.
Home page integration
apps/web/components/pages/HomePage/index.tsx
Imports and renders HomePageSchema as the first child inside the HomePage fragment (before <Header />).
TypeScript path alias
apps/web/tsconfig.json
Adds path mapping "@/data/*" -> "data/*" to enable imports like @/data/testimonials.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant NextApp as Next.js App
  participant HomePage as HomePage
  participant SchemaComp as HomePageSchema (client)
  participant SchemaUtils as web-schema.ts
  participant DOM as Document
  participant Crawler as Search Engine

  User->>NextApp: GET /
  NextApp->>HomePage: render page
  HomePage->>SchemaComp: mount HomePageSchema
  SchemaComp->>SchemaUtils: call createOrganization/WebSite/SoftwareApplication/... (use testimonials)
  SchemaUtils-->>SchemaComp: return schema objects
  SchemaComp->>DOM: inject <script id="homepage-structured-data" type="application/ld+json">JSON-LD</script>
  Crawler-->>DOM: fetch and parse JSON-LD
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I munched on JSON, tidy and sweet,
Breadcrumbs and FAQs all in a neat sheet.
Reviews and stars tucked under my paw—
I hid them in Script for bots that I saw.
Hop, hop, now search finds our home with a beat. 🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Add structured data schema for HomePage" is concise and accurately summarizes the primary change: adding JSON-LD structured data to the Home Page via a new HomePageSchema component and related web-schema utilities; the diff shows the HomePageSchema file, its import/usage in HomePage, and the new web-schema utilities that generate the schema. The phrasing is specific enough for a reviewer scanning PR history to understand the main intent without listing implementation details.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch structured-data

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (8)
apps/web/utils/web-schema.ts (5)

49-60: Honor the function parameter when selecting prioritized testimonials.

You’re searching allTestimonials for Roger even when a custom testimonials array is passed. This makes the function impure relative to its input.

Apply:

- const rogerTestimonial = allTestimonials.find(
+ const rogerTestimonial = testimonialsToUse.find(
   (t) => t.handle === "@_rogermattos",
 );
- const selectedTestimonials = rogerTestimonial
+ const selectedTestimonials = rogerTestimonial
   ? [
       rogerTestimonial,
-      ...testimonialsToUse
+      ...testimonialsToUse
         .filter((t) => t.handle !== "@_rogermattos")
         .slice(0, 4),
     ]
   : testimonialsToUse.slice(0, 5);

106-126: Use Offer.priceSpecification for monthly pricing instead of eligibleQuantity.

eligibleQuantity isn’t the right construct for billing period. Prefer UnitPriceSpecification with a monthly unit.

       {
         "@type": "Offer",
         price: "8.16",
         priceCurrency: "USD",
         name: "Pro Plan",
         priceValidUntil: "2025-12-31",
         description: "Full features for professional use",
-        eligibleQuantity: {
-          "@type": "QuantitativeValue",
-          unitText: "month",
-        },
+        priceSpecification: {
+          "@type": "UnitPriceSpecification",
+          price: "8.16",
+          priceCurrency: "USD",
+          unitText: "MONTH"
+        }
       },

Also confirm whether the “Pro” price or term changes before 2025‑12‑31; if so, update or remove priceValidUntil to avoid Search Console warnings.


127-134: Align reviewCount with the testimonial source used.

reviewCount should reflect the dataset you base reviews on.

-      reviewCount: allTestimonials.length.toString(),
+      reviewCount: testimonialsToUse.length.toString(),

15-20: Deduplicate social profiles in sameAs.

Both Twitter and X point to the same presence. Keep one to avoid noise.

   sameAs: [
     "https://github.com/capsoftware/cap",
-    "https://twitter.com/cap",
     "https://x.com/cap",
     "https://www.producthunt.com/products/cap-3",
   ],

255-267: Consistent @id pattern for LocalBusiness.

Use a fragment identifier like the other entities for consistency.

-  "@id": "https://cap.so",
+  "@id": "https://cap.so/#localbusiness",
apps/web/components/pages/HomePage/index.tsx (1)

10-11: Minor: unify data imports to use the new alias.

Since we’re touching imports here, consider switching ../../../data/homepage-copy to @/data/homepage-copy for consistency with the new @/data/* alias.

apps/web/components/pages/HomePage/HomePageSchema.tsx (2)

1-1: File naming: prefer kebab-case for TS/TSX files.

Rename to home-page-schema.tsx to match the repo guideline.


47-58: Optional: emit a single object with @graph.

Current array is valid. Using {"@context":"https://schema.org","@graph":[...]} can reduce repetition and slightly shrink payload.

-  const schemas = [
-    createOrganizationSchema(),
-    createWebSiteSchema(),
-    createSoftwareApplicationSchema(testimonials),
-    createProductSchema(),
-    createBreadcrumbSchema([{ name: "Home", url: "https://cap.so" }]),
-    createFAQSchema(homePageFAQs),
-  ];
-  return JSON.stringify(schemas);
+  const graph = [
+    createOrganizationSchema(),
+    createWebSiteSchema(),
+    createSoftwareApplicationSchema(testimonials),
+    createProductSchema(),
+    createBreadcrumbSchema([{ name: "Home", url: "https://cap.so" }]),
+    createFAQSchema(homePageFAQs),
+  ];
+  return JSON.stringify({ "@context": "https://schema.org", "@graph": graph });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e237cf7 and 197ffa5.

⛔ Files ignored due to path filters (1)
  • apps/web/public/cap-logo.png is excluded by !**/*.png
📒 Files selected for processing (4)
  • apps/web/components/pages/HomePage/HomePageSchema.tsx (1 hunks)
  • apps/web/components/pages/HomePage/index.tsx (2 hunks)
  • apps/web/tsconfig.json (1 hunks)
  • apps/web/utils/web-schema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for client-side server state and data fetching in the web app
Mutations should call Server Actions and perform precise cache updates with setQueryData/setQueriesData, avoiding broad invalidations
Prefer Server Components for initial data and pass initialData to client components for React Query hydration

Files:

  • apps/web/components/pages/HomePage/index.tsx
  • apps/web/components/pages/HomePage/HomePageSchema.tsx
  • apps/web/utils/web-schema.ts
{apps/web,packages/ui}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps/web,packages/ui}/**/*.{ts,tsx}: Use Tailwind CSS exclusively for styling in the web app and shared React UI components
Component naming: React components in PascalCase; hooks in camelCase starting with 'use'

Files:

  • apps/web/components/pages/HomePage/index.tsx
  • apps/web/components/pages/HomePage/HomePageSchema.tsx
  • apps/web/utils/web-schema.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; prefer shared types from packages

**/*.{ts,tsx}: Use Biome to format/lint TypeScript with a 2-space indent
TypeScript file names should be kebab-case (e.g., user-menu.tsx)

Files:

  • apps/web/components/pages/HomePage/index.tsx
  • apps/web/components/pages/HomePage/HomePageSchema.tsx
  • apps/web/utils/web-schema.ts
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

React/Solid components should be named using PascalCase

Files:

  • apps/web/components/pages/HomePage/index.tsx
  • apps/web/components/pages/HomePage/HomePageSchema.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-08T16:48:20.727Z
Learnt from: CR
PR: CapSoftware/Cap#0
File: AGENTS.md:0-0
Timestamp: 2025-09-08T16:48:20.727Z
Learning: Applies to **/tauri.ts : Do not edit auto-generated file: tauri.ts

Applied to files:

  • apps/web/tsconfig.json
🧬 Code graph analysis (3)
apps/web/components/pages/HomePage/index.tsx (1)
apps/web/components/pages/HomePage/HomePageSchema.tsx (1)
  • HomePageSchema (60-68)
apps/web/components/pages/HomePage/HomePageSchema.tsx (2)
apps/web/utils/web-schema.ts (5)
  • createOrganizationSchema (1-26)
  • createWebSiteSchema (28-39)
  • createSoftwareApplicationSchema (44-139)
  • createBreadcrumbSchema (141-152)
  • createFAQSchema (175-188)
apps/web/data/testimonials.ts (1)
  • testimonials (1-211)
apps/web/utils/web-schema.ts (1)
apps/web/data/testimonials.ts (1)
  • testimonials (1-211)
🪛 ast-grep (0.38.6)
apps/web/components/pages/HomePage/HomePageSchema.tsx

[warning] 64-64: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
apps/web/tsconfig.json (1)

29-31: Alias addition looks good.

The "@/data/*" path mapping is correct and aligns with how it’s used in this PR.

apps/web/utils/web-schema.ts (2)

200-211: Verify hardcoded aggregate rating for Product.

reviewCount: "250" and ratingValue: "4.8" are static. If not sourced from verifiable reviews, consider removing or wiring to a source of truth to avoid rich‑results issues.


41-47: Incorrect — Testimonial is exported from apps/web/data/testimonials.ts

apps/web/data/testimonials.ts exports Testimonial (line 213), so the import in apps/web/utils/web-schema.ts is valid; the suggested diff is unnecessary.

Likely an incorrect or invalid review comment.

apps/web/components/pages/HomePage/index.tsx (1)

24-25: LGTM: injecting structured data at the top of the page.

Placement before Header is good for visibility.

Ensure HomePageSchema isn’t rendered elsewhere to avoid duplicate JSON‑LD on the same route.

apps/web/components/pages/HomePage/HomePageSchema.tsx (1)

60-67: Safe JSON-LD injection pattern.

Using next/script with type="application/ld+json" and JSON.stringify(...) is the recommended approach; no XSS risk here since input is static and serialized.

If any schema content ever comes from user input, keep it JSON‑only and continue serializing with JSON.stringify (no HTML).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
apps/web/components/pages/HomePage/HomePageSchema.tsx (3)

1-1: Make this a Server Component (no client runtime needed)

This component only renders static JSON‑LD. Drop the client boundary to avoid unnecessary hydration and JS on the page.

Apply this diff:

-"use client";
+// Server Component: no client runtime required

14-45: Optional: move FAQs to a data module

Minor tidy-up: consider moving homePageFAQs to apps/web/data and importing it (similar to testimonials) for reuse and easier edits.


1-69: Rename file to kebab‑case per repo convention

TypeScript file names should be kebab‑case. Suggest: home-page-schema.tsx.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 197ffa5 and 6d6d0cc.

📒 Files selected for processing (1)
  • apps/web/components/pages/HomePage/HomePageSchema.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for client-side server state and data fetching in the web app
Mutations should call Server Actions and perform precise cache updates with setQueryData/setQueriesData, avoiding broad invalidations
Prefer Server Components for initial data and pass initialData to client components for React Query hydration

Files:

  • apps/web/components/pages/HomePage/HomePageSchema.tsx
{apps/web,packages/ui}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps/web,packages/ui}/**/*.{ts,tsx}: Use Tailwind CSS exclusively for styling in the web app and shared React UI components
Component naming: React components in PascalCase; hooks in camelCase starting with 'use'

Files:

  • apps/web/components/pages/HomePage/HomePageSchema.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; prefer shared types from packages

**/*.{ts,tsx}: Use Biome to format/lint TypeScript with a 2-space indent
TypeScript file names should be kebab-case (e.g., user-menu.tsx)

Files:

  • apps/web/components/pages/HomePage/HomePageSchema.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

React/Solid components should be named using PascalCase

Files:

  • apps/web/components/pages/HomePage/HomePageSchema.tsx
🧬 Code graph analysis (1)
apps/web/components/pages/HomePage/HomePageSchema.tsx (2)
apps/web/utils/web-schema.ts (5)
  • createOrganizationSchema (1-26)
  • createWebSiteSchema (28-39)
  • createSoftwareApplicationSchema (44-139)
  • createBreadcrumbSchema (141-152)
  • createFAQSchema (175-188)
apps/web/data/testimonials.ts (1)
  • testimonials (1-211)
🪛 ast-grep (0.38.6)
apps/web/components/pages/HomePage/HomePageSchema.tsx

[warning] 64-64: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (2)
apps/web/components/pages/HomePage/HomePageSchema.tsx (2)

47-55: Nice composition of multiple JSON‑LD entities

Good call composing Organization, WebSite, SoftwareApplication, Product, BreadcrumbList, and FAQPage for richer coverage. IDs and publisher references look consistent.


60-68: Inline Script usage LGTM once escaping is added

The Script tag with a stable id is fine for JSON‑LD. After applying the escaping above, this block is good to ship.

If you keep this as a Server Component, verify that Next.js in this repo supports using next/script in RSC (it generally does). If not, render this component within a server-rendered part of the tree (e.g., page/layout) so the JSON‑LD is present in the initial HTML.

Comment on lines +57 to +58
return JSON.stringify(schemas);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Escape JSON before injecting to prevent </script> break‑out (XSS)

JSON.stringify doesn’t escape </script>. If any string (e.g., testimonials) ever contains </script>, the tag can terminate early and allow injection. Escape unsafe chars before embedding.

Apply this diff:

-  return JSON.stringify(schemas);
+  // Escape unsafe characters to avoid </script> early termination and XSS
+  return JSON.stringify(schemas)
+    .replace(/</g, "\\u003c")
+    .replace(/>/g, "\\u003e")
+    .replace(/&/g, "\\u0026");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return JSON.stringify(schemas);
};
// Escape unsafe characters to avoid </script> early termination and XSS
return JSON.stringify(schemas)
.replace(/</g, "\\u003c")
.replace(/>/g, "\\u003e")
.replace(/&/g, "\\u0026");
🤖 Prompt for AI Agents
In apps/web/components/pages/HomePage/HomePageSchema.tsx around lines 57 to 58,
the code returns JSON.stringify(schemas) which does not escape sequences like
</script> and can allow script tag break-out; update the return to HTML-escape
unsafe characters in the JSON payload before injecting into a script tag (at
minimum replace the sequences </script> (and also </ and the characters U+2028
and U+2029) with safe equivalents such as <\/script> and escaped unicode), or
use a small safe serializer/utility that performs these replacements, and return
that escaped string instead of raw JSON.stringify output.

@richiemcilroy richiemcilroy merged commit 24be1a5 into main Sep 16, 2025
15 checks passed
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.

2 participants