All checks were successful
Build and Deploy Nuxt / build (push) Successful in 30s
77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
import { useHead, useSeoMeta, useRoute } from '#imports'
|
|
|
|
interface SeoOptions {
|
|
title?: string
|
|
description?: string
|
|
ogImage?: string
|
|
canonicalUrl?: string
|
|
articleDate?: string
|
|
structuredData?: Record<string, unknown>
|
|
}
|
|
|
|
export function useSeo(options: SeoOptions = {}) {
|
|
const { locale } = useI18n()
|
|
const route = useRoute()
|
|
const baseURL = 'https://aranroig.com'
|
|
|
|
// Map locale codes to BCP 47 tags
|
|
const localeToIso: Record<string, string> = {
|
|
en: 'en-US',
|
|
es: 'es-ES',
|
|
ca: 'ca-ES'
|
|
}
|
|
|
|
const canonicalPath = computed(() => {
|
|
if (!options.canonicalUrl) return ''
|
|
return options.canonicalUrl.startsWith('http') ? options.canonicalUrl : `${baseURL}${options.canonicalUrl}`
|
|
})
|
|
|
|
// Hreflang alternate links for all locales
|
|
const hreflangs = computed(() => {
|
|
const currentPath = typeof route.path === 'string' ? route.path : ''
|
|
const base = currentPath.replace(/^(\/en|\/es|\/ca)?\//, '/')
|
|
|
|
return [
|
|
{ rel: 'alternate', hreflang: 'en', href: `${baseURL}/en${base}` },
|
|
{ rel: 'alternate', hreflang: 'es', href: `${baseURL}/es${base}` },
|
|
{ rel: 'alternate', hreflang: 'ca', href: `${baseURL}/ca${base}` }
|
|
] as Array<{rel: string; hreflang: string; href: string}>
|
|
})
|
|
|
|
const seoTitle = computed(() => {
|
|
if (options.title) return `${options.title} | Aran Roig`
|
|
return 'Aran Roig — Developer, Artist & Designer'
|
|
})
|
|
|
|
const seoDescription = options.description || 'Personal website of Aran Roig — developer, artist, and designer. Explore projects, blog posts, art gallery, and more.'
|
|
|
|
useSeoMeta({
|
|
title: seoTitle.value,
|
|
description: seoDescription,
|
|
ogTitle: seoTitle.value,
|
|
ogDescription: seoDescription,
|
|
ogImage: options.ogImage || `${baseURL}/og-image.png`,
|
|
ogUrl: canonicalPath.value,
|
|
ogType: 'website',
|
|
twitterCard: 'summary_large_image',
|
|
twitterTitle: seoTitle.value,
|
|
twitterDescription: seoDescription,
|
|
twitterImage: options.ogImage || `${baseURL}/og-image.png`,
|
|
canonical: canonicalPath.value
|
|
})
|
|
|
|
useHead({
|
|
htmlAttrs: { lang: computed(() => localeToIso[locale.value] || 'en-US') },
|
|
link: [
|
|
...hreflangs.value,
|
|
...(canonicalPath.value ? [{ rel: 'canonical', href: canonicalPath.value }] : [])
|
|
],
|
|
script: options.structuredData ? [
|
|
{
|
|
type: 'application/ld+json',
|
|
innerHTML: JSON.stringify(options.structuredData)
|
|
}
|
|
] : []
|
|
})
|
|
}
|