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
sendIntervalif 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,
},
};