The product page
import { GeoffyProduct } from "@geoffy/headless/next";
export default async function ProductPage({ params }) { const { lang, handle } = await params;
return ( <> <YourProductUI handle={handle} />
<GeoffyProduct siteKey={process.env.GEOFFY_SITE_KEY!} handle={handle} locale={lang} canonicalUrl={`https://yourdomain.com/${lang}/products/${handle}`} /> </> );}The file path above is an example, not a requirement. Your product route can be as deep as
you like — nothing in this package reads the URL, because you pass handle explicitly.
What handle must be
Section titled “What handle must be”| Your setup | handle is |
|---|---|
| Shopify behind your storefront | the Shopify product handle, the same value you already query the Storefront API with |
| No commerce platform | the segment your product URL pattern captured. Geoffy shows you the handle it recorded next to each crawled product, so you can copy it rather than guess |
See which kind of site is yours.
canonicalUrl is optional and worth passing
Section titled “canonicalUrl is optional and worth passing”Geoffy publishes against one canonical URL per product, and this component renders wherever you mount it. So on a page that is not that URL, it would render another page’s content.
Pass canonicalUrl and it renders nothing there instead, leaving an inert
data-geoffy-skipped marker so a bare page can be diagnosed from view-source.
Pass the same value you already build for generateMetadata rather than a second copy of
the logic.
If your site serves more than one language
Section titled “If your site serves more than one language”Geoffy has content for one locale of each product — the one its canonical URL names.
Mounting <GeoffyProduct> on every locale is the tempting mistake: the other locales
render the canonical locale’s widget and a structured-data node in the canonical locale’s
language, under a page written in another one. Nothing errors; the page simply makes a
claim in the wrong language.
Pass locale and canonicalUrl, and you do not have to be careful about it. Mount the
component on every locale if that is simpler — it renders only on the page Geoffy published
against, in the language the content is written in, and does nothing on the rest. That is
why the two arguments are worth the lines.
<GeoffyProduct siteKey={process.env.GEOFFY_SITE_KEY!} handle={handle} locale={lang} // the locale this route renders, e.g. "sr" or "de-DE" canonicalUrl={canonical} // this page's own canonical/>On a page in another language the component declines without a request to Geoffy, and
leaves the same data-geoffy-skipped marker. Only a clear language mismatch hides the
content: if the component cannot tell the language, it renders.
If you want Geoffy on every locale today, the way to get it is one Geoffy site per locale — each with its own address, its own ownership check and its own catalogue. That works now, and it costs what it sounds like: a separate crawl and a separate publish for each. Per-locale content under a single site is not something this package can do yet. Tell us if you need it.
It never breaks your page
Section titled “It never breaks your page”<GeoffyProduct> renders nothing when Geoffy has nothing published for the handle, when
Geoffy is unreachable or erroring, and when locale or canonicalUrl does not match.
A product with nothing published is a normal state, not an error, and a page for it makes no request to Geoffy. So you can mount the component on every product page, published or not.
That is deliberate and absolute: your storefront is your business, and if we are having a bad minute the correct behaviour is for your product page to render exactly as it would have without us. The fetch carries its own timeout for the same reason — a revalidation that never settles holds a worker, which is worse than one that fails.