본문으로 이동

JSON 형식의 백링크 데이터: $100 무료 크레딧, 카드 불필요, Google 로그인.

SEOMasters.dev
SEOMasters.dev회원가입

Next.js 웹 백링크 체커, 키는 서버에 유지

CORS가 열려 있으므로 브라우저가 API에 직접 호출할 수 있습니다. 유료 키로 이를 수행하는 것이 실수입니다. 이 페이지는 이를 수정하는 얇은 서버 레이어를 보여줍니다.

방어된 케이블은 무거운 잠금으로 보호된 지하 서버실로 연결된 유리 집과 연결됩니다

오해 versus 실제 상황

오해현실
"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" } });
}

핸들러가 이렇게 보이는 이유

액션 허용 목록

UI의 버그로 인해 실수로 3크레딧 search 호출이 트리거되지 않도록 해야 합니다.

q에 대한 정규식

순수 호스트명만 통과합니다. 스키마, 경로, 쿼리 문자열이 상류로 유입되지 않습니다.

revalidate: 86400

도메인당 하루 상류 호출 1회. 월별 데이터라면 더 길어도 괜찮습니다.

402를 503으로 매핑

방문자에게 잔액이 비어 있음을 알릴 필요가 없습니다. 하지만 로그해야 합니다.

드론이 안개 낀 계곡 위에서 호버하는 동안 하이커가 돌다리를 건너고 있습니다

렌더링: 크롤러와 고객이 모두 보는 것

이 대시보드가 로그인 뒤에 있다면 서버 또는 클라이언트 렌더링은 UX 선택입니다. 인덱싱하려는 무료 조회 페이지와 같이 공개된 부분이 있다면 첫 번째 테이블을 서버에서 렌더링하세요.

하이드레이션 후 채워지는 빈 껍질로 도착하는 페이지는 인덱싱과 느린 폰트 모두에 위험합니다. Google은 JavaScript를 실행할 수 있지만 해당 단계를 큐에 넣고 항상 빠르게 처리하지는 않습니다.

무료 개요를 서버에 유지

저렴하고 무료인 overview 호출을 서버 컴포넌트에 넣고 방문자가 버튼을 통해 유료이고 더 깊은 조회를 트리거하도록 하세요. 이 분할은 크레딧 지출을 예측 가능하게 유지합니다.

무료 호출은 비용이 들지 않으므로 서버 렌더링된 개요는 크레딧 지출을 추가하지 않습니다. 유료 조회만 버튼과 세션이 필요합니다.

빈 상태를 먼저 고려하세요. 빈 테이블에 도착한 방문자는 도구가 고장 났다고 생각할 것이며, 빠른 두 번째 페인트는 그런 첫인상을 지우지 못합니다.

첫 화면에는 무료 액션을 선택하고 심층 조회에는 유료 액션을 선택하세요. 페이지를 열고 무료 요약을 본 방문자는 실수로 유료 버튼을 누를 이유가 없습니다.

숫자뿐만 아니라 설명도 링크하세요

숫자를 연결하는 것은 쉬운 부분입니다. 설명하는 것이 더 어렵므로 툴팁에서 DR 공식을 링크하고 색인이 월간으로 새로고침됨을 언급하세요.

공식 없이 점수를 본 방문자는 이를 추측할 것이므로 링크는 화면의 숫자 옆에 있어야 합니다.

공예가가 나무 파티션 뒤의 잠긴 서랍 근처에서 황동 열쇠를 들고 있습니다

하나의 대시보드의 서버 및 클라이언트 부분

대부분의 대시보드에는 두 가지 유형의 코드가 모두 필요합니다. 소유권이 분할을 결정합니다. 키, 데이터 및 접근 규칙은 각각 하나의 홈을 가져야 합니다. 공개된 웹 백링크 체크 페이지든 로그인 뒤에 있는 페이지든 동일한 분할이 필요합니다.

서버에서 가져오고 클라이언트에서 정렬

