Un verificatore di backlink web in Next.js, con la chiave mantenuta sul server
CORS è aperto, quindi un browser può chiamare l'API direttamente. Farlo con una chiave a pagamento è l'errore. Questa pagina mostra il sottile strato server che lo risolve.

Mito contro realtà
| Mito | Realtà |
|---|---|
| "CORS aperto significa che posso includere la chiave nel bundle React." | CORS aperto significa che la chiamata funziona. Chiunque può comunque leggere la chiave dalla scheda di rete e consumare il tuo saldo. |
| "Una tabella di link deve essere renderizzata sul client perché è un cruscotto." | Esegui il fetch sul server e invia HTML. Il tuo utente vede le righe al primo rendering e la tua chiave non lascia mai il server. |
| "Anche le azioni gratuite necessitano di protezione." | domain, overview, stats e suggest non richiedono chiave, quindi un browser può chiamarle direttamente. |
| "Dovrei rifare il fetch ad ogni visualizzazione della pagina." | L'indice si ricostruisce circa mensilmente. Una cache di 24 ore non perde nulla. |
| "Il proxy dovrebbe inoltrare tutto ciò che invia il client." | Consenti in allow-list le azioni che utilizzi. Un action=search inoltrato costa 3 crediti. |
Il route handler
App Router, file app/api/links/route.ts. La variabile d'ambiente non raggiunge mai il bundle client perché non ha il prefisso 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" } });
}Perché il handler è strutturato in questo modo
Allow-list delle azioni
Un bug nell'UI non dovrebbe essere in grado di attivare accidentalmente una chiamata search da 3 crediti.
Regex su q
Solo un hostname nudo viene accettato. Nessun protocollo, nessun percorso, nessuna query string nascosta inviata upstream.
revalidate: 86400
Una chiamata upstream per dominio al giorno. Con dati mensili, anche una finestra più lunga va bene.
402 mappato a 503
I tuoi visitatori non devono imparare che il tuo saldo è vuoto. Tu sì, quindi registralo nei log.

Rendering: ciò che vedono sia un crawler che un cliente
Se questo cruscotto è dietro un login, il rendering server-side o client-side è una scelta UX. Se è pubblico, ad esempio una pagina di lookup gratuita che vuoi indicizzare, renderizza la prima tabella sul server.
Una pagina che arriva come un guscio vuoto e si riempie dopo l'idratazione è un rischio per l'indicizzazione e per i dispositivi lenti. Google può eseguire JavaScript, ma accoda quel passaggio e non sempre lo esegue rapidamente.
Mantieni la panoramica gratuita sul server
Inserisci la chiamata economica e gratuita overview in un Server Component e lascia che il visitatore attivi le chiamate a pagamento più approfondite tramite un pulsante. Questa suddivisione mantiene anche prevedibile la spesa dei crediti.
Le chiamate gratuite non costano nulla, quindi una panoramica renderizzata sul server non aggiunge spese di crediti. Solo le chiamate a pagamento richiedono un pulsante e una sessione.
Pensa prima allo stato vuoto. Un visitatore che atterra su una tabella vuota penserà che lo strumento sia rotto, e un secondo rendering veloce non cancella quella prima impressione.
Scegli le azioni gratuite per la prima schermata e quelle a pagamento per i drill-down. Un visitatore che apre una pagina e vede un riepilogo gratuito non ha motivo di premere accidentalmente un pulsante a pagamento.
Collega la spiegazione, non solo il numero
Collegare i numeri è la parte facile. Spiegarli è più difficile, quindi collega la formula DR dal tooltip e menziona che l'indice si aggiorna mensilmente.
Un visitatore che vede un punteggio senza la sua formula indovinerà, quindi il link deve trovarsi accanto al numero sullo schermo.

