Skip to content

No commerce platform

Your products live in your own CMS, a database, or a folder of MDX. There is no commerce platform to sync from, so Geoffy builds its catalogue by reading your own pages.

The code on your product page is identical to the Shopify case. What differs is where handle comes from and what you do in the dashboard first.

Step one is in the dashboard, not in your editor

Section titled “Step one is in the dashboard, not in your editor”
  1. Geoffy reads your sitemap and proposes URL patterns that look like product pages. You need a working sitemap.xml before this — if you do not have one, that is the first thing to build.

  2. You confirm the pattern, for example /products/:handle. The segment it captures becomes each product’s handle.

  3. Geoffy crawls the matching pages and records what it could read about each product.

  4. You review what it found — per product, including the canonical URL each page declares. This is where a broken canonical shows up — before it reaches anything published.

  5. You publish the products worth publishing.

The crawl reads what your HTML says. So the quality of what Geoffy has to work with is decided by your product page, before any of this runs:

Give itWhy
A real <h1> with the product nameIt is the strongest signal of what the page is about
A <link rel="canonical"> naming this productWithout it the product is unpublishable — canonical_broken
Product facts as text, not only in imagesA specification table in a JPEG is invisible
Your own Product structured data, if you have itGeoffy supplements it rather than duplicating it
app/products/[handle]/page.tsx
import { notFound } from "next/navigation";
import { GeoffyProduct } from "@geoffy/headless/next";
import { getProduct } from "@/lib/catalogue";
const canonical = (handle: string) => `https://yourdomain.com/products/${handle}`;
export async function generateMetadata({ params }) {
const { handle } = await params;
const product = await getProduct(handle);
if (!product) return {};
return {
title: product.name,
alternates: { canonical: canonical(handle) },
};
}
export default async function ProductPage({ params }) {
const { handle } = await params;
const product = await getProduct(handle);
if (!product) notFound();
return (
<article>
<h1>{product.name}</h1>
<ProductBody product={product} />
{/* `handle` is the segment your URL pattern captured — the same value in your
route, your data layer and Geoffy's catalogue. */}
<GeoffyProduct
siteKey={process.env.GEOFFY_SITE_KEY!}
handle={handle}
canonicalUrl={canonical(handle)}
/>
</article>
);
}

A new product is not in Geoffy until it is crawled. Re-run the crawl from the dashboard after you add products; the pattern is already saved, so it is one action.

Removing a product from your site does not un-publish it. Unpublish it in Geoffy too, or its plain-text twin stays reachable under /apps/geoffy/llm/{handle}.md on your own domain.

Everything else — the root files, the namespace, robots.txt, the IndexNow key — is identical. Follow the quickstart.