Le problème
La plupart des sites d’avis cachent comment leurs notes sont construites, mélangent « testé » et « lu quelque part », et redirigent les clics via des liens que n’importe qui peut détourner.
Travistello fait le pari inverse : un catalogue où chaque produit, variante, caractéristique, source et note est un enregistrement typé, et où la plateforme refuse de publier ce qu’elle ne peut pas prouver.
De la source brute au clic
- 01Produit + variante
- 02Sources
- 03Note versionnée
- 04Offre
- 05Comparatif
- 06Redirection tracée
Règles de confiance et de sécurité dans le code
| RÈGLE | COUCHE | EMPÊCHE |
|---|---|---|
| Redirections /go/[token] opaques, destinations gérées côté serveur | Routage | Open redirect |
| Aucune adresse IP stockée sur les clics sortants, bots filtrés | Analytics | Abus de tracking |
| Les poids doivent faire 100 %, la note globale est calculée | Modèle de données | Notes arrangées |
| Avis sur recherche et avis testés sont deux états distincts | Modèle de données | Faux avis |
| Chaque média exige un ayant droit et une base d’utilisation | CMS | Problèmes de droits |
| Programmes d’affiliation et clics réservés aux admins | Rôles CMS | Fuite de données |
Directement depuis le code
Extraits du vrai dépôt et des livrables, raccourcis pour la lecture.
export const getSafeOutboundURL = (value: null | string | undefined) => {
if (!value) return null;
try {
const url = new URL(value);
if (
!["http:", "https:"].includes(url.protocol) ||
url.username ||
url.password
)
return null;
return url;
} catch {
return null;
}
};Chaque lien sortant passe par ce contrôle : uniquement http(s), jamais d’identifiants intégrés. Les destinations viennent de la base, jamais de l’URL.
export async function GET(request: NextRequest, { params }: RouteContext) {
const { token } = await params;
const offers = await payload.find({
collection: "offers",
limit: 1,
where: {
and: [
{ publicToken: { equals: token } },
{ status: { equals: "active" } },
],
},
});
const offer = offers.docs[0];
if (!offer || !isPublicOffer(offer))
return new NextResponse("Offer not found.", { status: 404 });
const destination = getSafeOutboundURL(offer.affiliateUrl || offer.destinationUrl);
if (!destination)
return new NextResponse("Offer destination is unavailable.", { status: 503 });
const bot = isLikelyBot(request.headers.get("user-agent"));
// ... only non-bot clicks are recorded, with no IP address
}La redirection /go/[token] : le token est résolu côté serveur, les offres inactives renvoient 404, et les bots ne créent jamais de clic.
Résultats
Ce que je ferais autrement
Le plus dur n’a jamais été les fonctionnalités, mais les règles de confiance : décider ce que la plateforme doit refuser d’afficher. Prochaine étape : activer les programmes d’affiliation et ajouter les premiers tests réels comme état de données à part.