Parti server e client di un unico cruscotto
La maggior parte dei cruscotti richiede entrambi i tipi di codice. La proprietà decide la suddivisione. La chiave, i dati e le regole di accesso hanno ciascuno una sede unica. Una pagina web di verifica dei backlink pubblica richiede la stessa suddivisione di una dietro un login.
Fetch sul server, ordinamento sul client
Una tabella di backlink è un componente client ragionevole quando le persone ordinano, filtrano e paginano. Le righe dovrebbero provenire da un componente server che ha già chiamato il tuo route, così il browser non detiene mai la chiave o l'URL upstream.
Ordinare dieci righe è istantaneo sul client. Ordinare cinque mila righe in una nuova richiesta ogni volta è lento, quindi mantieni l'insieme completo nel componente che hai già caricato.
Mantienilo semplice. Una tabella che si ordina istantaneamente batte una che rifà il fetch ad ogni click.
Mantieni il confine client basso
Spingi il confine il più in basso possibile nell'albero. Un widget importato dinamicamente, client-only, viene renderizzato nel browser, mentre il componente che ha fetchato le righe rimane sul server. Passa le righe come props e mantieni il fetch sopra il confine.
Se sposti il fetch sotto il confine, il browser effettua la chiamata direttamente. Questo è l'errore di cui avverte la sezione sulla perdita della chiave, quindi controlla la scheda Network dopo ogni modifica a questo confine.
Dove cade esattamente la linea? Disegnala prima su carta, con una casella per il fetch e una per ogni controllo interattivo, e conta le frecce che la attraversano.
Guscio e controlli di sessione restano sul server
Layout e controlli di sessione decidono cosa può vedere un visitatore in assoluto, quindi appartengono al server. Tabelle interattive, moduli e grafici possono essere idratati successivamente senza cambiare quella decisione.
Un guardrail client-side può nascondere un pulsante, ma non può fermare una richiesta. Il route handler e il layout server sono dove l'accesso viene effettivamente applicato.
Poi invia la stessa richiesta senza il cookie e conferma che il server la rifiuta, non solo la pagina.
La stessa regola copre il proxy stesso. Un controllo di sessione che avviene solo nel browser non può proteggere il route, quindi il route controlla la sessione anche prima di spendere un credito.
Cache per dominio, non per visitatore
Il route costruisce il suo URL upstream dal dominio, quindi la chiave di cache segue il dominio. Un visitatore che cerca un sito riscalda la pagina per il visitatore successivo che chiede dello stesso sito.
Questo è il punto della revalidate giornaliera nell'handler. Due visitatori sullo stesso dominio costano una chiamata upstream, e mille visitatori su mille domini costano mille chiamate, quindi la cache è il budget.
Attenzione anche al caso opposto. Una pagina che varia la sua query string con un timestamp invalida completamente la cache, e il conto lo mostra prima dei log.
Revalida su un programma che corrisponde all'indice. Una finestra giornaliera si adatta a dati mensili, e una più breve aggiunge solo chiamate senza aggiungere nuovi fatti.
Rafforzamento, nell'ordine in cui lo faremmo
L'handler è la parte facile. Il rafforzamento è dove una chiave fuoriesce o un bilancio si svuota, quindi lavora attraverso questi punti in ordine.
Scansiona la tua scheda Network
Apri Chrome DevTools sul tuo cruscotto e leggi la scheda Network. Guarda attentamente. Se una richiesta lì mostra sk_live_ da qualche parte, ruota la chiave oggi. Quel controllo richiede un minuto e trova la perdita più comune.
Cerca anche nei corpi delle risposte oltre alle intestazioni delle richieste. Una chiave riflessa in un messaggio di errore è comunque una perdita.
Limita le chiamate per utente
Successivamente, aggiungi un limite per utente. Mettilo davanti al route. L'handler sopra spende il tuo credito per chiunque possa raggiungerlo, quindi collegalo a una sessione e limita le chiamate all'ora. Scegli il limite dalla matematica del budget nella pagina crediti.
Inizia generoso e stringi dopo. Un limite che blocca gli utenti reali dal primo giorno li insegna a trovare un altro strumento.
Chi nomina chi per il ricarico
Vale anche la pena una riga nel runbook: chi viene chiamato quando il saldo scende sotto un livello impostato, e chi lo ricarica. Nomina la persona.
Scrivi il passaggio di ricarica come una checklist con il link alla pagina dell'account e il tempo di risposta previsto. Un runbook che esiste solo nella testa di una persona fallisce la prima volta che quella persona è in ferie. Il passaggio stesso richiede minuti, quindi la scrittura è il lavoro più grande.
Registra il saldo prima che si esaurisca
Poi registra _credits da ogni risposta upstream nelle tue metriche. Un grafico del saldo rimanente, campionato giornalmente, ti avvisa una settimana prima che arrivi l'HTTP 402.
Scegli il livello di alert dal tuo utilizzo settimanale. Un numero scelto da un modello raramente corrisponde alle tue chiamate reali.
Mantieni l'ultimo valore visto in memoria oltre che nel log, così il route può rifiutare le chiamate a pagamento una volta che il numero scende sotto il livello di alert. Quel controllo è una riga, e ferma un picco di traffico dallo spendere il resto.
Mantieni l'allow-list in un unico punto
Un'altra abitudine paga. Tieni l'allow-list in una costante condivisa, importata dall'handler e dai test, così una nuova azione non può raggiungere la produzione senza che qualcuno modifichi una lista visibile e un revisore se ne accorga.
Un file, un import. Nessuno dovrebbe dover cercare la lista in tre componenti per sapere cosa accetterà il proxy.
Testa il runtime a cui deployi
Infine, esegui l'handler sotto Node 20 o il runtime edge della tua scelta e testa entrambi. Lo streaming del corpo upstream, come fa il codice, funziona in entrambi, ma la gestione delle intestazioni differisce ed è degna di un test.
Aggiungi un test che chiama il route con un'azione non valida e uno con un'azione valida, e verifica i codici di stato. Questi due test catturano la maggior parte delle regressioni nell'allow-list e nella gestione delle intestazioni.
Collegare il proxy in ordine
- 1
Aggiungi la chiave sul server
Memorizza la chiave nel tuo ambiente server con un nome senza prefisso pubblico, poi riavvia il server dev così che venga caricato.
- 2
Scrivi prima l'allow-list
Elenca le azioni che le tue schermate chiamano davvero. Qualcos'altro riceverà un 400 dal tuo route, prima di raggiungere l'API upstream.
- 3
Fai il fetch del tuo route da un componente server
Renderizza le righe dove arrivano i dati. Il browser chiama solo il tuo dominio, quindi l'indirizzo upstream rimane fuori dalla pagina.
- 4
Tieni d'occhio il saldo
Registra
_creditssu ogni risposta upstream e imposta un livello di alert su cui puoi agire, prima che un 402 raggiunga un visitatore come un 503.