백링크 테이블은 사람들이 정렬, 필터링 및 페이지네이션을 수행할 때 적절한 클라이언트 컴포넌트입니다. 행은 이미 라우트를 호출한 서버 컴포넌트에서 제공되어야 하므로 브라우저는 키나 상류 URL을 보유하지 않습니다.

10개의 행 정렬은 클라이언트에서 즉시 완료됩니다. 매번 새로 요청하여 5000개의 행을 정렬하면 느리므로 이미 로드된 컴포넌트에 전체 세트를 유지하세요.

단순하게 유지하세요. 즉시 정렬되는 테이블이 매 클릭마다 다시 가져오는 테이블보다 낫습니다.

클라이언트 경계를 낮게 유지

트리에서 가능한 한 아래로 경계를 밀어붙이세요. 동적으로 가져온 클라이언트 전용 위젯은 브라우저에서 렌더링되고, 행을 가져온 컴포넌트는 서버에 남아 있습니다. 행을 props로 전달하고 가져오기를 경계 위에 유지하세요.

가져오기를 경계 아래로 이동하면 브라우저가 자체적으로 호출을 수행합니다. 이는 키 유출 섹션에서 경고하는 실수이므로 이 경계에 대한 변경 후 네트워크 탭을 확인하세요.

선이 정확히 어디에 떨어지는가? 먼저 종이 위에 그리고, 가져오기용 상자 하나와 모든 상호작용 컨트롤용 상자를 그리고, 그 경계를 가로지르는 화살표를 세어보세요.

셸 및 세션 확인은 서버에 유지

레이아웃 및 세션 확인은 방문자가 볼 수 있는 것을 결정하므로 서버에 있어야 합니다. 상호작용 테이블, 양식 및 차트는 해당 결정을 변경하지 않고 하이드레이션할 수 있습니다.

클라이언측 가드는 버튼을 숨길 수 있지만 요청을 차단할 수는 없습니다. 접근은 실제로 라우트 핸들러와 서버 레이아웃에서 강제됩니다.

그런 다음 쿠키 없이 동일한 요청을 보내고 서버가 페이지가 아닌 요청을 거부하는지 확인하세요.

동일한 규칙이 프록시 자체에도 적용됩니다. 브라우저에서만 발생하는 세션 확인은 라우트를 보호할 수 없으므로 라우트는 크레딧을 쓰기 전에 세션도 확인합니다.

방문자별이 아닌 도메인별 캐싱

라우트는 도메인에서 상류 URL을 구성하므로 캐시 키도 도메인을 따릅니다. 한 사이트의 조회를 수행한 방문자가 다음 방문자를 위해 페이지를 따뜻하게 합니다.

핸들러의 일일 재검토가 바로 그 목적입니다. 동일한 도메인의 두 방문자는 하나의 상류 호출에 비용이 들며, 천 개의 도메인의 천 명의 방문자는 천 개의 호출에 비용이 들므로 캐시가 예산입니다.

반대 경우도 주의하세요. 타임스탬프로 쿼리 문자열을 변경하는 페이지는 캐시를 완전히 무효화하며, 청구서는 로그보다 먼저 이를 보여줍니다.

색인과 일치하는 일정에 따라 재검토하세요. 일일 창은 월별 데이터에 적합하며, 더 짧은 창은 새로운 사실 없이 호출만 추가합니다.

강화, 우리가 수행할 순서대로

핸들러는 쉬운 부분입니다. 키가 유출되거나 잔액이 고갈되는 곳은 강화이므로 순서대로 작업하세요.

자신의 네트워크 탭을 스캔

자신의 대시보드에서 Chrome DevTools를 열고 네트워크 탭을 읽으세요. 주의 깊게 보세요. 만약 거기에 sk_live_가 표시되는 요청이 있다면 오늘 키를 회전하세요. 이 확인은 1분이 걸리며 가장 흔한 누출을 찾습니다.

