feat: SEO complet + analytics Umami + og:image § logo
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
SEO : - composable useSeoPage() : og:*, Twitter Cards, canonical sur toutes les pages (15 pages) - app.vue : JSON-LD Organization + Book, og:image global og-default.png - og-default.png 1200×630 : logo § calligraphique + texte (Pillow) - nuxt.config.ts : @nuxtjs/sitemap avec 26 URLs statiques Analytics Umami : - useTracking() : helpers typés audio/pdf/player/scroll/cta - useScrollTracking() : scroll depth 25/50/75/100% + liens externes auto - useAudioPlayer : trackAudioPlay/Progress/Complete - BookPdfReader : trackPdfOpen/Close avec durée - BookPlayer : trackPlayerOpen/Chapter/Mode - docker-compose : variables NUXT_PUBLIC_UMAMI_* passées au container Images : - Couv-Economie-du-don.jpg ajoutée dans public/images/ - bookplayer.config.yml + home.yml : références mises à jour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,19 @@
|
||||
/**
|
||||
* Umami analytics wrapper — safe server-side, no-op when not configured.
|
||||
* Usage: const { track } = useTracking()
|
||||
* track('player:open')
|
||||
* track('axis:navigate', { axis: 'numerique' })
|
||||
*
|
||||
* Events convention:
|
||||
* audio:play | audio:pause | audio:complete | audio:progress
|
||||
* pdf:open | pdf:close
|
||||
* player:open | player:chapter | player:mode
|
||||
* scroll:depth
|
||||
* link:external
|
||||
* cta:click
|
||||
*/
|
||||
export function useTracking() {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const enabled = !!runtimeConfig.public.umamiWebsiteId
|
||||
|
||||
// ── Core ────────────────────────────────────────────────────────────────
|
||||
function track(event: string, data?: Record<string, unknown>) {
|
||||
if (!import.meta.client || !enabled) return
|
||||
const umami = (window as Record<string, unknown>).umami as
|
||||
@@ -16,5 +22,75 @@ export function useTracking() {
|
||||
umami?.track(event, data)
|
||||
}
|
||||
|
||||
return { track, enabled }
|
||||
// ── Audio ────────────────────────────────────────────────────────────────
|
||||
function trackAudioPlay(songId: string, songTitle: string, context?: string) {
|
||||
track('audio:play', { song_id: songId, song_title: songTitle, context })
|
||||
}
|
||||
|
||||
function trackAudioPause(songId: string, progressPct: number) {
|
||||
track('audio:pause', { song_id: songId, progress_pct: progressPct })
|
||||
}
|
||||
|
||||
function trackAudioComplete(songId: string, songTitle: string) {
|
||||
track('audio:complete', { song_id: songId, song_title: songTitle })
|
||||
}
|
||||
|
||||
/** Fired at 25 / 50 / 75 % milestones */
|
||||
function trackAudioProgress(songId: string, milestone: 25 | 50 | 75) {
|
||||
track('audio:progress', { song_id: songId, milestone })
|
||||
}
|
||||
|
||||
// ── PDF ──────────────────────────────────────────────────────────────────
|
||||
function trackPdfOpen(trigger?: string) {
|
||||
track('pdf:open', { trigger })
|
||||
}
|
||||
|
||||
function trackPdfClose(pagesVisited: number, durationMs: number) {
|
||||
track('pdf:close', { pages_visited: pagesVisited, duration_ms: durationMs })
|
||||
}
|
||||
|
||||
// ── BookPlayer ───────────────────────────────────────────────────────────
|
||||
function trackPlayerOpen(trigger?: string) {
|
||||
track('player:open', { trigger })
|
||||
}
|
||||
|
||||
function trackPlayerChapter(chapterSlug: string) {
|
||||
track('player:chapter', { chapter_slug: chapterSlug })
|
||||
}
|
||||
|
||||
function trackPlayerMode(mode: 'guided' | 'scroll') {
|
||||
track('player:mode', { mode })
|
||||
}
|
||||
|
||||
// ── Navigation & UX ──────────────────────────────────────────────────────
|
||||
function trackScrollDepth(page: string, depth: 25 | 50 | 75 | 100) {
|
||||
track('scroll:depth', { page, depth })
|
||||
}
|
||||
|
||||
function trackExternalLink(url: string, label?: string) {
|
||||
const route = useRoute()
|
||||
track('link:external', { url, label, from_page: route.path })
|
||||
}
|
||||
|
||||
function trackCta(label: string, target?: string) {
|
||||
const route = useRoute()
|
||||
track('cta:click', { label, target, from_page: route.path })
|
||||
}
|
||||
|
||||
return {
|
||||
track,
|
||||
enabled,
|
||||
trackAudioPlay,
|
||||
trackAudioPause,
|
||||
trackAudioComplete,
|
||||
trackAudioProgress,
|
||||
trackPdfOpen,
|
||||
trackPdfClose,
|
||||
trackPlayerOpen,
|
||||
trackPlayerChapter,
|
||||
trackPlayerMode,
|
||||
trackScrollDepth,
|
||||
trackExternalLink,
|
||||
trackCta,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user