Guides

Performance Optimization

Reduce bundle size, lazy load, and optimize SDK initialization

Performance Optimization

This guide covers strategies for minimizing the performance impact of BotSigged on your application, including lazy loading, deferred initialization, and bundle optimization.

Bundle Size Overview

The SDK is designed with performance in mind:

ESM Build (npm package)

File Size Gzipped When Loaded
index.js 85 KB 18 KB Always (initial)
challenge-*.js 10 KB 2.8 KB Only if action: 'challenge'
hash-*.js 14 KB 5.1 KB Only if hashVerification.enabled

For most integrations, the initial footprint is only 18 KB gzipped.

IIFE Build (script tag)

File Size Gzipped
botsigged.js 470 KB 116 KB

The IIFE build includes all features in a single file for simplicity. Consider the ESM build if bundle size is critical.

Lazy Loading

Dynamic Import

Load the SDK only when needed:

// Load SDK on first user interaction
let botSigged = null;

document.addEventListener('mousemove', async () => {
  if (botSigged) return;

  const { BotSigged } = await import('@botsigged/sdk');
  botSigged = BotSigged.init({
    apiKey: 'your-api-key',
  });
}, { once: true });

React Lazy Loading

import { lazy, Suspense, useEffect, useState } from 'react';

// Lazy load the provider
const BotSiggedProvider = lazy(() =>
  import('@/lib/botsigged').then((mod) => ({ default: mod.BotSiggedProvider }))
);

export function App({ children }) {
  const [shouldLoad, setShouldLoad] = useState(false);

  useEffect(() => {
    // Load after initial render
    const timer = setTimeout(() => setShouldLoad(true), 100);
    return () => clearTimeout(timer);
  }, []);

  if (!shouldLoad) {
    return <>{children}</>;
  }

  return (
    <Suspense fallback={children}>
      <BotSiggedProvider apiKey={process.env.NEXT_PUBLIC_BOTSIGGED_KEY!}>
        {children}
      </BotSiggedProvider>
    </Suspense>
  );
}

Next.js Dynamic Import

import dynamic from 'next/dynamic';

const BotSiggedProvider = dynamic(
  () => import('@/lib/botsigged').then((mod) => mod.BotSiggedProvider),
  {
    ssr: false,
    loading: () => null,
  }
);

export default function Layout({ children }) {
  return <BotSiggedProvider>{children}</BotSiggedProvider>;
}

Vue Async Component

import { defineAsyncComponent } from 'vue';

const BotSiggedProvider = defineAsyncComponent(() =>
  import('@/components/BotSiggedProvider.vue')
);

Deferred Initialization

Load After Critical Content

// Wait for page to be interactive
if (document.readyState === 'complete') {
  initBotSigged();
} else {
  window.addEventListener('load', initBotSigged);
}

function initBotSigged() {
  // Defer to next idle period
  if ('requestIdleCallback' in window) {
    requestIdleCallback(() => {
      BotSigged.init({ apiKey: 'your-api-key' });
    });
  } else {
    setTimeout(() => {
      BotSigged.init({ apiKey: 'your-api-key' });
    }, 200);
  }
}

Initialize on Route

Only load on pages that need protection:

// React Router example
import { useLocation } from 'react-router-dom';

const PROTECTED_ROUTES = ['/checkout', '/login', '/signup'];

export function ConditionalBotSigged({ children }) {
  const location = useLocation();
  const [sdk, setSdk] = useState(null);

  useEffect(() => {
    const needsProtection = PROTECTED_ROUTES.some((route) =>
      location.pathname.startsWith(route)
    );

    if (needsProtection && !sdk) {
      import('@botsigged/sdk').then(({ BotSigged }) => {
        setSdk(BotSigged.init({ apiKey: 'your-api-key' }));
      });
    }
  }, [location.pathname]);

  return children;
}

Manual Start

Control when tracking begins:

const botSigged = new BotSigged({
  apiKey: 'your-api-key',
  autoStart: false, // Don't start automatically
});

// Start when ready
document.getElementById('checkout-form')?.addEventListener('focus', () => {
  botSigged.start();
}, { once: true });

Reducing Signal Collection

Disable Unused Trackers

If you only need specific signals:

BotSigged.init({
  apiKey: 'your-api-key',
  trackers: {
    mouse: true,
    scroll: true,
    form: true,
    browser: true,
  },
});

Increase Send Interval

Reduce network requests:

BotSigged.init({
  apiKey: 'your-api-key',
  sendInterval: 5000, // Send every 5 seconds instead of 2
});

Limit Buffer Size

Cap memory usage for long sessions:

BotSigged.init({
  apiKey: 'your-api-key',
  maxBufferSize: 500, // Limit events per buffer
});

Script Loading Strategies

Async Loading (Recommended)

<script async src="https://unpkg.com/@botsigged/sdk/dist/botsigged.js"></script>
<script>
  window.BotSiggedConfig = {
    apiKey: 'your-api-key',
    // SDK will auto-initialize when loaded
  };
</script>

Defer Loading

Load after HTML parsing:

<script defer src="https://unpkg.com/@botsigged/sdk/dist/botsigged.js"></script>

Preconnect

Hint browser to establish connection early:

<head>
  <link rel="preconnect" href="https://your-botsigged-instance.com" />
  <link rel="dns-prefetch" href="https://your-botsigged-instance.com" />
</head>

Module Preload

For ESM builds:

<link rel="modulepreload" href="/js/botsigged.esm.js" />

Code Splitting

Split by Feature

Only load challenge UI when needed:

BotSigged.init({
  apiKey: 'your-api-key',
  action: 'challenge',
  // Challenge module loaded only when triggered
});

Split by Route

// routes/checkout.js
export async function loader() {
  // Preload SDK for checkout
  await import('@botsigged/sdk');
  return null;
}

Service Worker Caching

Cache the SDK for faster subsequent loads:

// sw.js
const CACHE_NAME = 'botsigged-v1';
const SDK_URL = '/js/botsigged.js';

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.add(SDK_URL))
  );
});

self.addEventListener('fetch', (event) => {
  if (event.request.url.endsWith('/js/botsigged.js')) {
    event.respondWith(
      caches.match(event.request).then((response) => {
        return response || fetch(event.request);
      })
    );
  }
});

Measuring Performance

SDK Performance Metrics

const botSigged = BotSigged.init({
  apiKey: 'your-api-key',
});

// After initialization
const perf = botSigged.getPerformanceSummary();
console.log({
  initTime: perf.initTimeMs,
  connectionTime: perf.connectionTimeMs,
  timeToFirstScore: perf.timeToFirstScoreMs,
});

// Detailed breakdown
botSigged.logPerformance();

Web Vitals Impact

Measure impact on Core Web Vitals:

import { onLCP, onFID, onCLS } from 'web-vitals';

// Measure before SDK
const beforeMetrics = {};

onLCP((metric) => (beforeMetrics.lcp = metric.value));
onFID((metric) => (beforeMetrics.fid = metric.value));
onCLS((metric) => (beforeMetrics.cls = metric.value));

// Initialize SDK
BotSigged.init({ apiKey: 'your-api-key' });

// Compare after
setTimeout(() => {
  const perf = botSigged.getPerformanceSummary();
  analytics.track('botsigged_performance', {
    ...perf,
    ...beforeMetrics,
  });
}, 5000);

Bundle Analyzer

Verify SDK size in your bundle:

# webpack
npx webpack-bundle-analyzer stats.json

# vite
npx vite-bundle-analyzer

# next.js
ANALYZE=true npm run build

Optimization Checklist

Initial Load

  • [ ] Use ESM build instead of IIFE when possible
  • [ ] Lazy load SDK on user interaction or protected routes
  • [ ] Add preconnect hints for WebSocket endpoint
  • [ ] Use async/defer for script loading

Runtime

  • [ ] Increase sendInterval if real-time scoring isn’t critical
  • [ ] Disable unused trackers
  • [ ] Set appropriate buffer limits for long sessions

Build

  • [ ] Verify SDK is code-split correctly
  • [ ] Check bundle size with analyzer
  • [ ] Enable tree-shaking in bundler

Caching

  • [ ] Configure CDN caching for SDK files
  • [ ] Consider service worker caching
  • [ ] Use versioned URLs for cache busting

Performance Budgets

Recommended targets:

Metric Target With BotSigged
Initial JS < 200 KB + 18 KB gzipped
Time to Interactive < 3.8s + ~50ms
First Input Delay < 100ms Negligible impact
WebSocket connection - ~100-200ms
Time to first score - ~500-1000ms

Troubleshooting

High Memory Usage

Check buffer sizes:

const buffers = botSigged.getBufferSizes();
console.log('Mouse events:', buffers.mouse);
console.log('Scroll events:', buffers.scroll);

// If too high, signals are being sent too slowly
// or sendInterval is too long

Slow Initialization

Check connection time:

const perf = botSigged.getPerformanceSummary();
if (perf.connectionTimeMs > 1000) {
  // WebSocket connection is slow
  // Check network, consider preconnect
}

Bundle Too Large

Verify tree-shaking is working:

// Only import what you need
import { BotSigged } from '@botsigged/sdk';
// NOT: import * as BotSigged from '@botsigged/sdk';

Check bundler is configured for ESM:

// vite.config.js
export default {
  build: {
    target: 'esnext',
    modulePreload: true,
  },
};