SEO
All checks were successful
Build and Deploy Nuxt / build (push) Successful in 30s

This commit is contained in:
2026-06-09 18:36:09 +02:00
parent 3da7424418
commit 09b44952df
23 changed files with 3652 additions and 8 deletions

View File

@@ -0,0 +1,76 @@
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)
}
] : []
})
}