wacrm/src/components/seo/json-ld.tsx
Arnas Donauskas 15e4b5366d
feat(seo): full SEO pass (reapply with OG-image build fix)
Re-applies the original SEO work from PR #47 that was reverted in
PR #54 because its opengraph-image.tsx crashed `next build` during
static prerender of /opengraph-image. Satori (next/og's renderer)
errored with:

  Error: Expected <div> to have explicit "display: flex",
         "display: contents", or "display: none" if it has more
         than one child node.
  Failed to load dynamic font for ✓ . Status: 400

Root cause in the OG image JSX:
- Several <div>s held text + a sibling element (e.g. "Run your
  WhatsApp business from <span>one inbox.</span>") without an
  explicit display value. Satori requires it whenever there is
  more than one child.
- The ✓ Unicode glyph isn't covered by the default font Satori
  loads, so next/og tried to download a dynamic font and failed.

Fix:
- Every <div> with more than one child now has display: flex.
- Multi-span headline sits in a flex-wrap row of two <span>s.
- ✓ replaced with plain text dot separators in the feature row.

Verified end-to-end: `npm run build` now completes with 27/27
pages prerendered, /opengraph-image among them.

All the other SEO-pass content is identical to the originally
merged-and-reverted work: metadataBase + title template, OpenGraph
+ Twitter Cards defaults, JSON-LD (WebSite + Organization +
SoftwareApplication + FAQPage), sitemap.ts, robots.ts, noindex on
auth + dashboard route-group layouts, shared faq-data + site-config
modules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:25:59 +03:00

23 lines
710 B
TypeScript

/**
* Emits one <script type="application/ld+json"> per schema node.
* Google recommends separate tags per schema type over one combined
* @graph blob, to make it easier for them to parse selectively.
*
* Uses dangerouslySetInnerHTML because React escapes the < / > in
* script bodies otherwise, which breaks JSON-LD in subtle ways.
* The input is JSON.stringify'd locally — no user input flows here.
*/
export function JsonLd({ data }: { data: Record<string, unknown>[] }) {
return (
<>
{data.map((node, i) => (
<script
key={i}
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(node) }}
/>
))}
</>
)
}