금요일 오후에 배포하고 있는데(알죠, 알죠), 모든 것이 잘 진행되다가 갑자기 모니터링이 크리스마스 트리처럼 반짝거립니다. 사용자들이 429 오류를 받고 있습니다. API가 요청을 거부하고 있습니다. 아니면 반대일 수도 있습니다 — 써드파티 API를 호출하는데 그쪽에서 당신을 거부하고 있습니다. 어느 쪽이든, HTTP 429 Too Many Requests 상태 코드가 당신의 하루에서 가장 중요한 것이 되었습니다.

나는 이 양쪽 입장을 모두 경험했습니다. 잘못된 빌드 프로세스 설정으로 우연히 CMS API에 DDoS를 하고 있던 개발자였던 적도 있고, 자신의 서버를 제어 불가능한 클라이언트로부터 보호하기 위해 rate limiting을 구현하는 사람이었던 적도 있습니다. 두 경험 모두 문서에서 다루지 않는 것들을 가르쳐 주었습니다. 이 모든 것을 함께 살펴보겠습니다.

목차

HTTP 429 Too Many Requests: Causes, Fixes, and Rate Limiting

HTTP 429는 실제로 무엇을 의미하나요?

HTTP 429는 2012년에 발표된 RFC 6585에서 정의됩니다. 스펙은 놀랍도록 짧습니다. 핵심은 다음과 같습니다: 사용자(또는 클라이언트)가 주어진 시간 내에 너무 많은 요청을 보냈습니다.

그게 전부입니다. 이것은 rate limiting 응답입니다. 서버가 말하는 것은, "당신의 요청을 이해했습니다, 아마 유효하겠지만, 속도를 늦춰야 합니다."입니다.

이것은 403 Forbidden(허용되지 않음) 또는 503 Service Unavailable(전체 서버가 어려움)과는 다릅니다. 429는 타겟팅됩니다. 구체적으로 당신의 요청 속도에 대한 것입니다.

응답에는 클라이언트에게 다시 시도하기 전에 얼마나 오래 기다려야 하는지 알려주는 Retry-After 헤더가 포함되어야 합니다. 많은 API가 귀찮아서 이것을 보내지 않기 때문에 나는 "should"라고 말했는데, 이것은 모두의 삶을 더 어렵게 만듭니다.

실제로 429를 볼 곳들

  • 써드파티 API: Stripe, OpenAI, GitHub, Contentful, Sanity — 모두 rate limits를 가지고 있습니다
  • CDN 및 호스팅 플랫폼: Vercel, Cloudflare, AWS는 엣지 rate limits를 초과하면 429를 반환합니다
  • 자신의 API: rate limiting을 구현했다면(그리고 해야 합니다)
  • 빌드 프로세스: 모든 페이지에 대해 CMS API를 치는 정적 사이트 생성은 쉽게 rate limits를 트리거할 수 있습니다
  • 웹 스크래핑: 외부 소스에서 공격적으로 데이터를 가져오는 경우

429 오류의 일반적인 원인

프로덕션에서 실제로 만난 시나리오를 분류해서 설명하겠습니다.

1. 정적 사이트 빌드가 Headless CMS에 과부하를 주는 경우

이것은 headless 아키텍처로 작업하는 팀에 가장 많이 영향을 미칩니다. 각각 CMS에서 데이터가 필요한 2,000개의 페이지가 있는 사이트가 있습니다. 빌드 프로세스가 병렬로 모든 요청을 발사하고, CMS가 대규모 스파이크를 보고, 429를 반환하기 시작합니다. 빌드가 실패합니다.

우리는 headless CMS 프로젝트에서 정기적으로 이것을 봅니다. 해결책은 요청 큐잉과 동시성 제한을 포함하며, 이것을 아래에서 다룰 것입니다.

2. 캐싱이 없거나 손상된 경우

캐싱 레이어가 작동하지 않아서 모든 페이지 로드가 새로운 API 호출을 트리거하면, 특히 트래픽 스파이크 시에 rate limits를 빠르게 칠 것입니다. 한번 revalidate가 실수로 0으로 설정된 Next.js 앱을 디버깅했는데, 이것은 ISR이 효과적으로 비활성화되었다는 의미였습니다. 모든 방문자가 Contentful에 새로운 API 호출을 트리거했습니다. 실제 트래픽에서 429를 받기 시작하기까지 약 45분이 걸렸습니다.

