82 lines
2.8 KiB
TypeScript
82 lines
2.8 KiB
TypeScript
import type { Metadata } from 'next'
|
|
|
|
interface Profile {
|
|
businessName: string | null
|
|
locality: string | null
|
|
logoUrl: string | null
|
|
siteUrl: string | null
|
|
}
|
|
|
|
async function profile(): Promise<Profile | null> {
|
|
const base = process.env.NEXT_PUBLIC_VULA_COMMERCE_URL
|
|
const id = process.env.NEXT_PUBLIC_VULA_PROJECT_ID
|
|
if (!base || !id) return null
|
|
try {
|
|
const res = await fetch(base + '/api/public/profile/' + id, { next: { revalidate: 3600 } })
|
|
return res.ok ? ((await res.json())?.profile ?? null) : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A title for one section of the site.
|
|
*
|
|
* "Services | Riverside Dental, Melville" — the section first, because
|
|
* Google truncates the end and the section is what distinguishes this page
|
|
* from every other page on the site. The town is included when we know it:
|
|
* someone searching "chiropractor Richards Bay" is searching for the town as
|
|
* much as the trade.
|
|
*
|
|
* Falls back to the bare section name. A title that says "Services" is worse
|
|
* than one that says "Services | Riverside Dental" and far better than a
|
|
* template that reads "Services | undefined".
|
|
*/
|
|
export async function sectionMetadata(section: string, description?: string): Promise<Metadata> {
|
|
const p = await profile()
|
|
const where = p?.businessName
|
|
? p.locality
|
|
? p.businessName + ', ' + p.locality
|
|
: p.businessName
|
|
: null
|
|
|
|
const title = where ? section + ' | ' + where : section
|
|
|
|
return {
|
|
title,
|
|
...(description ? { description } : {}),
|
|
// The card WhatsApp, Facebook and Slack show when someone pastes the link.
|
|
// Without og: tags they fall back to the title and description, which is why
|
|
// a share already reads correctly — the picture is the only thing missing.
|
|
//
|
|
// Her own logo, or nothing. Never a stock photograph: the same one can end
|
|
// up on several sites, and a gleaming clinic on the card with a modest room
|
|
// at the other end is the kind of overselling this whole pass removed.
|
|
openGraph: {
|
|
title,
|
|
...(description ? { description } : {}),
|
|
...(p?.businessName ? { siteName: p.businessName } : {}),
|
|
...(p?.siteUrl ? { url: p.siteUrl } : {}),
|
|
type: 'website',
|
|
locale: 'en_ZA',
|
|
...(p?.logoUrl ? { images: [{ url: p.logoUrl }] } : {}),
|
|
},
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A page that must never appear in search results.
|
|
*
|
|
* A customer's own booking page is reached from a WhatsApp message with a
|
|
* code in the link. Those links get forwarded, pasted into groups and picked
|
|
* up, and a booking of hers turning up in Google is a breach she would hear
|
|
* about from the patient, not from us. Checkout and account pages are the
|
|
* same: private by purpose, not by obscurity.
|
|
*/
|
|
export function privatePage(section: string): Metadata {
|
|
return {
|
|
title: section,
|
|
robots: { index: false, follow: false },
|
|
}
|
|
}
|