diff --git a/src/components/BusinessSchema.tsx b/src/components/BusinessSchema.tsx new file mode 100644 index 0000000..43498af --- /dev/null +++ b/src/components/BusinessSchema.tsx @@ -0,0 +1,67 @@ +interface Profile { + businessName: string | null + schemaType: string + phone: string | null + email: string | null + socials?: { network: string; url: string }[] + address: { + street: string | null + locality: string | null + region: string | null + postalCode: string | null + country: string | null + } | null +} + +/** + * schema.org LocalBusiness data for this business, rendered into the page. + * + * Server-side, and revalidated hourly rather than per request: this is the + * same answer for every visitor, and a crawler should not be waiting on our + * backend to see a page. + */ +export default async function BusinessSchema() { + const base = process.env.NEXT_PUBLIC_VULA_COMMERCE_URL + const id = process.env.NEXT_PUBLIC_VULA_PROJECT_ID + if (!base || !id) return null + + let p: Profile | null = null + try { + const res = await fetch(base + '/api/public/profile/' + id, { next: { revalidate: 3600 } }) + if (res.ok) p = (await res.json())?.profile ?? null + } catch { + // A page without structured data still renders. A page that fails to + // render because our backend is slow does not. + return null + } + if (!p?.businessName) return null + + const data: Record = { + '@context': 'https://schema.org', + '@type': p.schemaType || 'LocalBusiness', + name: p.businessName, + } + if (p.phone) data.telephone = p.phone + if (p.email) data.email = p.email + // sameAs is how Google ties this site to her Facebook and Instagram pages — + // the same entity match that decides whether she appears in the map listing. + if (p.socials?.length) data.sameAs = p.socials.map((s) => s.url) + if (p.address) { + data.address = { + '@type': 'PostalAddress', + streetAddress: p.address.street, + addressLocality: p.address.locality, + ...(p.address.region ? { addressRegion: p.address.region } : {}), + ...(p.address.postalCode ? { postalCode: p.address.postalCode } : {}), + addressCountry: p.address.country || 'ZA', + } + } + + return ( +