Skip to content
Frontend Network Resilience Patterns logo

Frontend Network Resilience Patterns

Network resilience patterns for frontend - retry logic, exponential backoff, error handling, offline detection

SKILL.md

Full skill instructions

Frontend Network Resilience Patterns

Comprehensive patterns for building robust network operations in frontend applications.


Core Principles

  1. Never trust the network - Always plan for failure
  2. Fail gracefully - Degrade functionality, don't break completely
  3. Provide feedback - User should know what's happening
  4. Auto-recover - Retry transient failures automatically
  5. Differentiate errors - Different errors need different messages

Pattern 1: Exponential Backoff Retry

When to Use:

  • Loading external SDKs (Clerk, Stripe, Google Maps)
  • API calls to backend services
  • Third-party API integrations
  • CDN resource loading

When NOT to Use:

  • User-initiated actions (use immediate retry with button instead)
  • Real-time operations (WebSocket - use reconnection logic instead)
  • Operations that must succeed exactly once (payments - use idempotency)

Implementation Pattern:

/​**
 * Retry an operation with exponential backoff
 *
 * @param operation - Async function to retry
 * @param maxAttempts - Maximum number of attempts (default: 4)
 * @param initialDelay - Initial delay in ms (default: 2000)
 * @param maxDelay - Maximum delay in ms (default: 30000)
 * @returns Result of operation or throws after all attempts exhausted
 */
async function retryWithBackoff<T>(
  operation: () => Promise<T>,
  maxAttempts = 4,
  initialDelay = 2000,
  maxDelay = 30000
): Promise<T> {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await operation();
    } catch (error) {
      // Last attempt - rethrow error
      if (attempt === maxAttempts) {
        throw error;
      }

      // Calculate delay with exponential backoff
      const delay = Math.min(
        initialDelay * Math.pow(2, attempt - 1),
        maxDelay
      );

      console.log(`Attempt ${attempt}/​${maxAttempts} failed, retrying in ${delay}ms...`);

      // Wait before next attempt
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw new Error('All retry attempts exhausted');
}

Usage Example:

// Loading external SDK with retry
const sdkLoaded = await retryWithBackoff(
  async () => {
    if (!sdk.loaded) {
      throw new Error('SDK not ready');
    }
    return true;
  },
  4,      // 4 attempts
  2000,   // Start with 2s delay
  30000   // Max 30s delay
);

Backoff Schedule Examples:

4 attempts, 2s initial:
  Attempt 1 → wait 2s → Attempt 2 → wait 4s → Attempt 3 → wait 8s → Attempt 4
  Total: ~14s

4 attempts, 3s initial:
  Attempt 1 → wait 3s → Attempt 2 → wait 6s → Attempt 3 → wait 12s → Attempt 4
  Total: ~21s

5 attempts, 2s initial:
  Attempt 1 → wait 2s → Attempt 2 → wait 4s → Attempt 3 → wait 8s →
  Attempt 4 → wait 16s → Attempt 5
  Total: ~30s

Pattern 2: Error Type Differentiation

Error Taxonomy:

type NetworkErrorType =
  | 'offline'           // No internet connection
  | 'timeout'           // Request/​load took too long
  | 'blocked'           // Blocked by browser/​extension/​firewall
  | 'not_found'         // Resource doesn't exist (404)
  | 'server_error'      // Server returned 5xx
  | 'unauthorized'      // Auth failed (401/​403)
  | 'rate_limited'      // Too many requests (429)
  | 'network_error'     // Generic network failure
  | 'unknown';          // Unclassified error

interface NetworkError {
  type: NetworkErrorType;
  message: string;
  userMessage: string;  // User-friendly message
  canRetry: boolean;    // Should we offer retry?
  autoRetry: boolean;   // Should we auto-retry?
}

Error Detection:

function classifyNetworkError(error: any): NetworkError {
  // Offline detection
  if (!navigator.onLine) {
    return {
      type: 'offline',
      message: 'Network offline',
      userMessage: 'No internet connection. Please check your network.',
      canRetry: true,
      autoRetry: false  // Don't auto-retry if offline
    };
  }

  // Timeout
  if (error.name === 'TimeoutError' || error.message?.includes('timeout')) {
    return {
      type: 'timeout',
      message: error.message,
      userMessage: 'Connection is very slow. Please check your internet.',
      canRetry: true,
      autoRetry: true  // Auto-retry timeouts
    };
  }

  // Blocked (CSP, ad blocker, CORS)
  if (error.message?.includes('blocked') ||
      error.message?.includes('CSP') ||
      error.name === 'SecurityError') {
    return {
      type: 'blocked',
      message: error.message,
      userMessage: 'Resource blocked. Please disable ad blockers.',
      canRetry: true,
      autoRetry: false  // User must fix blocker first
    };
  }

  // HTTP status codes
  if (error.status === 404) {
    return {
      type: 'not_found',
      message: error.message,
      userMessage: 'Resource not found.',
      canRetry: false,
      autoRetry: false
    };
  }

  if (error.status >= 500) {
    return {
      type: 'server_error',
      message: error.message,
      userMessage: 'Server error. Please try again later.',
      canRetry: true,
      autoRetry: true  // Server errors are often transient
    };
  }

  if (error.status === 401 || error.status === 403) {
    return {
      type: 'unauthorized',
      message: error.message,
      userMessage: 'Please sign in again.',
      canRetry: false,
      autoRetry: false
    };
  }

  if (error.status === 429) {
    return {
      type: 'rate_limited',
      message: error.message,
      userMessage: 'Too many requests. Please wait a moment.',
      canRetry: true,
      autoRetry: true  // With longer backoff
    };
  }

  // Generic network error
  return {
    type: 'network_error',
    message: error.message || 'Unknown error',
    userMessage: 'Network error. Please try again.',
    canRetry: true,
    autoRetry: true
  };
}

Pattern 3: Offline Detection

Implementation:

import { onMount } from 'svelte';

let isOnline = $state(true);

onMount(() => {
  // Initial check
  isOnline = navigator.onLine;

  // Listen for online/​offline events
  const handleOnline = () => { isOnline = true; };
  const handleOffline = () => { isOnline = false; };

  window.addEventListener('online', handleOnline);
  window.addEventListener('offline', handleOffline);

  return () => {
    window.removeEventListener('online', handleOnline);
    window.removeEventListener('offline', handleOffline);
  };
});

Usage in Components:

{#if !isOnline}
  <div class="offline-banner">
    ⚠️ No internet connection. Some features may not work.
  </​div>
{/​if}

<button
  onclick={handleAction}
  disabled={!isOnline}
>
  Submit
</​button>

Pattern 4: Progressive Loading with Feedback

For External SDK Loading:

<script lang="ts">
  let loadingState = $state<'idle' | 'loading' | 'ready' | 'error'>('idle');
  let loadingTime = $state(0);
  let attemptNumber = $state(0);
  let error = $state<NetworkError | null>(null);

  // Show progress message after 3s
  let showProgress = $derived(loadingTime > 3);

  async function loadSDK() {
    loadingState = 'loading';
    loadingTime = 0;

    // Update loading time every second
    const interval = setInterval(() => {
      loadingTime++;
    }, 1000);

    try {
      await retryWithBackoff(
        async () => {
          attemptNumber++;
          // SDK load logic
          if (!sdk.loaded) throw new Error('Not ready');
          return true;
        },
        4,
        2000
      );

      clearInterval(interval);
      loadingState = 'ready';
    } catch (err) {
      clearInterval(interval);
      loadingState = 'error';
      error = classifyNetworkError(err);
    }
  }
</​script>

{#if loadingState === 'loading'}
  <div class="loading-container">
    <Spinner />

    {#if attemptNumber > 1}
      <p>Connecting... (attempt {attemptNumber}/​4)</​p>
    {:else if showProgress}
      <p>This is taking longer than expected ({loadingTime}s)...</​p>
    {:else}
      <p>Loading...</​p>
    {/​if}
  </​div>
{:else if loadingState === 'error'}
  <ErrorMessage
    error={error}
    onRetry={error.canRetry ? loadSDK : undefined}
  />
{/​if}

Pattern 5: SSR-Friendly Network Operations

Guidelines:

  1. Never call network operations during SSR

    // ❌ BAD - Will fail during SSR
    const data = await fetch('/​api/​data');
    
    // ✅ GOOD - Only runs client-side
    onMount(async () => {
      const data = await fetch('/​api/​data');
    });
    
  2. Provide initial loading state in SSR HTML

    <!-- This renders during SSR -->
    <div class="content">
      {#if data}
        <DataDisplay {data} />
      {:else}
        <!-- This spinner is in SSR HTML -->
        <Spinner />
        <p>Loading content...</​p>
      {/​if}
    </​div>
    
  3. Handle hydration gracefully

    let data = $state(null);
    let isLoading = $state(true); // true during SSR and hydration
    
    onMount(async () => {
      // This runs after hydration
      data = await loadData();
      isLoading = false;
    });
    

Planning Checklist

When planning any feature with network operations, ensure:

Network Operation Requirements

  • Retry logic with exponential backoff specified
  • Number of attempts and timing justified (e.g., 4 attempts, 2s initial)
  • Error types are differentiated with appropriate messages
  • Offline state is detected and handled
  • Timeout values are specified and justified
  • Manual retry option provided (button)
  • Loading states defined for all durations (0-3s, 3-10s, 10s+)

SSR/​Hydration Requirements

  • Network operations only in onMount or client-side code
  • Initial loading state present in SSR HTML
  • No flash of incorrect content (FOUC) during hydration
  • Progressive enhancement considered

UX Requirements

  • User sees feedback within 1s (spinner, progress indicator)
  • Progress message shown if loading > 3s
  • Error messages are user-friendly and actionable
  • User can retry without full page refresh
  • Graceful degradation if JS disabled (where possible)

Testing Requirements

All network operations must include tests for:

  1. Success scenario - Normal network conditions
  2. Slow network - Simulated 3G (3-5s response time)
  3. Very slow network - Extreme latency (8-10s)
  4. Offline - No internet connection
  5. Timeout - No response within timeout period
  6. Intermittent failure - Fails first 2 attempts, succeeds on 3rd
  7. Persistent failure - Fails all retry attempts
  8. Error types - 401, 403, 404, 429, 500, 503

Playwright Example:

test('SDK loads with retry on slow network', async ({ page, context }) => {
  // Simulate slow 3G
  await context.route('**/​clerk.sdk.js', async (route) => {
    await new Promise(resolve => setTimeout(resolve, 4000));
    await route.continue();
  });

  await page.goto('/​login');

  // Should show loading state
  await expect(page.locator('.spinner')).toBeVisible();

  // Should show progress message after 3s
  await expect(page.locator('text=Taking longer')).toBeVisible({ timeout: 4000 });

  // Should eventually load
  await expect(page.locator('.clerk-sign-in')).toBeVisible({ timeout: 10000 });
});

Common Mistakes to Avoid

❌ Mistake 1: Single Timeout Without Retry

// BAD - Gives up after one timeout
const loaded = await waitForSDK(10000);
if (!loaded) {
  showError('Failed to load');
}
// GOOD - Retries with backoff
const loaded = await retryWithBackoff(
  () => waitForSDK(5000),
  4,
  2000
);

❌ Mistake 2: Generic Error Messages

// BAD - Same message for all errors
catch (error) {
  showError('Something went wrong. Please try again.');
}
// GOOD - Differentiated messages
catch (error) {
  const networkError = classifyNetworkError(error);
  showError(networkError.userMessage);

  if (networkError.canRetry) {
    showRetryButton();
  }
}

❌ Mistake 3: Ignoring Offline State

// BAD - Attempts network call while offline
async function loadData() {
  return await fetch('/​api/​data');
}
// GOOD - Checks offline state first
async function loadData() {
  if (!navigator.onLine) {
    throw new Error('offline');
  }
  return await fetch('/​api/​data');
}

❌ Mistake 4: Network Calls During SSR

// BAD - Will fail during SSR
const data = await fetch('/​api/​data');
// GOOD - Only client-side
let data = $state(null);

onMount(async () => {
  data = await fetch('/​api/​data');
});

References


Last Updated: 2025-11-06 Applies To: All frontend network operations Mandatory: YES - Plan agents must reference this during planning

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View