Where to put the markup
Two decisions here, and only one of them changes whether a crawler reads you.
Where on the page the structured data sits does not. Google documents JSON-LD as valid
in the <head> and the <body>, and Geoffy’s own live check searches the whole
document rather than a region. Both placements pass, on both sides.
Whether it is server-rendered decides everything. Google can read JSON-LD injected by JavaScript — and that is the trap, because most of the AI crawlers this package exists for cannot. Treat Google as the exception rather than the rule: if the markup is not in the HTML your server sends, assume it is invisible.
Astro can put it in the head, and mildly should
Section titled “Astro can put it in the head, and mildly should”getGeoffyProductMarkup returns the two surfaces separately, so you can send
jsonLdScript to the <head> and place widgetHtml wherever a shopper should see it.
<Layout> {geoffy && <Fragment set:html={geoffy.jsonLdScript} slot="head" />}
<YourProductUI handle={handle} />
{geoffy && <div data-geoffy-widget set:html={geoffy.widgetHtml} />}</Layout>This is a preference, not a requirement. The head is read before the body, and if a crawler truncates a long document the head is the part that survives. That is the whole advantage. Body placement verifies and is read perfectly well — Next.js has no choice but to use it.
Ship both, or ship neither
Section titled “Ship both, or ship neither”A page carrying structured data without the visible widget makes a claim to a crawler that
no shopper can see. Geoffy holds such a product at code_not_added rather than publishing
it — so keep the two {geoffy && …} guards together, and do not render one arm without
the other.
Where to put the widget
Section titled “Where to put the widget”Wherever a shopper should see it. Usually under your description or your buy box. Its position is entirely yours; Geoffy checks that it is present, never where.
Its root is a block-level <details> at width: 100%, so give it a full-width container
rather than a narrow column. Its CSS is scoped entirely under .geoffy-widget and
inherits your font and colour, so it cannot restyle the rest of your page and needs no
dark-mode handling from you.