Перейти до змісту

Дані про посилання у форматі JSON: $100 безкоштовного кредиту, без картки, вхід через Google.

SEOMasters.dev
SEOMasters.devЗареєструватися

Перевірник беклінків для веб-сайтів на Next.js із збереженням ключа на сервері

CORS відкритий, тому браузер може звертатися до API напряму. Використання платного ключа в клієнтській частині — це помилка. Ця сторінка показує тонкий шар сервера, який виправляє ситуацію.

Броньовані кабелі з'єднують скляний будинок із підземним серверним приміщенням, захищеним міцними замками

Міф проти реальності

МіфРеальність
"Відкритий CORS означає, що я можу додати ключ у свій React-бандл."Відкритий CORS означає, що запит працює. Будь-хто все ще може прочитати ключ у вкладці мережі та витратити ваш баланс.
"Таблицю посилань потрібно рендерити на клієнті, оскільки це панель керування."Отримуйте дані на сервері та надсилайте HTML. Користувач бачить рядки при першому малюванні, а ваш ключ ніколи не залишає сервер.
"Навіть безкоштовні дії потребують захисту."domain, overview, stats та suggest не потребують ключа, тому браузер може звертатися до них напряму.
"Мені слід оновлювати дані при кожному перегляді сторінки."Індекс оновлюється приблизно раз на місяць. 24-годинне кешування нічого не втрачає.
"Проксі повинен перенаправляти все, що надсилає клієнт."Дозвольте лише ті дії, які ви використовуєте. Перенаправлений action=search коштує 3 кредити.

Обробник маршруту

App Router, файл app/api/links/route.ts. Змінна середовища ніколи не потрапляє в клієнтський бандл, оскільки не має префікса NEXT_PUBLIC_.

ts
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" } });
}

Чому обробник виглядає саме так

Список дозволених дій

Помилка у вашому інтерфейсі не повинна випадково запускати запит search, що коштує 3 кредити.

Регулярний вираз для q

Проходить лише чистий хост. Без схеми, шляху чи рядка запиту, які можуть просочитися вгору.

revalidate: 86400

Один запит до сервера на домен на день. З місячними даними можна використовувати і довший час.

402 перетворено на 503

Відвідувачам не потрібно знати, що ваш баланс порожній. Вам — потрібно, тому логуйте це.

Турист йде кам'яним мостом, поки дрон зависає над туманною долиною

Рендеринг: те, що бачить і пошуковий робот, і відвідувач

Якщо ця панель керування захищена паролем, вибір між серверним і клієнтським рендерингом — це питання UX. Якщо ж вона публічна, наприклад, безкоштовна сторінка пошуку, яку потрібно проіндексувати, рендерте першу таблицю на сервері.

Сторінка, яка спочатку з'являється як порожня оболонка, а заповнюється після гідратації, є ризиком як для індексації, так і для повільних телефонів. Google може виконувати JavaScript, але ставить цей крок у чергу і не завжди виконує його швидко.

Залишайте безкоштовний огляд на сервері

Розмістіть дешевий або безкоштовний запит overview у Server Component і дозвольте відвідувачу запускати платні, глибші запити за допомогою кнопки. Такий поділ також робить витрати кредитів передбачуваними.

Безкоштовні запити нічого не коштують, тому серверний рендеринг огляду не збільшує витрати кредитів. Лише платні запити потребують кнопки та сесії.

Подумайте про порожній стан заздалегідь. Відвідувач, який бачить порожню таблицю, вирішить, що інструмент зламаний, і швидке друге малювання не скасує це перше враження.

Обирайте безкоштовні дії для першого екрану, а платні — для поглибленого аналізу. Відвідувач, який відкриває сторінку і бачить безкоштовний звіт, не має причини випадково натискати платну кнопку.

Посилайтеся на пояснення, а не лише на цифру

Підключення цифр — це легка частина. Пояснити їх складніше, тому додайте посилання на формулу DR у спливаючій підказці та згадайте, що індекс оновлюється щомісяця.

Відвідувач, який бачить рейтинг без формули, буде його вгадувати, тому посилання має бути поруч із цифрою на екрані.

Майстер тримає латунний ключ біля зачиненої шухляди за дерев'яною перегородкою

Серверна та клієнтська частини однієї панелі керування

Більшості панелей керування потрібні обидва типи коду. Поділ визначається володінням. Ключ, дані та правила доступу мають бути в одному місці. Сторінка перевірника беклінків, яка є публічною, потребує такого ж поділу, як і та, що захищена паролем.

Отримуйте дані на сервері, сортируйте на клієнті

Таблиця беклінків — це розумний клієнтський компонент, якщо користувачі сортують, фільтрують і пагінують її. Рядки мають надходити від Server Component, який вже виконав запит до вашого маршруту, щоб браузер ніколи не містив ключ або URL зовнішнього API.

Сортування десяти рядків відбувається миттєво на клієнті. Сортування п'яти тисяч рядків у новому запиті кожного разу — повільно, тому зберігайте повний набір у компоненті, який ви вже завантажили.

Тримайте все просто. Таблиця, яка сортується миттєво, краща за ту, що оновлює дані при кожному натисканні.

Тримайте клієнтську межу низькою

Перенесіть межу якомога глибше в дерево компонентів. Динамічно імпортований клієнтський віджет рендериться в браузері, тоді як компонент, який отримав рядки, залишається на сервері. Передавайте рядки як пропси і тримайте запит вище за межу.

Якщо ви перенесете запит нижче за межу, браузер виконуватиме запит сам. Це та помилка, про яку йдеться в розділі про витік ключа, тому перевіряйте вкладку Network після кожної зміни цієї межі.