요청 헤더뿐만 아니라 응답 본문도 검색하세요. 오류 메시지에 키가 다시 표시되면 여전히 누출입니다.

사용자별 호출 제한

다음으로 사용자별 제한을 추가하세요. 라우트 앞에 두세요. 위의 핸들러는 도달할 수 있는 모든 사람을 위해 크레딧을 사용하므로 세션에 묶고 시간당 호출을 제한하세요. 크레딧 페이지의 예산 계산에서 제한을 선택하세요.

관대하게 시작하고 나중에 조이세요. 첫날에 실제 사용자를 차단하는 제한은 그들이 다른 도구를 찾게 만듭니다.

누가 충전하는지 명시

런북에 한 줄 더 쓸 가치가 있습니다. 잔액이 설정된 수준 아래로 떨어질 때 누가 페이지를 받고 누가 충전하는지. 사람을 명시하세요.

계정 페이지 링크와 예상 처리 시간을 포함하는 체크리스트로 충전 단계를 기록하세요. 한 사람의 머릿속에만 있는 런북은 그 사람이 휴가 첫날에 실패합니다. 단계 자체는 몇 분이지만 작성하는 것이 더 큰 작업입니다.

잔액이 떨어지기 전에 로그

그런 다음 각 상류 응답에서 _credits를 자체 메트릭에 로그하세요. 잔액의 일일 샘플링 그래프는 HTTP 402가 경고하기 1주 전에 경고합니다.

경보 수준을 자신의 주간 사용량에서 선택하세요. 템플릿에서 선택한 숫자는 실제 호출과 일치하지 않는 경우가 많습니다.

마지막으로 본 값을 로그뿐만 아니라 메모리에도 유지하여 숫자가 경보 수준 아래로 떨어지면 라우트가 유료 조회를 거부할 수 있습니다. 이 확인은 한 줄이며, 트래픽 폭주가 나머지를 소모하는 것을 방지합니다.

허용 목록을 한 곳에 유지

하나의 습관이 더 큰 보상을 줍니다. 허용 목록을 공유 상수에 유지하여 핸들러와 테스트 모두에서 가져오게 하세요. 그러면 새로운 액션이 누군가가 가시적인 목록을 편집하고 검토자가 이를 알아차리지 않는 한 프로덕션에 도달할 수 없습니다.

하나의 파일, 하나의 가져오기. 프록시가 무엇을 허용하는지 알기 위해 세 가지 구성 요소에서 목록을 찾아야 하는 사람은 없어야 합니다.

배포할 런타임을 테스트

마지막으로 Node 20 또는 선택한 에지 런타임에서 핸들러를 실행하고 둘 다 테스트하세요. 코드가 수행하는 대로 상류 본문을 스트리밍하면 각기 작동하지만 헤더 처리는 다르며 테스트할 가치가 있습니다.

잘못된 액션으로 라우트를 호출하는 테스트와 유효한 액션으로 호출하는 테스트를 추가하고 상태 코드를 확인하세요. 이러한 두 테스트는 허용 목록 및 헤더 처리에서 대부분의 회귀를 잡습니다.

순서대로 프록시 연결

  1. 1

    서버에 키 추가

    공개 접두사가 없는 이름으로 서버 환경에 키를 저장한 후 로드되도록 개발 서버를 다시 시작하세요.

  2. 2

    먼저 허용 목록 작성

    화면이 실제로 호출하는 액션을 나열하세요. 나머지는 상류 API에 도달하기 전에 자체 라우트에서 400을 받습니다.

  3. 3

    서버 컴포넌트에서 라우트 가져오기

    데이터가 도착하는 곳에서 행을 렌더링하세요. 브라우저는 자체 도메인만 호출하므로 상류 주소는 페이지에 표시되지 않습니다.

  4. 4

    잔액 모니터링

    각 상류 응답에서 _credits를 로그하고 방문자에게 503으로 도달하기 전에 402 전에 조치할 수 있는 경보 수준을 설정하세요.