3. Backoff 없는 재시도 루프

당신의 코드가 오류를 받고, 즉시 재시도하고, 다른 오류를 받고, 즉시 재시도합니다... 축하합니다, 당신은 rate-limit-triggering 기계를 만들었습니다. 나는 webhook 핸들러, 백그라운드 작업, 심지어 클라이언트 측 fetch 호출에서 이 패턴을 봤습니다.

4. 여러 서비스가 API 키를 공유하는 경우

당신의 스테이징 환경, 프로덕션 환경, 로컬 dev 설정, CI/CD 파이프라인이 모두 같은 API 키를 사용하고 있습니다. 각각은 개별적으로 괜찮아 보이지만, 집합적으로 rate limit 예산을 빠르게 소진하고 있습니다.

5. Debouncing 없는 클라이언트 측 Fetch

모든 키 입력에서 API 호출을 발사하는 검색 기능입니다. 매 500ms마다 폴링하는 대시보드입니다. 사용자가 스크롤할 수 있는 것보다 더 빨리 가져오기를 트리거하는 무한 스크롤입니다. 이 패턴들은 특히 모든 사용자에게 곱해질 때 확실히 429를 트리거할 수 있습니다.

6. 실제 남용 또는 공격

때때로 429는 정확히 그 역할을 하고 있습니다 — 합리적이지 않은 많은 요청을 보내는 누군가로부터 서버를 보호합니다. 봇, 자격증 채우기, 스크래핑 — rate limiting은 당신의 첫 번째 방어선입니다.

Retry-After 헤더 설명

Retry-After 헤더는 서버가 정확히 언제 다시 시도하면 되는지를 알려주는 방법입니다. 두 가지 형식으로 올 수 있습니다:

기다릴 초 단위:

HTTP/1.1 429 Too Many Requests
Retry-After: 60

특정 날짜/시간:

HTTP/1.1 429 Too Many Requests
Retry-After: Thu, 01 Jan 2026 00:00:00 GMT

초 단위 형식이 훨씬 더 일반적입니다. 날짜 형식은 RFC 7231에서 정의한 HTTP-date를 사용합니다.

대부분의 튜토리얼이 말해주지 않을 것은: 많은 API가 Retry-After를 전혀 보내지 않거나, 또는 일관되지 않게 보냅니다. OpenAI의 API는 일반적으로 포함합니다. GitHub의 API는 X-RateLimit-Reset과 함께 포함합니다. 많은 작은 API는 그냥 naked 429를 보내고 추측하도록 남겨둡니다.

일부 API는 또한 추가 rate limit 헤더를 보냅니다:

| 헤더 | 목적 | 예시 | |--------|---------|--------|| | X-RateLimit-Limit | 윈도우당 허용되는 최대 요청 | 100 | | X-RateLimit-Remaining | 현재 윈도우에서 남은 요청 | 0 | | X-RateLimit-Reset | 윈도우가 리셋되는 Unix 타임스탬프 | 1735689600 | | Retry-After | 재시도 전에 기다릴 초 | 30 |

항상 이 헤더들을 확인하세요. 이들은 더 똑똑한 재시도 로직을 구현하고 limit을 치기 전에 사전에 속도를 늦출 수 있게 해줍니다.

HTTP 429 Too Many Requests: Causes, Fixes, and Rate Limiting - architecture

클라이언트로서 429 오류 처리하는 방법

429 오류를 받고 있을 때, 올바르게 처리하는 방법은 다음과 같습니다.

지수 Backoff와 Jitter

이것이 금표준입니다. 그냥 고정된 시간 동안 기다리지 말고 — 각 재시도마다 지수적으로 지연을 증가시키고, 일부 무작위성(jitter)을 더해서 thundering herd 문제를 방지하세요.

