shadimage

Testimonial Schema

Review JSON-LD with editable CONTENT.

registry/default/blocks/testimonial-schema.tsx

import { getBrand } from "@/lib/brand"
import { getSiteUrl } from "@/lib/site-url"

function serializeJsonLd(data: unknown): string {
  return JSON.stringify(data).replace(/</g, "\u003c")
}


/** Edit CONTENT to set the reviews emitted as JSON-LD. */
const CONTENT = {
  reviews: [
    {
      name: "Alex R.",
      title: "Founder",
      company: "Dev tools startup",
      rating: 5,
      quote:
        "Shipped what would have taken two months in a week. Daily updates, no handoffs.",
    },
    {
      name: "Priya M.",
      title: "Head of Product",
      company: "Legal tech",
      rating: 5,
      quote:
        "The AI feature fits our workflow - not a chatbot bolted on the side.",
    },
  ],
}

type TestimonialSchemaProps = {
  pageUrl?: string
}

/**
 * Individual Review JSON-LD only - no AggregateRating.
 */
export function TestimonialSchema({ pageUrl }: TestimonialSchemaProps = {}) {
  const siteUrl = getSiteUrl()
  const url = pageUrl ?? siteUrl

  const data = {
    "@context": "https://schema.org",
    "@graph": CONTENT.reviews.map((t) => ({
      "@type": "Review",
      author: {
        "@type": "Person",
        name: t.name,
        jobTitle: t.title,
        worksFor: { "@type": "Organization", name: t.company },
      },
      reviewRating: {
        "@type": "Rating",
        ratingValue: String(t.rating),
        bestRating: "5",
        worstRating: "1",
      },
      reviewBody: t.quote,
      itemReviewed: {
        "@type": "ProfessionalService",
        name: getBrand().name,
        url,
      },
    })),
  }

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: serializeJsonLd(data) }}
    />
  )
}
Where it goes
testimonialdata-table
Use cases
dashboard