Provedly fonctionne dans une application Next.js avec une seule balise script, chargée une fois depuis votre layout racine. Le widget s'affiche dans un Shadow DOM : il ne touche ni à votre Tailwind ni à vos CSS Modules, et le loader est idempotent lors des changements de route côté client. Le seul travail propre à Next consiste à choisir où placer la balise et, si vous voulez l'attribution, à transmettre l'identifiant visiteur à votre checkout.
L'installation en une ligne
Créez un widget dans le dashboard, copiez le token et ajoutez le script à votre layout racine. Avec l'App Router, next/script est la façon propre de le faire.
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://provedly.app/w/widget.js"
data-token="pp_your_token"
strategy="afterInteractive"
/>
</body>
</html>
);
}
afterInteractive charge le script après l'hydratation, ce qui est le bon compromis pour un élément non critique. N'utilisez pas beforeInteractive ; rien dans une notification d'achat ne justifie de bloquer la page. Le bundle pèse environ 6 Ko compressé en gzip et le loader récupère la configuration du widget via GET /api/w/{token}/config, qui renvoie les réglages et jusqu'à 30 enregistrements récents.
Si vous utilisez encore le Pages Router, placez la même balise <Script> dans pages/_app.tsx. Le comportement est identique.
Changements de route et problème des SPA
Les notifications obéissent à des plafonds par session : par défaut, 3 secondes de délai avant la première, 6 secondes à l'écran, 30 secondes d'intervalle et 5 notifications au maximum par session. Ces plafonds sont suivis par le widget, pas par la page : un visiteur qui parcourt cinq fiches produit ne repart pas pour cinq nouveaux cycles. C'est voulu. Une notification qui se déclenche à chaque navigation est le moyen le plus rapide de passer pour du spam.
Le loader vérifie s'il existe déjà une instance avant de s'initialiser ; même si votre layout remonte la balise script, il n'y aura pas de double affichage. Si vous préférez le monter depuis un composant client, le modèle documenté ressemble à ceci :
'use client';
import { useEffect } from 'react';
export function ProofWidget() {
useEffect(() => {
if (document.querySelector('script[data-token="pp_your_token"]'))
return;
const s = document.createElement('script');
s.src = 'https://provedly.app/w/widget.js';
s.dataset.token = 'pp_your_token';
s.async = true;
document.body.appendChild(s);
}, []);
return null;
}
Affichez <ProofWidget /> une seule fois, dans le layout racine. Le ciblage par URL (toutes les pages, seulement celles-ci, toutes sauf) se configure dans le dashboard : inutile de recourir au rendu conditionnel dans React pour écarter le widget de /account, par exemple.
Style : ce que vous contrôlez et ce que vous ne contrôlez pas
Comme le widget vit dans un Shadow DOM, votre CSS global ne peut pas l'atteindre et son CSS ne peut pas vous atteindre. Cela écarte les collisions de noms de classes et les mauvaises surprises de z-index. L'apparence se choisit dans le dashboard : l'un des cinq thèmes (Clean white, Deep ocean, Midnight, Electric violet, Sunset blaze), le format du nom, le format du lieu et la position. Lisez Shadow DOM : pourquoi le widget ne peut pas casser votre CSS pour les détails.
Si vous appliquez une Content Security Policy stricte, autorisez https://provedly.app pour script-src et connect-src. Le widget envoie ses requêtes aux endpoints config, track et presence de cette origine, et nulle part ailleurs.
Rendre les achats « vérifiés »
Une notification qui affiche « Achat vérifié » est une notification arrivée par un webhook signé. Pour une application Next.js qui encaisse via Stripe Checkout, cela signifie enregistrer https://provedly.app/api/hooks/stripe/{token} comme endpoint de webhook pour checkout.session.completed et coller le secret de signature dans l'onglet Data du widget. La procédure complète se trouve dans Webhooks Stripe pour les achats vérifiés.
Si vous encaissez via votre propre backend, ou via un prestataire sans webhook Provedly, envoyez les enregistrements depuis un Route Handler ou une server action :
// app/api/orders/route.ts, after payment is confirmed
await fetch('https://provedly.app/api/v1/widgets/pp_your_token/records', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PROVEDLY_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: order.customerName,
city: order.shipping.city,
country: order.shipping.countryCode,
product_name: order.lines[0].title,
price: order.totalFormatted,
external_id: order.id,
visitor: order.provedlyVisitor,
}),
});
Les enregistrements envoyés ainsi sont affichés, mais avec l'étiquette « Imported », jamais « Verified ». C'est la règle d'honnêteté : seul un événement signé cryptographiquement par le prestataire de paiement obtient la mention. Voir Utiliser l'API Records pour les achats issus de votre propre backend.
Attribution : transmettre l'identifiant visiteur
Le dashboard affiche les commandes assistées par la preuve, c'est-à-dire celles d'un acheteur qui a vu une notification avant d'acheter, rapprochées par identifiant visiteur sur une fenêtre de 30 jours. Pour que le rapprochement ait lieu, l'achat doit porter cet identifiant. Côté client, lisez-le avec window.Provedly?.visitorId() et envoyez-le à votre code de checkout. Avec Stripe, passez-le dans metadata.provedly_visitor lors de la création de la Checkout Session. Avec l'API Records, envoyez-le dans le champ visitor.
| Étape | Où | Ce qu'il faut passer |
|---|---|---|
| Lire l'identifiant | composant client au checkout | window.Provedly?.visitorId() |
| Stripe Checkout | serveur, création de la session | metadata: { provedly_visitor: id } |
| API Records | serveur, après le paiement | "visitor": id |
Pas d'identifiant, pas d'attribution. Le dashboard ne fait jamais d'estimation.
Checklist avant la mise en production
- Script dans le layout racine avec
strategy="afterInteractive". - Domaine de production ajouté aux domaines autorisés du site ; ajoutez explicitement les hôtes de preview et de staging, car les previews
*.vercel.appne correspondront pas à votre domaine de production. - CSP mise à jour si vous en avez une.
- Webhook enregistré (Stripe) ou API Records branchée (backend sur mesure).
- Identifiant visiteur transmis au checkout.
Questions fréquentes
Le widget fonctionne-t-il avec les React Server Components ?
Oui. La balise script est du HTML simple produit par le layout ; elle n'a pas besoin de frontière client. Le composant optionnel ProofWidget n'est un composant client que parce qu'il utilise useEffect.
La notification va-t-elle provoquer un décalage de mise en page ?
Non. Le widget est positionné en fixed dans sa propre shadow root et n'occupe aucune place dans le flux du document : il ne contribue donc pas au CLS.
Puis-je l'afficher uniquement sur les pages marketing et pas dans l'application ?
Oui. Utilisez le ciblage par URL dans le dashboard (« all except » avec /app/*, ou « only these » avec vos chemins marketing) plutôt que le rendu conditionnel.
Commencez avec le plan Free, qui inclut les achats vérifiés et 2 000 impressions par mois : créer un compte.