async function fetchWithRetry(
  url: string,
  options: RequestInit = {},
  maxRetries: number = 5
): Promise<Response> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status !== 429) {
      return response;
    }

    if (attempt === maxRetries) {
      throw new Error(`Still getting 429 after ${maxRetries} retries`);
    }

    // 먼저 Retry-After 헤더를 확인하세요
    const retryAfter = response.headers.get('Retry-After');
    let delay: number;

    if (retryAfter) {
      // 초 또는 날짜일 수 있습니다
      const parsed = parseInt(retryAfter, 10);
      if (!isNaN(parsed)) {
        delay = parsed * 1000;
      } else {
        delay = new Date(retryAfter).getTime() - Date.now();
      }
    } else {
      // Jitter를 가진 지수 backoff
      const baseDelay = Math.pow(2, attempt) * 1000;
      const jitter = Math.random() * 1000;
      delay = baseDelay + jitter;
    }

    console.log(`Rate limited. Waiting ${Math.round(delay / 1000)}s before retry ${attempt + 1}`);
    await new Promise(resolve => setTimeout(resolve, delay));
  }

  // TypeScript는 이것을 원하지만, 우리는 절대 도달하지 않을 것입니다
  throw new Error('Unexpected end of retry loop');
}

빌드 프로세스를 위한 요청 큐잉

수백 또는 수천 개의 API 호출을 해야 하는 정적 사이트 생성을 위해, 동시성 제어와 함께 큐를 사용하세요:

import pLimit from 'p-limit';

// 동시 요청을 5개로 제한하세요
const limit = pLimit(5);

const pages = await getAllPageSlugs(); // ['/', '/about', '/blog/post-1', ...]를 반환합니다

const results = await Promise.all(
  pages.map(slug =>
    limit(() => fetchWithRetry(`https://api.cms.com/pages/${slug}`))
  )
);

p-limit 라이브러리(2025년에 주당 250만+ npm 다운로드)는 이것을 위한 내 최애입니다. 또한 요청 간 지연을 추가할 수 있습니다:

const limit = pLimit(3);

const delay = (ms: number) => new Promise(r => setTimeout(r, ms));

const results = await Promise.all(
  pages.map((slug, i) =>
    limit(async () => {
      if (i > 0) await delay(200); // 요청 사이에 200ms
      return fetchWithRetry(`https://api.cms.com/pages/${slug}`);
    })
  )
);

Next.js API Routes에서 Rate Limiting 구현하기

이제 다른 쪽으로 넘어가겠습니다 — API를 빌드하고 있고 보호해야 합니다. Next.js로 빌드하고 있다면, API routes에 rate limiting을 추가하는 방법은 다음과 같습니다.

간단한 인메모리 Rate Limiter

단일 서버 배포 또는 개발 중에는 이것이 작동합니다:

// lib/rate-limit.ts
type RateLimitEntry = {
  count: number;
  resetTime: number;
};

const rateLimitMap = new Map<string, RateLimitEntry>();

export function rateLimit({
  windowMs = 60 * 1000,
  maxRequests = 100,
}: {
  windowMs?: number;
  maxRequests?: number;
} = {}) {
  return function check(identifier: string): {
    allowed: boolean;
    remaining: number;
    resetIn: number;
  } {
    const now = Date.now();
    const entry = rateLimitMap.get(identifier);

    if (!entry || now > entry.resetTime) {
      rateLimitMap.set(identifier, {
        count: 1,
        resetTime: now + windowMs,
      });
      return { allowed: true, remaining: maxRequests - 1, resetIn: windowMs };
    }

    if (entry.count >= maxRequests) {
      return {
        allowed: false,
        remaining: 0,
        resetIn: entry.resetTime - now,
      };
    }

    entry.count++;
    return {
      allowed: true,
      remaining: maxRequests - entry.count,
      resetIn: entry.resetTime - now,
    };
  };
}

Next.js App Router API 라우트에서 사용하기:

// app/api/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { rateLimit } from '@/lib/rate-limit';

const limiter = rateLimit({ windowMs: 60_000, maxRequests: 30 });

export async function GET(request: NextRequest) {
  const ip = request.headers.get('x-forwarded-for') ?? 'anonymous';
  const { allowed, remaining, resetIn } = limiter(ip);

  if (!allowed) {
    return NextResponse.json(
      { error: 'Too many requests. Please slow down.' },
      {
        status: 429,
        headers: {
          'Retry-After': String(Math.ceil(resetIn / 1000)),
          'X-RateLimit-Limit': '30',
          'X-RateLimit-Remaining': '0',
        },
      }
    );
  }

  // 여기에 실제 라우트 로직을 입력하세요
  return NextResponse.json(
    { data: 'Here you go' },
    {
      headers: {
        'X-RateLimit-Limit': '30',
        'X-RateLimit-Remaining': String(remaining),
      },
    }
  );
}

