Un vérificateur de backlinks web en Next.js, avec la clé conservée sur le serveur
Le CORS est ouvert, donc un navigateur peut appeler l'API directement. Utiliser une clé payante dans ce cas est une erreur. Cette page présente la fine couche serveur qui corrige le problème.

Idée reçue contre réalité
| Idée reçue | Réalité |
|---|---|
| "Un CORS ouvert signifie que je peux embarquer la clé dans mon bundle React." | Un CORS ouvert signifie que l'appel fonctionne. N'importe qui peut toutefois lire la clé dans l'onglet réseau et dépenser votre solde. |
| "Un tableau de liens doit être rendu côté client car il s'agit d'un tableau de bord." | Effectuez le fetch côté serveur et envoyez du HTML. Votre utilisateur voit les lignes dès le premier rendu et votre clé ne quitte jamais le serveur. |
| "Les actions gratuites nécessitent aussi une protection." | domain, overview, stats et suggest n'ont pas besoin de clé, donc un navigateur peut les appeler directement. |
| "Je devrais rafraîchir les données à chaque affichage de page." | L'index est reconstruit environ mensuellement. Une mise en cache de 24 heures ne fait perdre aucune donnée. |
| "Le proxy devrait transmettre tout ce que le client envoie." | Listez les actions que vous utilisez. Un action=search transmis coûte 3 crédits. |
Le gestionnaire de route
App Router, fichier app/api/links/route.ts. La variable d'environnement n'atteint jamais le bundle client car elle ne porte pas le préfixe NEXT_PUBLIC_.
import { NextRequest, NextResponse } from "next/server";
const ALLOWED = new Set(["refdomains", "anchors", "toppages"]);
export async function GET(req: NextRequest) {
const p = req.nextUrl.searchParams;
const action = p.get("action") ?? "";
const q = (p.get("q") ?? "").toLowerCase();
if (!ALLOWED.has(action) || !/^[a-z0-9.-]+$/.test(q)) {
return NextResponse.json({ ok: false, error: "bad request" }, { status: 400 });
}
const url = `https://seomasters.dev/api.php?action=${action}&q=${q}&per=100`;
const upstream = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.SEO_API_KEY}` },
next: { revalidate: 86400 },
});
if (upstream.status === 402) {
return NextResponse.json({ ok: false, error: "data temporarily unavailable" }, { status: 503 });
}
return new NextResponse(upstream.body, { status: upstream.status, headers: { "content-type": "application/json" } });
}Pourquoi le gestionnaire ressemble à cela
Liste des actions autorisées
Un bug dans votre interface ne devrait pas pouvoir déclencher accidentellement un appel search de 3 crédits.
Regex sur q
Seul un nom d'hôte nu est autorisé. Aucun schéma, aucun chemin, aucune chaîne de requête ne peut être dissimulé et envoyé en amont.
revalidate: 86400
Un appel en amont par domaine par jour. Avec des données mensuelles, une durée plus longue est également acceptable.
402 mappé en 503
Vos visiteurs n'ont pas besoin de savoir que votre solde est vide. Vous, si, donc journalisez-le.

Rendu : ce que voient à la fois un crawler et un client
Si ce tableau de bord est protégé par une connexion, le choix entre le rendu côté serveur ou client est une question d'expérience utilisateur. Si l'une des pages est publique, par exemple une page de consultation gratuite que vous souhaitez indexer, rendez le premier tableau côté serveur.
Une page qui arrive sous forme de coquille vide et se remplit après l'hydratation présente un risque pour l'indexation et pour les téléphones lents. Google peut exécuter JavaScript, mais il met cette étape en file d'attente et ne la traite pas toujours rapidement.
Conservez l'aperçu gratuit côté serveur
Placez l'appel overview (gratuit et peu coûteux) dans un Composant Serveur et laissez le visiteur déclencher les appels payants et plus profonds depuis un bouton. Cette séparation permet également de maîtriser vos dépenses en crédits.
Les appels gratuits ne coûtent rien, donc un aperçu rendu côté serveur n'engendre aucune dépense de crédit. Seuls les appels payants nécessitent un bouton et une session.
Pensez à l'état vide en premier. Un visiteur qui atterrit sur un tableau vide supposera que l'outil est cassé, et un deuxième rendu rapide n'efface pas cette première impression.
Sélectionnez les actions gratuites pour le premier écran et les actions payantes pour les détails. Un visiteur qui ouvre une page et voit un résumé gratuit n'a aucune raison d'appuyer accidentellement sur un bouton payant.
Lie l'explication, pas seulement le chiffre
Intégrer les chiffres est la partie facile. Les expliquer est plus difficile, alors liez la formule DR depuis l'infobulle et mentionnez que l'index se rafraîchit mensuellement.
Un visiteur qui voit un score sans sa formule va deviner, donc le lien doit figurer à côté du chiffre à l'écran.

Parties serveur et client d'un tableau de bord unique
La plupart des tableaux de bord nécessitent les deux types de code. La propriété détermine la répartition. La clé, les données et les règles d'accès ont chacun un lieu unique. Une page de vérificateur de backlinks web publique nécessite la même répartition qu'une page protégée par une connexion.
Fetch côté serveur, tri côté client
Un tableau de backlinks est un composant client raisonnable lorsque les utilisateurs trient, filtrent et paginent. Les lignes doivent provenir d'un composant serveur qui a déjà appelé votre route, afin que le navigateur ne détienne jamais la clé ni l'URL en amont.
Trier dix lignes est instantané côté client. Trier cinq mille lignes à chaque nouvelle requête est lent, donc conservez l'ensemble complet dans le composant que vous avez déjà chargé.
Restez simple. Un tableau qui trie instantanément vaut mieux qu'un qui re-fetch à chaque clic.
Gardez la limite client basse
Poussez la limite aussi bas que possible dans l'arborescence. Un widget importé dynamiquement et réservé au client s'affiche dans le navigateur, tandis que le composant qui a récupéré les lignes reste sur le serveur. Transmettez les lignes en tant que props et conservez le fetch au-dessus de la limite.
Si vous déplacez le fetch en dessous de la limite, le navigateur effectue l'appel lui-même. C'est l'erreur mentionnée dans la section sur la fuite de clé, donc vérifiez l'onglet Réseau après chaque modification de cette limite.
Où exactement se situe la ligne ? Dessinez-la d'abord sur papier, avec une case pour le fetch et une case pour chaque contrôle interactif, et comptez les flèches qui la traversent.
La coquille et les vérifications de session restent côté serveur
La mise en page et les vérifications de session déterminent ce qu'un visiteur peut voir, donc elles appartiennent au serveur. Les tableaux interactifs, les formulaires et les graphiques peuvent s'hydrater ensuite sans modifier cette décision.
Une garde côté client peut masquer un bouton, mais elle ne peut pas arrêter une requête. Le gestionnaire de route et la mise en page serveur sont les endroits où l'accès est réellement appliqué.
Ensuite, envoyez la même requête sans le cookie et confirmez que le serveur la refuse, et pas seulement la page.
La même règle s'applique au proxy lui-même. Une vérification de session qui n'a lieu que dans le navigateur ne peut pas protéger la route, donc la route vérifie également la session avant de dépenser un crédit.
Cache par domaine, pas par visiteur
Le gestionnaire construit son URL en amont à partir du domaine, donc la clé de cache suit également le domaine. Un visiteur qui consulte un site réchauffe la page pour le prochain visiteur qui demande le même site.
C'est l'intérêt de la revalidate quotidienne dans le gestionnaire. Deux visiteurs sur le même domaine coûtent un appel en amont, et mille visiteurs sur mille domaines coûtent mille appels, donc le cache est le budget.
Surveillez également le cas inverse. Une page qui varie sa chaîne de requête par un horodatage annule entièrement le cache, et la facture le montre avant les logs.
Revalidez selon un calendrier qui correspond à l'index. Une fenêtre quotidienne convient aux données mensuelles, et une fenêtre plus courte n'ajoute que des appels sans ajouter de nouvelles informations.
Durcissement, dans l'ordre où nous le ferions
Le gestionnaire est la partie facile. Le durcissement est là où une clé fuit ou un solde s'épuise, donc travaillez à travers ces points dans l'ordre.
Scannez votre propre onglet Réseau
Ouvrez Chrome DevTools sur votre propre tableau de bord et lisez l'onglet Réseau. Regardez attentivement. Si une requête y affiche sk_live_ quelque part, faites tourner la clé aujourd'hui. Cette vérification prend une minute et trouve la fuite la plus courante.
Recherchez également dans les corps de réponse ainsi que dans les en-têtes de requête. Une clé échoyée dans un message d'erreur est toujours une fuite.
Limitez les appels par utilisateur
Ensuite, ajoutez une limite par utilisateur. Placez-la devant la route. Le gestionnaire ci-dessus dépense votre crédit pour quiconque peut y accéder, donc liez-la à une session et limitez les appels par heure. Choisissez la limite à partir des calculs budgétaires sur la page des crédits.
Commencez généreux et serrez la vis plus tard. Une limite qui bloque les vrais utilisateurs dès le premier jour les incite à trouver un autre outil.
Nommez qui effectue le rechargement
Vaut également la peine d'une ligne dans le manuel d'exécution : qui est alerté lorsque le solde tombe en dessous d'un certain niveau, et qui effectue le rechargement. Nommez la personne.
Notez l'étape de rechargement sous forme de liste de contrôle avec le lien vers la page de compte et le délai d'intervention attendu. Un manuel d'exécution qui n'existe que dans la tête d'une seule personne échoue la première fois que cette personne est en congé. L'étape elle-même prend quelques minutes, donc la rédaction est le travail le plus important.
Journalisez le solde avant qu'il ne s'épuise
Ensuite, journalisez _credits de chaque réponse en amont dans vos propres métriques. Un graphique du solde restant, échantillonné quotidiennement, vous avertit une semaine avant que le HTTP 402 ne survienne.
Choisissez le niveau d'alerte à partir de votre utilisation hebdomadaire. Un nombre choisi dans un modèle ne correspond rarement à vos appels réels.
Conservez la dernière valeur que vous avez vue en mémoire ainsi que dans le journal, afin que la route puisse refuser les appels payants une fois que le nombre tombe en dessous du niveau d'alerte. Cette vérification tient en une ligne et empêche une rafale de trafic de dépenser le reste.
Conservez la liste des actions autorisées en un seul endroit
Une autre habitude paie. Conservez la liste des actions autorisées dans une constante partagée, importée par le gestionnaire et par les tests, afin qu'une nouvelle action ne puisse pas atteindre la production sans que quelqu'un ne modifie une liste visible et qu'un réviseur ne le remarque.
Un fichier, un import. Personne ne devrait avoir à chercher la liste dans trois composants pour savoir ce que le proxy acceptera.
Testez le runtime que vous déployez
Enfin, exécutez le gestionnaire sous Node 20 ou le runtime edge de votre choix et testez les deux. La diffusion du corps en amont, comme le code le fait, fonctionne dans les deux, mais la gestion des en-têtes diffère et mérite un test.
Ajoutez un test qui appelle la route avec une action invalide et un test avec une action valide, et vérifiez les codes de statut. Ces deux tests capturent la plupart des régressions dans la liste des actions autorisées et la gestion des en-têtes.
Intégration du proxy dans l'ordre
- 1
Ajoutez la clé côté serveur
Stockez la clé dans votre environnement serveur sous un nom sans préfixe public, puis redémarrez le serveur de développement pour qu'il soit chargé.
- 2
Écrivez d'abord la liste des actions autorisées
Listez les actions que vos écrans appellent réellement. Tout le reste recevra un 400 de votre propre route, avant d'atteindre l'API en amont.
- 3
Récupérez votre route depuis un composant serveur
Rendez les lignes là où les données arrivent. Le navigateur n'appelle jamais que votre propre domaine, donc l'adresse en amont reste hors de la page.
- 4
Surveillez le solde
Journalisez
_creditssur chaque réponse en amont et définissez un niveau d'alerte sur lequel vous pouvez agir, avant qu'un 402 n'atteigne un visiteur sous forme de 503.