
- Mise à jour des métadonnées SEO dans index.html pour un meilleur référencement - Ajout de la prise en charge des balises Open Graph et Twitter pour les partages sur les réseaux sociaux - Intégration de données structurées pour les pages About, Fiverr et Home - Ajout d'un fichier robots.txt pour contrôler l'accès des robots d'indexation - Création d'un fichier sitemap.xml pour améliorer la découverte des pages par les moteurs de recherche - Ajout d'un fichier site.webmanifest pour la prise en charge des applications web progressives - Mise à jour des traductions pour refléter les changements dans le contenu et les services - Amélioration de l'accessibilité avec des attributs ARIA dans les composants de l'interface utilisateur
190 lines
5.1 KiB
TypeScript
190 lines
5.1 KiB
TypeScript
import { onMounted, onUnmounted } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
|
|
interface SeoOptions {
|
|
title?: string
|
|
description?: string
|
|
keywords?: string
|
|
ogTitle?: string
|
|
ogDescription?: string
|
|
ogImage?: string
|
|
ogUrl?: string
|
|
twitterCard?: string
|
|
twitterTitle?: string
|
|
twitterDescription?: string
|
|
twitterImage?: string
|
|
canonicalUrl?: string
|
|
structuredData?: object
|
|
noindex?: boolean
|
|
nofollow?: boolean
|
|
}
|
|
|
|
export function useSeo(options: SeoOptions = {}) {
|
|
const route = useRoute()
|
|
const originalTitle = document.title
|
|
const metaElements: HTMLMetaElement[] = []
|
|
const linkElements: HTMLLinkElement[] = []
|
|
const scriptElements: HTMLScriptElement[] = []
|
|
|
|
const setTitle = (title: string) => {
|
|
document.title = title
|
|
setMetaTag('og:title', title, true)
|
|
setMetaTag('twitter:title', title, true)
|
|
}
|
|
|
|
const setMetaTag = (name: string, content: string, property?: boolean) => {
|
|
let meta = document.querySelector(`meta[${property ? 'property' : 'name'}="${name}"]`) as HTMLMetaElement
|
|
|
|
if (!meta) {
|
|
meta = document.createElement('meta')
|
|
if (property) {
|
|
meta.setAttribute('property', name)
|
|
} else {
|
|
meta.setAttribute('name', name)
|
|
}
|
|
document.head.appendChild(meta)
|
|
metaElements.push(meta)
|
|
}
|
|
|
|
meta.setAttribute('content', content)
|
|
}
|
|
|
|
const setLinkTag = (rel: string, href: string) => {
|
|
let link = document.querySelector(`link[rel="${rel}"]`) as HTMLLinkElement
|
|
|
|
if (!link) {
|
|
link = document.createElement('link')
|
|
link.setAttribute('rel', rel)
|
|
document.head.appendChild(link)
|
|
linkElements.push(link)
|
|
}
|
|
|
|
link.setAttribute('href', href)
|
|
}
|
|
|
|
const setStructuredData = (data: object) => {
|
|
const script = document.createElement('script')
|
|
script.type = 'application/ld+json'
|
|
script.textContent = JSON.stringify(data)
|
|
document.head.appendChild(script)
|
|
scriptElements.push(script)
|
|
}
|
|
|
|
onMounted(() => {
|
|
// Set title with site name suffix
|
|
if (options.title) {
|
|
const fullTitle = options.title.includes('Killian') ? options.title : `${options.title} | Killian - Full Stack Developer`
|
|
setTitle(fullTitle)
|
|
}
|
|
|
|
// Set description
|
|
if (options.description) {
|
|
setMetaTag('description', options.description)
|
|
setMetaTag('og:description', options.ogDescription || options.description, true)
|
|
setMetaTag('twitter:description', options.twitterDescription || options.description, true)
|
|
}
|
|
|
|
// Set keywords
|
|
if (options.keywords) {
|
|
setMetaTag('keywords', options.keywords)
|
|
}
|
|
|
|
// Set Open Graph tags
|
|
if (options.ogTitle) {
|
|
setMetaTag('og:title', options.ogTitle, true)
|
|
}
|
|
|
|
if (options.ogImage) {
|
|
setMetaTag('og:image', options.ogImage, true)
|
|
setMetaTag('twitter:image', options.twitterImage || options.ogImage, true)
|
|
}
|
|
|
|
if (options.ogUrl || route) {
|
|
const url = options.ogUrl || `https://killian-portfolio.com${route.path}`
|
|
setMetaTag('og:url', url, true)
|
|
setMetaTag('twitter:url', url, true)
|
|
}
|
|
|
|
// Set Twitter Card
|
|
setMetaTag('twitter:card', options.twitterCard || 'summary_large_image', true)
|
|
|
|
// Set canonical URL
|
|
if (options.canonicalUrl || route) {
|
|
const canonicalUrl = options.canonicalUrl || `https://killian-portfolio.com${route.path}`
|
|
setLinkTag('canonical', canonicalUrl)
|
|
}
|
|
|
|
// Set robots meta
|
|
const robotsContent = []
|
|
if (options.noindex) robotsContent.push('noindex')
|
|
if (options.nofollow) robotsContent.push('nofollow')
|
|
if (robotsContent.length === 0) robotsContent.push('index', 'follow')
|
|
setMetaTag('robots', robotsContent.join(', '))
|
|
|
|
// Set default Open Graph type
|
|
setMetaTag('og:type', 'website', true)
|
|
setMetaTag('og:site_name', 'Killian Portfolio', true)
|
|
|
|
// Set structured data
|
|
if (options.structuredData) {
|
|
setStructuredData(options.structuredData)
|
|
}
|
|
|
|
// Default structured data for breadcrumbs
|
|
if (route && route.name !== 'home') {
|
|
const breadcrumbData = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'BreadcrumbList',
|
|
'itemListElement': [
|
|
{
|
|
'@type': 'ListItem',
|
|
'position': 1,
|
|
'name': 'Home',
|
|
'item': 'https://killian-portfolio.com'
|
|
},
|
|
{
|
|
'@type': 'ListItem',
|
|
'position': 2,
|
|
'name': options.title || route.name,
|
|
'item': `https://killian-portfolio.com${route.path}`
|
|
}
|
|
]
|
|
}
|
|
setStructuredData(breadcrumbData)
|
|
}
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
// Restore original title
|
|
document.title = originalTitle
|
|
|
|
// Remove meta tags we added
|
|
metaElements.forEach(meta => {
|
|
if (meta.parentNode) {
|
|
meta.parentNode.removeChild(meta)
|
|
}
|
|
})
|
|
|
|
// Remove link tags we added
|
|
linkElements.forEach(link => {
|
|
if (link.parentNode) {
|
|
link.parentNode.removeChild(link)
|
|
}
|
|
})
|
|
|
|
// Remove script tags we added
|
|
scriptElements.forEach(script => {
|
|
if (script.parentNode) {
|
|
script.parentNode.removeChild(script)
|
|
}
|
|
})
|
|
})
|
|
|
|
return {
|
|
setTitle,
|
|
setMetaTag,
|
|
setLinkTag,
|
|
setStructuredData
|
|
}
|
|
}
|