Upstash Redis를 사용한 프로덕션 Rate Limiting

인메모리 접근 방식은 Vercel과 같은 서버리스 플랫폼에서 실행할 때 깨집니다. 함수 호출마다 다른 인스턴스에 도달할 수 있기 때문입니다. 공유 저장소가 필요합니다. Upstash Redis는 2025년에 이것을 위한 가장 인기 있는 선택입니다.

npm install @upstash/ratelimit @upstash/redis
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

export const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(30, '60 s'),
  analytics: true,
  prefix: 'api-ratelimit',
});
// app/api/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';

export async function GET(request: NextRequest) {
  const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
  const { success, limit, remaining, reset } = await ratelimit.limit(ip);

  if (!success) {
    const retryAfter = Math.ceil((reset - Date.now()) / 1000);
    return NextResponse.json(
      { error: 'Rate limit exceeded' },
      {
        status: 429,
        headers: {
          'Retry-After': String(retryAfter),
          'X-RateLimit-Limit': String(limit),
          'X-RateLimit-Remaining': '0',
          'X-RateLimit-Reset': String(reset),
        },
      }
    );
  }

  return NextResponse.json({ data: 'Success' }, {
    headers: {
      'X-RateLimit-Limit': String(limit),
      'X-RateLimit-Remaining': String(remaining),
    },
  });
}

Upstash의 무료 티어는 매일 10,000개의 요청을 제공하는데, 이것은 작은 프로젝트에 충분합니다. Pro 계획은 2025년 초 기준으로 매일 500K 명령을 위해 $10/월부터 시작합니다.

미들웨어 레벨 Rate Limiting

모든 API 라우트에 rate limiting을 원하면, Next.js 미들웨어가 장소입니다:

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';

export async function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/api/')) {
    const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
    const { success, reset } = await ratelimit.limit(ip);

    if (!success) {
      return NextResponse.json(
        { error: 'Too many requests' },
        {
          status: 429,
          headers: {
            'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
          },
        }
      );
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: '/api/:path*',
};

Rate Limiting 전략 비교

모든 rate limiting 알고리즘이 같은 것은 아닙니다. 주요 알고리즘들이 어떻게 비교되는지는 다음과 같습니다:

알고리즘 작동 방식 장점 단점 최적용도
Fixed Window 고정 시간 윈도우에서 요청을 개수 세기(예: 분 단위) 구현이 간단합니다 윈도우 경계에서의 버스트가 limit의 2배를 허용할 수 있습니다 간단한 API, 내부 도구
Sliding Window 로링 시간 기간에 대한 요청 개수 세기 더 매끄러운 분포 약간 더 복잡, 더 많은 메모리 대부분의 프로덕션 API
Token Bucket 토큰이 정상 속도로 리필되고, 각 요청이 토큰을 소비합니다 제어된 버스트를 허용합니다 더 복잡한 상태 관리 버스트 허용이 필요한 API
Leaky Bucket 요청이 큐에 들어가고 고정 속도로 처리됩니다 매우 매끄러운 출력 속도 지연을 추가할 수 있고, 요청이 삭제될 수 있습니다 Webhook 배달, 작업 처리
Sliding Window Log 각 요청의 타임스탐프를 저장합니다 가장 정확합니다 대규모에서 높은 메모리 사용량 낮은 볼륨, 높은 정확도 필요

대부분의 웹 애플리케이션의 경우, sliding window가 좋은 절충점입니다. Upstash가 기본값으로 사용하는 것이며, 다른 특정 이유가 있지 않은 한 이것을 추천합니다.

Astro 및 다른 프레임워크에서 Rate Limiting

Astro로 빌드하고 있다면, Astro는 주로 정적 우선 프레임워크이기 때문에 rate limiting은 다르게 작동합니다. 하지만 Astro의 서버 엔드포인트(SSR 모드에서 사용 가능)를 사용하면, 개념은 같습니다:

// src/pages/api/data.ts
import type { APIRoute } from 'astro';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(30, '60 s'),
});