Де саме проходить лінія? Спочатку намалюйте її на папері: одна коробка для запиту та по одній для кожного інтерактивного елемента, і порахуйте стрілки, що перетинають межу.

Оболонка та перевірка сесії залишаються на сервері

Макет та перевірка сесії вирішують, що відвідувач взагалі може бачити, тому вони мають бути на сервері. Інтерактивні таблиці, форми та графіки можуть гідратуватися пізніше, не змінюючи цього рішення.

Клієнтський захист може сховати кнопку, але не може зупинити запит. Маршрутний обробник та серверний макет — це місця, де доступ фактично контролюється.

Потім надішліть той самий запит без cookie та переконайтеся, що сервер відмовляє в доступі, а не лише сторінка.

Те саме правило стосується самого проксі. Перевірка сесії, яка відбувається лише в браузері, не захищає маршрут, тому маршрут також перевіряє сесію перед витрачанням кредиту.

Кешування на домен, а не на відвідувача

Обробник маршруту будує URL зовнішнього API на основі домену, тому ключ кешування також базується на домені. Один відвідувач, який перевіряє сайт, готує сторінку для наступного відвідувача, який запитує той самий сайт.

В цьому і є сенс щоденного оновлення в обробнику. Два відвідувачі на одному домені коштують один запит до сервера, а тисяча відвідувачів на тисячі доменів коштують тисячу запитів, тому кеш — це ваш бюджет.

Спостерігайте й за протилежною ситуацією. Сторінка, яка змінює рядок запиту за допомогою мітки часу, повністю знищує кеш, і рахунок покаже це раніше за логи.

Оновлюйте кеш за розкладом, що відповідає індексу. Щоденне вікно підходить для місячних даних, а коротший період лише додає запити без додавання нових фактів.

Посилення захисту в порядку, у якому ми б це робили

Обробник маршруту — це легка частина. Посилення захисту — це те місце, де ключ може витікти або баланс вичерпатися, тому працюйте через ці кроки послідовно.

Перевірте вкладку Network у своєму браузері

Відкрийте Chrome DevTools на своїй панелі керування та уважно прочитайте вкладку Network. Якщо там є запит із sk_live_, змініть ключ сьогодні. Ця перевірка займає хвилину і знаходить найпоширеніший витік.

Шукайте ключі не лише в заголовках запитів, а й у тілах відповідей. Ключ, який повернувся в повідомленні про помилку, — це також витік.

Обмежте кількість запитів на користувача

Далі додайте ліміт на користувача. Розмістіть його перед маршрутом. Обробник вище витрачає ваш кредит для будь-кого, хто може до нього дістатися, тому прив'яжіть його до сесії та обмежте кількість запитів на годину. Оберіть ліміт з розрахунку бюджету на сторінці кредитів.

Почніть з помірних обмежень і потім звужуйте їх. Ліміт, який блокує реальних користувачів з першого дня, змусить їх шукати інший інструмент.

Вкажіть, хто поповнює баланс

Також варто додати рядок у інструкцію: кого повідомляти, коли баланс падає нижче певного рівня, і хто його поповнює. Вкажіть конкретну особу.

Запишіть крок поповнення як чек-лист із посиланням на сторінку облікового запису та очікуваним часом виконання. Інструкція, яка існує лише в голові однієї людини, не спрацює, коли ця людина буде у відпустці. Сам крок займає кілька хвилин, тому написання інструкції — це більша робота.

Логуйте баланс до його вичерпання

Потім логуйте _credits з кожної відповіді зовнішнього API у свої метрики. Графік залишкового балансу, що оновлюється щодня, попередить вас за тиждень до того, як з'явиться HTTP 402.

Оберіть рівень сповіщення на основі вашого щотижневого використання. Число, обране з шаблону, рідко відповідає вашим реальним запитам.

Зберігайте останнє бачене значення як в пам'яті, так і в логах, щоб маршрут міг відмовляти в платних запитах, коли число падає нижче рівня сповіщення. Ця перевірка займає один рядок коду і зупиняє сплеск трафіку, який може витратити решту коштів.

Тримайте список дозволених дій в одному місці

Ще одна звичка, яка окупається. Тримайте список дозволених дій у спільній константі, яку імпортує обробник та тести, щоб новий дії не потрапила в продакшн, хтось не редагував видимий список і рецензент не помітив цього.

Один файл, один імпорт. Ніхто не повинен шукати список в трьох компонентах, щоб знати, що прийме проксі.

Тестуйте середовище, до якого ви деплоїте

Нарешті, запустіть обробник під Node 20 або edge runtime за вашим вибором і протестуйте обидва. Потік тіла зовнішнього API, як це робить код, працює в обох, але обробка заголовків відрізняється, тому це варто протестувати.

Додайте один тест, який викликає маршрут з недійсною дією, і один з дійсною дією, і перевірте коди статусу. Ці два тести ловлять більшість регресій у списку дозволених дій та обробці заголовків.

Підключення проксі в правильному порядку

  1. 1

    Додайте ключ на сервері

    Збережіть ключ у змінних середовища сервера під іменем без публічного префікса, потім перезапустіть dev-сервер, щоб він завантажився.

  2. 2

    Спочатку напишіть список дозволених дій

    Перелічіть дії, які ваші екрани дійсно викликають. Все інше отримає 400 від вашого маршруту, перш ніж потрапить до зовнішнього API.

  3. 3

    Отримуйте дані з маршруту в Server Component

    Рендерьте рядки там, де з'являються дані. Браузер завжди звертається лише до вашого домену, тому адреса зовнішнього API залишається поза межами сторінки.

  4. 4

    Слідкуйте за балансом

    Логуйте _credits у кожній відповіді зовнішнього API та встановіть рівень сповіщення, на який можна реагувати, перш ніж 402 дійде до відвідувача як 503.