feat(seo): amélioration du référencement et ajout de nouvelles fonctionnalités

- 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
This commit is contained in:
Mr¤KayJayDee
2025-06-22 20:40:08 +02:00
parent 82147b5ca6
commit 104c667ab9
20 changed files with 958 additions and 376 deletions

View File

@@ -1,19 +1,35 @@
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) => {
@@ -33,29 +49,109 @@ export function useSeo(options: SeoOptions = {}) {
meta.setAttribute('content', content)
}
onMounted(() => {
if (options.title) {
setTitle(options.title)
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.ogDescription) {
setMetaTag('og:description', options.ogDescription, 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(() => {
@@ -68,10 +164,26 @@ export function useSeo(options: SeoOptions = {}) {
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
setMetaTag,
setLinkTag,
setStructuredData
}
}