Skip to content

The package

Two entry points matter to an Astro app: @geoffy/headless/astro for the framework adapters, and @geoffy/headless for the framework-agnostic client underneath them.

interface GeoffyClientOptions {
siteKey: string; // from Geoffy settings; public, addresses rather than authorises
origin?: string; // overrides GEOFFY_ORIGIN and the default, for this call
timeoutMs?: number; // how long before giving up and rendering without Geoffy
revalidateSeconds?: number; // Next.js only; accepted and ignored in Astro
}

timeoutMs is deliberately short: the fallback — render without the widget — is cheap, while a stalled build is not.

getGeoffyProductMarkup(options, handle, extra?)

Section titled “getGeoffyProductMarkup(options, handle, extra?)”
const geoffy = await getGeoffyProductMarkup(
{ siteKey },
handle,
{ canonicalUrl, locale },
);

extra takes canonicalUrl and locale, both optional. locale is the language this page renders, for example sr or de-DE. Only the language part is compared, so en-GB content matches an en-US page. A value that is not a language tag is treated as unknown, and the helper returns the content.

Resolves to null, or to:

interface GeoffyProductMarkup {
jsonLdScript: string; // a complete <script type="application/ld+json"> element
widgetHtml: string; // the visible widget, styles included
artifact: GeoffyProductArtifact;
}

Both strings are pre-rendered HTML, so both need set:html. null covers every case — nothing published, Geoffy unreachable, an error, a locale in a different language from the published content, or a canonicalUrl that does not match — so your {geoffy && …} guards handle every one of them.

artifact is the raw data behind the two strings, if you want to render your own markup instead.

Pages with nothing to show cost no request

Section titled “Pages with nothing to show cost no request”

The helper reads a small list of the products Geoffy has published, with the language and the path of each, and keeps it in memory for 30 seconds. With that list, it returns null for an unpublished product, a page in another language, or a page whose path is not the published one, without asking Geoffy about the product. A newly published product appears within those 30 seconds, or at your next build on a static site. There is nothing to configure.

If the list cannot be read, the helper asks about the product directly, as older versions do. A problem on Geoffy’s side never hides a published product.

export const GET = createGeoffyTextEndpoint({ siteKey }, "llms.txt");

name is "llms.txt", "llms-full.txt" or "agents.md". It refuses "robots-rules.txt": that is a fragment you append to your own file, not a whole file to serve. Answers 503 with Retry-After when Geoffy is degraded, never a fabricated 404.

export const prerender = false;
export const GET = createGeoffyProxyEndpoint({ siteKey });

The /apps/geoffy/[...path] catch-all. prerender = false is required — without it astro build fails outright. Query strings are not forwarded.

The framework-agnostic client. You will use fetchGeoffyText for robots.txt; the rest is what another framework’s adapter would be built on.

ExportSignatureNotes
fetchGeoffyText(options, name) => Promise<string | null>null means unreachable. Accepts "robots-rules.txt", which the endpoint factory does not
fetchGeoffyProduct(options, handle) => Promise<GeoffyProductArtifact | null>The raw artifact. null also when nothing is published for the handle
serializeJsonLd(node: unknown) => stringEscapes < safely for inline script
compareCanonical(page, artifact) => "match" | "mismatch" | "unknown"Path only, not origin
handleGeoffyProxy(options, requestUrl) => Promise<Response>What the proxy endpoint wraps
resolveProxyPath(requestUrl) => string | nullnull for anything outside the namespace
DEFAULT_GEOFFY_ORIGIN"https://api.geoffy.ai"
PROXY_PREFIX"/apps/geoffy"The mount path is fixed; see the namespace
interface GeoffyProductArtifact {
handle: string;
canonicalUrl: string; // the URL Geoffy published against
jsonLd: unknown; // schema.org Product node
widgetHtml: string; // styles included — do not sanitise them out
publishedAt: string | null;
}

When your site is verified but nothing is published for a handle, Geoffy answers with a successful { "handle": "blue-widget", "published": false }, not an error. fetchGeoffyProduct returns null for it, so your guards render nothing.

Node 18 or newer — the package uses global fetch, AbortController and crypto.subtle. ESM only. React is an optional peer dependency and is not needed for the /astro entry point.