export const GET: APIRoute = async ({ request }) => {
  const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
  const { success, reset } = await ratelimit.limit(ip);

  if (!success) {
    return new Response(JSON.stringify({ error: 'Rate limit exceeded' }), {
      status: 429,
      headers: {
        'Content-Type': 'application/json',
        'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
      },
    });
  }

  return new Response(JSON.stringify({ data: 'Hello' }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
};

Cloudflare Workers에 엣지로 배포된 애플리케이션의 경우, Cloudflare의 내장 Rate Limiting 규칙도 고려해볼 수 있는데, 이것은 인프라 레벨에서 작동하고 애플리케이션 레벨 솔루션보다 훨씬 더 많은 트래픽을 처리할 수 있습니다. Advanced Rate Limiting은 Business 플랜에서 좋은 요청당 $0.05부터 시작합니다.

프로덕션에서 429 오류 모니터링 및 디버깅

보지 못하는 것은 고칠 수 없습니다. 429 오류를 프로덕션에서 처리할 때의 내 체크리스트입니다:

429를 받고 있을 때

  1. 어느 API가 429를 반환하는지 확인하세요 — 상태 코드만이 아니라 응답 URL을 보세요
  2. Retry-After 헤더를 기록하세요 — 일관되게 매우 길면, 더 높은 티어 플랜이 필요할 수 있습니다
  3. 요청 패턴을 감시하세요 — 중복 호출을 하고 있습니까? 요청을 배치할 수 있습니까?
  4. 캐싱을 구현하세요stale-while-revalidate, Redis 캐싱, 또는 Next.js ISR을 사용해서 API 호출을 줄이세요
  5. 여러 환경이 API 키를 공유하는지 확인하세요 — 이것이 가장 일반적인 "비밀" 429 원인입니다

429를 보내고 있을 때

  1. 대시보드를 설정하세요 — 시간에 따른 429 응답 속도를 추적하세요
  2. 상위 위반자를 식별하세요 — 어느 IP 주소 또는 API 키가 가장 자주 limits를 칠까요?
  3. limits를 검토하세요 — 너무 제한적입니까? 너무 느슨합니까? 서버 용량을 확인하고 조정하세요
  4. 항상 Retry-After를 보내세요 — 좋은 API 시민이 되세요
  5. 도움이 되는 오류 메시지를 포함하세요 — 클라이언트에게 어느 limit을 칠했는지, 언제 재시도할지 알려주세요

잘 만들어진 429 응답 본문은 이렇게 보입니다:

{
  "error": {
    "type": "rate_limit_exceeded",
    "message": "You've exceeded 30 requests per minute. Please wait before retrying.",
    "retryAfter": 42,
    "documentation": "https://docs.yourapi.com/rate-limits"
  }
}

이것은 그냥 { "error": "Too many requests" }보다 훨씬 더 도움이 됩니다.

headless 아키텍처에서 지속적인 rate limiting 문제를 다루고 있다면 — 빌드 중, 런타임 중, 또는 둘 다 — 당신의 아키텍처에 대해 논의하기 위해 연락을 주는 것이 가치가 있을 수 있습니다. 우리는 다양한 CMS 및 프레임워크 조합에서 많은 이 문제들을 보았고, 증상에만 밴드에이드를 붙이는 것이 아니라 일반적으로 패턴 레벨의 해결책이 있습니다.

자주 묻는 질문

HTTP 429 Too Many Requests는 무엇을 의미하나요? HTTP 429는 주어진 시간 기간 내에 서버에 너무 많은 요청을 보냈다는 의미의 상태 코드입니다. 서버가 rate limiting을 하고 있습니다 — 속도를 늦추라고 요청하고 있습니다. 인증 오류도 서버 오류도 아닙니다; 당신의 요청은 아마 유효하지만, 너무 많이 있습니다. 서버는 다시 시도하기 전에 얼마나 오래 기다려야 하는지 알려주는 Retry-After 헤더를 포함해야 합니다.

429 오류를 어떻게 고칩니까? API에서 429 오류를 받고 있다면, 재시도 로직에서 exponential backoff with jitter를 구현하고, 요청 빈도를 줄이고, 불필요한 호출을 피하기 위해 캐싱을 추가하고, Retry-After 헤더를 준수하세요. 빌드 중에 limit을 칠 경우, 동시성 제어와 함께 요청 큐잉을 사용하세요. 일관되게 발생하면, 더 관대한 rate limits를 가진 더 높은 API 플랜으로 업그레이드해야 할 수 있습니다.

Retry-After 헤더는 무엇입니까? Retry-After 헤더는 429(또는 503) 응답과 함께 전송되어 클라이언트에게 다른 요청을 하기 전에 얼마나 오래 기다려야 하는지 알려줍니다. 초 수로 지정할 수 있습니다(예: Retry-After: 60) 또는 HTTP 날짜로 지정할 수 있습니다(예: Retry-After: Thu, 01 Jan 2026 00:00:00 GMT). 모든 API가 이 헤더를 포함하지는 않지만, 잘 설계된 API는 포함합니다.

Next.js에서 rate limiting을 어떻게 구현합니까? 개발 또는 단일 서버 배포를 위해, IP 주소당 요청 개수를 추적하기 위해 인메모리 Map을 사용할 수 있습니다. Vercel과 같은 플랫폼에서의 프로덕션 서버리스 배포의 경우, @upstash/ratelimit 패키지와 함께 Upstash Redis를 사용하세요. 개별 라우트 레벨에서 또는 Next.js 미들웨어를 사용해서 모든 API 라우트에 rate limiting을 적용할 수 있습니다.

429와 503 오류의 차이는 무엇입니까? 429 Too Many Requests는 구체적으로 rate limiting에 대한 것입니다 — 당신의 클라이언트가 너무 많은 요청을 보내고 있습니다. 503 Service Unavailable은 서버가 과부하되거나 유지보수 중이고 누구 로부터도 요청을 처리할 수 없다는 뜻입니다. 둘 다 Retry-After 헤더를 포함할 수 있지만, 매우 다른 문제를 나타냅니다. 429는 당신을 타겟팅하고, 503은 모두에게 영향을 미칩니다.

Rate limiting이 DDoS 공격을 방지할 수 있습니까? Rate limiting은 DDoS 공격에 대한 방어의 한 계층이지만, 그 자체로는 충분하지 않습니다. 애플리케이션 레벨 rate limiting(Next.js에서 구현하는 것 같은)은 중간 규모의 남용을 처리할 수 있지만, 진정한 DDoS 공격은 인프라 레벨에서 완화되어야 합니다 — Cloudflare, AWS Shield, 또는 호스팅 제공자의 내장 보호와 같은 서비스를 사용합니다. 애플리케이션 레벨 rate limiting을 바운서라고 생각하고, 인프라 레벨 보호를 요새 벽이라고 생각하세요.

내 API에 대해 어느 rate limit을 설정해야 합니까? 이것은 완전히 당신의 사용 사례에 달려 있습니다. 공개 API의 일반적인 시작점은 분 단위로 IP당 60개 요청이거나, 시간당 API 키당 1,000개 요청입니다. 인증된 사용자의 경우, 더 많이 허용할 수 있습니다. 핵심은 실제 사용 패턴을 모니터링하고, 정당한 사용에 여유를 두고 limits를 설정하며, 실제 데이터를 기반으로 조정하는 것입니다. 더 제한적인 것부터 시작하고 느슨하게 하세요 — 사용자가 더 높은 속도에 의존한 후에 limits을 타이트하게 하는 것보다 더 쉽습니다.

정적 사이트 빌드 중에 429 오류가 발생하는 이유는 무엇입니까? Next.js와 Astro와 같은 정적 사이트 생성기는 빌드 시점에 모든 페이지에 대해 데이터를 가져옵니다. 수백 또는 수천 개의 페이지가 있으면, 그것은 수백 또는 수천 개의 빠른 연속 API 호출입니다. 대부분의 CMS API는 초당 5-20개 요청의 rate limits를 가지고 있습니다. p-limit 또는 유사한 라이브러리를 사용해서 동시성을 3-5개 동시 요청으로 캡핑하고, 배치 사이에 작은 지연을 추가하고, 한 번에 모든 것을 재빌드하는 것을 피하기 위해 증분 빌드(Next.js의 ISR, 또는 Astro의 증분 콘텐츠 컬렉션)를 사용하는 것을 고려하세요.