Solid
SolidJS a un wrapper dédié : @vskstudio/takt-solid (composant, accesseur, click déclaratif, web component), bâti sur le cœur @vskstudio/takt-core.
pnpm add @vskstudio/takt-solid @vskstudio/takt-core solid-js (^1.8) et @vskstudio/takt-core (>=0.10.0 depuis la 0.8.0) sont des peer dependencies. C’est une fine couche sûre au SSR (l’init n’a lieu qu’au montage, côté navigateur), entièrement typée, qui ne touche jamais au payload réseau ni aux garanties de vie privée du cœur. Elle expose deux points d’entrée :
@vskstudio/takt-solid: le composant<Takt>, l’accesseuruseTakt(),createTaktEvent(), le composant<TaktEvent>, les widgets<TaktBadge>/<TaktEmbed>, ainsi que les helpers du cœur réexportés (badgeUrl(),embedUrl(),createStats(),PublicApiError,optOut(),optIn(),isOptedOut()).@vskstudio/takt-solid/element: le web component<takt-analytics>sans Solid, pour du HTML pur.
Composant <Takt> + useTakt()
Place <Takt> une fois près de la racine : il boote l’analytics dans onMount, émet le pageview initial, câble la navigation SPA et publie l’instance dans un store de module. useTakt() la récupère ensuite depuis n’importe où dans l’application, à condition d’appeler l’accesseur après ce montage.
import type { JSX } from 'solid-js'
import { Takt } from '@vskstudio/takt-solid'
export function App(props: { children: JSX.Element }) {
return (
<Takt domain="exemple.fr" outbound files={['pdf', 'zip']}>
{props.children}
</Takt>
)
} Appelle useTakt() dans le gestionnaire, pas dans le corps du composant : le corps d’un descendant s’exécute avant le onMount de <Takt>, donc une instance capturée là resterait le no-op à vie et tous les events partiraient dans le vide. Le gestionnaire de clic, un createEffect ou createTaktEvent() résolvent l’instance après le montage.
import { useTakt } from '@vskstudio/takt-solid'
export function SignupButton() {
return (
<button
onClick={() =>
useTakt().track('Signup', {
props: { plan: 'pro' },
revenue: { amount: '29.00', currency: 'EUR' }
})
}
>
S'inscrire
</button>
)
} Props du composant <Takt> :
| Prop | Type | Défaut | Rôle |
|---|---|---|---|
domain | string | location.hostname | Identifiant du site |
endpoint | string | https://taktlytics.com/api/event | URL d’ingestion. Passe /api/event pour un proxy first-party same-origin. |
scriptOrigin | string | aucun | Origine first-party dont dériver l’endpoint (l’origine suivie de /api/event). endpoint l’emporte. |
outbound | boolean | false | Suit les liens sortants |
files | boolean \| string[] | false | Suit les téléchargements (liste d’extensions optionnelle) |
spa | boolean | true | Suit la navigation client (pageviews auto) |
track404 | boolean | false | Signale un événement 404 sur les pages d’erreur (marqueur [data-takt-404] / <meta name="takt:404">, ou statut HTTP 404). |
tagged | boolean | false | Suit automatiquement les clics sur les éléments [data-takt-event] |
respectDnt | boolean | true | Respecte Do Not Track |
excludeLocalhost | boolean | true | Ignore localhost / IP privées |
enabled | boolean | true | Interrupteur général : false coupe toute mesure |
sampleRate | number | 1 | Fraction de sessions échantillonnées (0 à 1) |
trackQuery | boolean | false | Conserve la query string dans les URL |
queryParams | string[] | aucun | Paramètres conservés quand trackQuery est false (liste blanche) |
exclude | string[] | aucun | Préfixes de chemin jamais suivis. Bornés au segment : /app couvre /app et /app/… mais pas /application. |
scrubUrl | (url: string) => string | aucun | Transforme chaque URL avant envoi (page, referrer, url des liens sortants et des téléchargements, path des 404) |
debug | boolean | false | Trace chaque payload dans la console avant l’envoi |
redactRoutes | string[] | aucun | Motifs de routes envoyés à la place du chemin réel, ex. ['/verify/:token'] (voir Masquage de routes) |
redactUndecodablePaths | boolean | true | Non exposée par ce wrapper : se règle sur le SDK core (init() / createTakt()), voir Chemins illisibles |
routeTemplates | boolean | false | Envoie chaque page sous la forme de son gabarit de route (/blog/:slug) ; exige routeTemplate |
routeTemplate | () => string \| null \| undefined | aucun | Renvoie le gabarit de la route courante : solidRouterTemplate(useCurrentMatches()) avec @solidjs/router |
Les props de config sont lues une seule fois au montage de <Takt>. Les changer ensuite n’a aucun effet, remonte donc le composant pour reconfigurer. routeTemplate fait exception : il est relu à chaque pageview.
enabled, sampleRate, trackQuery, queryParams, exclude, scrubUrl) sont disponibles en props, sans instancier le cœur à côté, tout comme debug depuis la 0.7.0.useTakt() ne lève jamais : appelé hors d’un <Takt>, pendant le SSR, ou avant que <Takt> ne soit monté, il renvoie une instance no-op qui n’émet rien sur le réseau et avertit une fois en console (useTakt() called before <Takt> mounted). Ce message est le symptôme du piège ci-dessus.
Le consentement, lui, marche avant le montage : useTakt().optOut(), useTakt().optIn() et useTakt().isOptedOut() agissent directement sur le choix stocké, tout comme les fonctions optOut, optIn et isOptedOut exportées par le package. Une bannière de consentement peut donc s’afficher en premier, et l’instance créée ensuite respecte le choix.
import { createSignal } from 'solid-js'
import { isOptedOut, optIn, optOut } from '@vskstudio/takt-solid'
function AnalyticsToggle() {
const [blocked, setBlocked] = createSignal(isOptedOut())
const toggle = () => {
if (blocked()) optIn()
else optOut()
setBlocked(isOptedOut())
}
return <button onClick={toggle}>{blocked() ? 'Activer la mesure' : 'Désactiver la mesure'}</button>
} Masquage de routes
La query string est retirée par défaut, mais le chemin part en clair : /verify/abc123 transmet le jeton. redactRoutes liste les routes sensibles ; un chemin qui correspond part sous la forme du motif, tous les autres gardent leur valeur réelle.
<Takt domain="exemple.fr" redactRoutes={['/verify/:token', '/reset/:code', '/invoices/:id']}>
<App />
</Takt> Les motifs acceptent la syntaxe Solid Router (:param, :param?, *reste) ainsi que [param], [[optionnel]], [...reste], (groupe), * et **. La règle couvre l’URL de la page, le referrer du même site, les liens sortants et les téléchargements, et le chemin des 404.
Pour une application entièrement privée, routeTemplates envoie chaque page sous la forme de son gabarit : /blog/bonjour devient /blog/:slug. Avec @solidjs/router, appelle useCurrentMatches() dans un composant rendu sous le routeur et passe-le à solidRouterTemplate(), qui renvoie le pattern de la route trouvée la plus profonde :
import { Router, Route, useCurrentMatches, type RouteSectionProps } from '@solidjs/router'
import { Takt, solidRouterTemplate } from '@vskstudio/takt-solid'
function Layout(props: RouteSectionProps) {
const matches = useCurrentMatches()
return (
<Takt domain="exemple.fr" routeTemplates routeTemplate={solidRouterTemplate(matches)}>
{props.children}
</Takt>
)
}
export function App() {
return (
<Router root={Layout}>
<Route path="/" component={Home} />
<Route path="/blog/:slug" component={Post} />
</Router>
)
} solidRouterTemplate n’attend qu’un accesseur qui renvoie { route: { pattern } }[] : le package ne dépend pas de @solidjs/router. Quand aucune route ne correspond, redactRoutes s’applique quand même, sinon le chemin réel part. Sur un site public, ce mode fusionne tous les articles en une seule ligne : préfère redactRoutes. Détails dans Vie privée.
createTaktEvent() + <TaktEvent>
Pour du tracking de clic déclaratif, deux voies qui résolvent l’instance active au moment du clic (pas de closure périmée), avec repli sur l’instance par défaut du cœur.
createTaktEvent() renvoie un { onClick } que tu étales sur n’importe quel élément :
import { createTaktEvent } from '@vskstudio/takt-solid'
export function BuyButton() {
const onBuy = createTaktEvent({
name: 'Purchase',
revenue: { amount: '29.00', currency: 'EUR' }
})
return <button {...onBuy}>Acheter</button>
} <TaktEvent> enveloppe un unique enfant et compose son onClick existant, pour annoter un élément sans toucher à son handler :
import { TaktEvent } from '@vskstudio/takt-solid'
export function SignupCta(props: { onClick: () => void }) {
return (
<TaktEvent name="Signup" props={{ plan: 'pro' }}>
<button onClick={props.onClick}>S'inscrire</button>
</TaktEvent>
)
} Le onClick d’origine de l’enfant se déclenche toujours ; le tracking tourne en parallèle. L’enfant doit rendre un véritable élément DOM : sinon le tracking est désactivé, avec un avertissement en console.
Widgets <TaktBadge> et <TaktEmbed>
Pour afficher les statistiques publiques d’un site (voir Widgets), le package fournit deux composants prêts à poser :
import { TaktBadge, TaktEmbed } from '@vskstudio/takt-solid'
export function Footer() {
return (
<>
<TaktBadge domain="exemple.fr" variant="b" glyph="dash" lang="fr" />
<TaktEmbed domain="exemple.fr" theme="dark" lang="fr" />
</>
)
} <TaktBadge> rend une <img> (loading="lazy", decoding="async") : variant vaut a, b ou d, glyph vaut unplug, dash, off ou eyeoff. <TaktEmbed> rend une <iframe> de 404×264 par défaut, loading="lazy" et sandboxée : theme vaut light, dark ou auto. Les deux acceptent lang (fr / en), une prop host pour pointer une autre origine Takt, et forwardent le reste de leurs attributs à l’élément rendu. Le client createStats() réexporté du cœur sert les mêmes statistiques publiques en JSON.
Web component <takt-analytics>
Pour du HTML pur ou un framework non-Solid, importe le sous-chemin ./element (auto-enregistré, embarque le cœur, aucun runtime Solid) :
import '@vskstudio/takt-solid/element' <takt-analytics domain="exemple.fr" outbound files></takt-analytics> Les attributs miroir des props, en kebab-case :
- Drapeaux (présence = actif, valeur ignorée) :
outbound,files,track-404,tagged. - Actifs par défaut, mets
="false"(ou="0") pour les couper :spa,respect-dnt,exclude-localhost. - Valeurs :
domain,endpoint,script-origin,sample-rate, ainsi quequery-params,excludeetredact-routesqui prennent une liste séparée par des virgules.enabled,track-queryetdebugne sont lus qu’en présence de l’attribut, et se coupent avec="false"/="0".
<takt-analytics domain="exemple.fr" spa="false" respect-dnt="false"></takt-analytics> files ne prend pas de liste d’extensions ici, et scrubUrl n’a pas d’équivalent : ce sont des valeurs que seul le composant <Takt> reçoit en props. routeTemplates n’en a pas non plus, puisque l’élément n’a pas de routeur.
<Takt> boote dans onMount et est gardé par isServer de Solid : rien ne touche window/document côté serveur, et useTakt() renvoie le no-op pendant la passe SSR. Importer ./element côté serveur est un no-op tant que customElements n'existe pas.