--- export type Image = { src: string; alt: string; }; export type SEOMetadata = { name: string; title: string; description: string; image: Image; canonicalURL?: URL | string | null; locale?: string; }; export type OpenGraph = Partial & { type?: string; }; export type Twitter = Partial & { handle?: string; card?: 'summary' | 'summary_large_image'; }; export type Props = SEOMetadata & { og?: OpenGraph; twitter?: Twitter; }; const { name, title, description, image, locale = 'en', canonicalURL = new URL(Astro.url.pathname, Astro.site), } = Astro.props; const og = { name, title, description, canonicalURL, image, locale, type: 'website', ...Astro.props.og, } satisfies OpenGraph; const twitter = { name, title, description, canonicalURL, image, locale, card: 'summary_large_image', ...Astro.props.twitter, } satisfies Twitter; function formatCanonicalURL(url: string | URL) { const path = url.toString(); const hasQueryParams = path.includes('?'); if (hasQueryParams) path.replace(/\/?$/, ''); return path.replace(/\/?$/, hasQueryParams ? '' : ''); } --- {canonicalURL && } {og.canonicalURL && } {og.image && } {og.image && } {twitter.card && } {twitter.handle && } {twitter.image && } {twitter.image && }