162 lines
4.0 KiB
TypeScript
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,
|
|
}
|
|
}
|