How to Handle Rate Limiting in FckSignups: Cloudflare Workers and Exponential Backoff Explained

FckSignups handles rate limiting through a two-tier strategy: Cloudflare's native rate-limiting service automatically throttles abusive incoming traffic, while custom exponential-backoff retry logic manages rate-limited responses from external APIs like GitHub.

FckSignups (archived as NoSignups) is a privacy-focused tool directory that relies on a Cloudflare Worker as its API gateway. Understanding how this open-source project handles rate limiting is essential for anyone building serverless architectures that interact with third-party services. This article examines the complete rate-limiting implementation across the Cloudflare Worker layer, external API integrations, and React frontend.

Cloudflare Worker: Native Rate Limiting for Incoming Requests

The first line of defense operates at the edge. In [cloudflare-worker/worker.ts](https://github.com/BraveOPotato/FckSignups/blob/main/cloudflare-worker/worker.ts), the entry point simply routes requests to handlers without implementing custom throttling logic.

Cloudflare's Rate Limiting service handles protection automatically when configured in the dashboard:

  • Rule example: 10 requests per 10 seconds per IP address
  • Testing mode: Enable "Simulate" to preview blocked requests without enforcement
  • Response: HTTP 429 with Retry-After header when triggered

The worker never sees excessive requests—Cloudflare drops or delays them at the network edge. This zero-code approach keeps the worker lightweight while providing robust DDoS protection.

External API Rate Limiting: GitHub API Integration

The worker implements custom retry logic for outbound calls to rate-limited services. In [cloudflare-worker/urlHandlers/handleSubmitTool.ts](https://github.com/BraveOPotato/FckSignups/blob/main/cloudflare-worker/urlHandlers/handleSubmitTool.ts), submitting a new tool creates a GitHub issue via the REST API—a service with strict rate limits (5000 requests/hour for authenticated users).

Detecting Rate-Limit Responses

The handler checks for two status codes indicating throttling:

const res = await fetch(githubApiUrl, { … });
if (res.status === 429 || res.status === 403) {
  const retryAfter = Number(res.headers.get('Retry-After')) || 0;
  // Proceed to exponential backoff
}

GitHub returns 403 Forbidden with rate-limit headers when the limit is exceeded, while 429 Too Many Requests is the standard HTTP status. Both are handled identically.

Exponential Backoff Implementation

The retry helper lives in [cloudflare-worker/utils.ts](https://github.com/BraveOPotato/FckSignups/blob/main/cloudflare-worker/utils.ts):

export async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 3): Promise<T> {
  let attempt = 0;
  while (true) {
    try {
      return await fn();
    } catch (err: any) {
      if (attempt++ >= maxAttempts) throw err;
      
      const delay = (err.headers?.get('Retry-After')
        ? Number(err.headers.get('Retry-After')) * 1000
        : Math.pow(2, attempt) * 1000);
      
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

Algorithm behavior:

  1. First retry: 2 seconds (2¹ × 1000ms)
  2. Second retry: 4 seconds (2² × 1000ms)
  3. Third retry: 8 seconds (2³ × 1000ms)
  4. Respect Retry-After: If the API provides this header, use its value instead of calculated backoff

Applied in Handler Code

Here's how handleSubmitTool.ts uses the wrapper:

import { withRetry } from '../utils';

export async function handleSubmitTool(request: Request, env: Env, origin: string) {
  // Validation and payload construction omitted
  
  const res = await withRetry(() =>
    fetch(`https://api.github.com/repos/${env.GITHUB_REPO_OWNER}/${env.GITHUB_REPO_NAME}/issues`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${env.GITHUB_TOKEN}`,
        'Accept': 'application/vnd.github+json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(issueBody),
    })
  );
  
  return new Response(JSON.stringify({ success: true, issueUrl: res.url }), {
    headers: { 'Content-Type': 'application/json' },
  });
}

The same pattern appears in:

Frontend: Propagating Rate-Limit Errors

The React frontend in [src/hooks/useTools.ts](https://github.com/BraveOPotato/FckSignups/blob/main/src/hooks/useTools.ts) doesn't implement client-side retry logic. Instead, it surfaces 429 responses from the worker directly to the UI:

import { useQuery } from '@tanstack/react-query';

export default function useTools() {
  return useQuery({
    queryKey: ['tools'],
    queryFn: async () => {
      const res = await fetch('/api/tools');
      if (!res.ok) {
        const error = new Error(res.statusText);
        (error as any).status = res.status;
        throw error;
      }
      return res.json();
    },
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
}

Component-level handling:

import useTools from '@/hooks/useTools';

function ToolsList() {
  const { data, error, isLoading } = useTools();

  if (error?.status === 429) {
    return <p>Rate limit reached – please wait a moment and try again.</p>;
  }
  
  if (isLoading) return <Spinner />;
  
  return (
    <div>
      {data.map(tool => <ToolCard key={tool.id} {...tool} />)}
    </div>
  );
}

This architecture keeps the frontend simple and stateless. All retry complexity lives in the worker, where it can be centrally maintained and audited.

Configuration and Deployment

Setting up Cloudflare's native rate limiting:

  1. Navigate to Security > WAF > Rate limiting rules in the Cloudflare dashboard
  2. Create a rule matching your worker's route pattern
  3. Set thresholds based on expected legitimate traffic
  4. Enable logging to analyze patterns before enforcement

Environment variables required for the worker:

// wrangler.toml or Cloudflare dashboard
[env.production.vars]
GITHUB_REPO_OWNER = "your-username"
GITHUB_REPO_NAME = "your-repo"
GITHUB_TOKEN = "ghp_xxxx"  // Fine-grained personal access token

Summary

  • Edge protection: Cloudflare's native rate limiting handles incoming request throttling without code changes in worker.ts
  • API resilience: The withRetry helper in utils.ts implements exponential backoff with Retry-After header support for GitHub API calls
  • Handler coverage: Submit, report, and suggest tool handlers all use consistent retry patterns
  • Frontend simplicity: React hooks propagate 429 errors to the UI without client-side retry complexity
  • Deploy-time configuration: Rate limits are tuned via Cloudflare dashboard, not hardcoded

Frequently Asked Questions

How does FckSignups handle GitHub API rate limits specifically?

The worker detects 403 and 429 status codes from GitHub API responses, extracts the Retry-After header if present, and retries the request after that delay. If no header is provided, it falls back to exponential backoff with base-2 progression (2s, 4s, 8s). After 3 failed attempts, the error propagates to the client.

Why doesn't the frontend implement its own retry logic?

All retry logic is centralized in the Cloudflare Worker to avoid thundering herd problems and ensure consistent behavior. The frontend simply displays rate-limit errors to users. This separation of concerns keeps the React bundle smaller and prevents conflicting retry strategies between client and server.

Can I adjust the rate limits without changing code?

Yes. Cloudflare Rate Limiting rules are configured entirely through the dashboard. You can modify thresholds, time windows, and enforcement actions without redeploying the worker. The worker's retry logic for external APIs uses environment variables for configuration, so you can adjust maxAttempts by modifying the withRetry call parameters in each handler.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →