Files
e2e 6c231d1d36
Some checks failed
Main / Setup and Test (push) Has been cancelled
Main / Build Website (push) Has been cancelled
chore: heygermany customer project
2026-07-11 10:52:27 +02:00

162 lines
4.0 KiB
TypeScript

type MetadataLike = {
title?: string
description?: string
manifest?: string
alternates?: {
canonical?: string
languages?: Record<string, string>
}
icons?: {
icon?: Array<{ url: string; sizes?: string; type?: string }>
apple?: Array<{ url: string; sizes?: string }>
}
openGraph?: {
title?: string
description?: string
url?: string
siteName?: string
type?: string
images?: Array<{ url: string; alt?: string }>
}
twitter?: {
card?: string
title?: string
description?: string
images?: string[]
}
robots?: {
index?: boolean
follow?: boolean
nocache?: boolean
googleBot?: {
index?: boolean
follow?: boolean
noimageindex?: boolean
}
}
}
const pushMeta = (
meta: Array<Record<string, string>>,
key: string,
value?: string
) => {
if (!value) return
meta.push({ [key]: value })
}
export function metadataToHead(metadata: MetadataLike) {
const meta: Array<Record<string, string>> = [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
]
const links: Array<Record<string, string>> = []
if (metadata.title) {
meta.push({ title: metadata.title })
meta.push({ property: 'og:title', content: metadata.title })
meta.push({ name: 'twitter:title', content: metadata.title })
}
pushMeta(meta, 'name', metadata.description ? 'description' : undefined)
if (metadata.description) {
meta.push({ name: 'description', content: metadata.description })
meta.push({ property: 'og:description', content: metadata.description })
meta.push({ name: 'twitter:description', content: metadata.description })
}
if (metadata.manifest) {
links.push({ rel: 'manifest', href: metadata.manifest })
}
for (const icon of metadata.icons?.icon ?? []) {
links.push({
rel: 'icon',
href: icon.url,
...(icon.sizes ? { sizes: icon.sizes } : {}),
...(icon.type ? { type: icon.type } : {}),
})
}
for (const icon of metadata.icons?.apple ?? []) {
links.push({
rel: 'apple-touch-icon',
href: icon.url,
...(icon.sizes ? { sizes: icon.sizes } : {}),
})
}
if (metadata.alternates?.canonical) {
links.push({
rel: 'canonical',
href: metadata.alternates.canonical,
})
}
for (const [hreflang, href] of Object.entries(
metadata.alternates?.languages ?? {}
)) {
links.push({
rel: 'alternate',
hrefLang: hreflang,
href,
})
}
if (metadata.openGraph?.url) {
meta.push({ property: 'og:url', content: metadata.openGraph.url })
}
if (metadata.openGraph?.siteName) {
meta.push({ property: 'og:site_name', content: metadata.openGraph.siteName })
}
if (metadata.openGraph?.type) {
meta.push({ property: 'og:type', content: metadata.openGraph.type })
}
for (const image of metadata.openGraph?.images ?? []) {
meta.push({ property: 'og:image', content: image.url })
if (image.alt) {
meta.push({ property: 'og:image:alt', content: image.alt })
}
}
if (metadata.twitter?.card) {
meta.push({ name: 'twitter:card', content: metadata.twitter.card })
}
for (const image of metadata.twitter?.images ?? []) {
meta.push({ name: 'twitter:image', content: image })
}
if (metadata.robots) {
const robots = [
metadata.robots.index === false ? 'noindex' : 'index',
metadata.robots.follow === false ? 'nofollow' : 'follow',
metadata.robots.nocache ? 'nocache' : undefined,
]
.filter(Boolean)
.join(', ')
if (robots) {
meta.push({ name: 'robots', content: robots })
}
if (metadata.robots.googleBot) {
const googleBot = [
metadata.robots.googleBot.index === false ? 'noindex' : 'index',
metadata.robots.googleBot.follow === false ? 'nofollow' : 'follow',
metadata.robots.googleBot.noimageindex ? 'noimageindex' : undefined,
]
.filter(Boolean)
.join(', ')
if (googleBot) {
meta.push({ name: 'googlebot', content: googleBot })
}
}
}
return {
meta,
links,
}
}