Package Exports
- @tabbridge/use-multi-tab-detection
- @tabbridge/use-multi-tab-detection/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@tabbridge/use-multi-tab-detection) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@tabbridge/use-multi-tab-detection
A lightweight React hook for detecting when your application is open in multiple browser tabs.
Features
- ✅ Native
BroadcastChannelAPI for efficient inter-tab communication - ✅ Fully type-safe with strict TypeScript
- ✅ Automatic cleanup of inactive tabs
- ✅ Configurable heartbeat intervals and thresholds
- ✅ Callback support for state changes
- ✅ Browser support detection
- ✅ Zero dependencies (except React peer dependency)
Installation
npm install @tabbridge/use-multi-tab-detectionQuick Start
import { useMultiTabDetection } from '@tabbridge/use-multi-tab-detection';
function App() {
const { isMultiTab, tabCount } = useMultiTabDetection({
channelName: 'my-app'
});
if (isMultiTab) {
return <div>⚠️ Warning: This app is open in {tabCount} tabs</div>;
}
return <div>Your app content</div>;
}Usage
Basic Implementation
Add the hook to your root layout or any component where you want to track multiple tabs:
import { useMultiTabDetection } from '@tabbridge/use-multi-tab-detection';
export default function Layout() {
const { isMultiTab, tabCount, tabId } = useMultiTabDetection({
channelName: 'my-app-channel',
debug: process.env.NODE_ENV === 'development'
});
return (
<div>
{isMultiTab && (
<div className='warning-banner'>
Multiple tabs detected ({tabCount} active)
</div>
)}
{/* Your app content */}
</div>
);
}With Callbacks
React to multi-tab state changes with the onMultiTabChange callback:
const { isMultiTab, tabCount, activeTabUrls } = useMultiTabDetection({
channelName: 'my-app',
onMultiTabChange: (isMultiTab, count, urls) => {
console.log(`Multi-tab: ${isMultiTab}, Count: ${count}`);
console.log('Active URLs:', Array.from(urls.values()));
// Send analytics event
if (isMultiTab) {
analytics.track('multi_tab_detected', { tabCount: count });
}
}
});API Reference
useMultiTabDetection(options?)
Options
| Option | Type | Default | Description |
|---|---|---|---|
channelName |
string |
Required | Unique identifier for the BroadcastChannel |
heartbeatInterval |
number |
10000 |
Interval (ms) between heartbeat messages |
inactivityThreshold |
number |
30000 |
Time (ms) before considering a tab inactive |
debug |
boolean |
false |
Enable console logging for debugging |
onMultiTabChange |
function |
undefined |
Callback when multi-tab state changes |
onMultiTabChange Callback
(isMultiTab: boolean, tabCount: number, activeTabUrls: Map<string, string>) => voidReturn Value
| Property | Type | Description |
|---|---|---|
isMultiTab |
boolean |
Whether multiple tabs are currently detected |
tabCount |
number |
Total count of active tabs |
tabId |
string |
Unique identifier for the current tab |
isSupported |
boolean |
Whether BroadcastChannel API is supported |
activeTabUrls |
Map<string, string> |
Map of tab IDs to their URLs |
How It Works
- Tab Registration: Each tab creates a unique ID and joins a
BroadcastChannel - Heartbeat System: Tabs send periodic heartbeat messages to announce they're active
- Tab Discovery: New tabs broadcast a discovery message, and existing tabs respond
- Inactive Cleanup: Tabs that haven't sent a heartbeat within the threshold are removed
- State Updates: The hook tracks active tabs and updates state accordingly
Tab 1 Tab 2
| |
|--[heartbeat]---------->|
|<-------[heartbeat]-----|
| |
|--[heartbeat]---------->|
| X (closed)
|
|--[cleanup after 30s]-->
| (detects Tab 2 inactive)Browser Support
Uses the BroadcastChannel API:
- ✅ Chrome 54+
- ✅ Firefox 38+
- ✅ Safari 15.4+
- ✅ Edge 79+
- ❌ Internet Explorer (not supported)
The hook gracefully handles unsupported browsers by setting isSupported: false.
Examples
Analytics Tracking
useMultiTabDetection({
channelName: 'reporting-demo',
onMultiTabChange: async (isMultiTab, count, urls) => {
if (isMultiTab) {
await fetch('/api/report-multi-tab', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tabCount: count,
urls: Array.from(urls.values())
})
});
}
}
});Custom Warning Banner
function WarningBanner() {
const { isMultiTab, tabCount } = useMultiTabDetection({
channelName: 'banner-demo'
});
if (!isMultiTab) return null;
return (
<div className='bg-yellow-100 border-l-4 border-yellow-500 p-4'>
<p className='font-bold'>⚠️ Multiple Tabs Detected</p>
<p>
You have {tabCount} tabs open. Please close extra tabs to avoid
synchronization issues.
</p>
</div>
);
}Testing
Manual Testing
- Open your app in one tab with
debug: trueenabled - Open the same page in a second tab
- Check the browser console for debug messages
- Verify
isMultiTabbecomestrueandtabCountshows2 - Close one tab and verify the count decreases after the inactivity threshold
Troubleshooting
Hook not detecting other tabs
- Ensure all tabs use the same
channelName - Verify
isSupportedistruein your browser - Enable
debug: trueto see message flow in console - Check browser console for errors
False positives (detecting closed tabs)
- Increase
inactivityThresholdif you have slow network conditions - Default 30s threshold should work for most cases
- Verify cleanup is running with
debug: true
Performance concerns
- Default settings are already optimized for most use cases
- Increase
heartbeatIntervalto reduce message frequency if needed - Avoid triggering expensive operations in
onMultiTabChangecallback - Consider debouncing state updates that cause re-renders
Performance Notes
⚠️ Important: While BroadcastChannel is lightweight, be mindful of:
- Triggering global context updates on every state change
- Re-rendering expensive components unnecessarily
- Triggering data refetches in the callback
Best practices:
- Use
onMultiTabChangefor side effects (analytics, logging) - Memoize expensive computations based on
isMultiTab - Debounce UI updates if needed
Security Considerations
- BroadcastChannel only communicates within the same origin (browser security)
- Messages cannot be accessed across different domains
- Tab IDs are randomly generated and not tied to user identity
- Avoid sending sensitive data through the channel
Why BroadcastChannel?
We chose BroadcastChannel over alternatives for several reasons:
| Approach | Pros | Cons |
|---|---|---|
| BroadcastChannel | ✅ Native API ✅ No polling ✅ Auto cleanup |
❌ Limited browser support |
| LocalStorage events | ✅ Wider support | ❌ Same-origin only ❌ No auto cleanup |
| SharedWorker | ✅ Powerful | ❌ Complex setup ❌ Limited support |
| Server polling | ✅ Works everywhere | ❌ Network overhead ❌ Requires backend |
TypeScript
This package is written in TypeScript and includes full type definitions:
interface UseMultiTabDetectionOptions {
channelName: string;
heartbeatInterval?: number;
inactivityThreshold?: number;
debug?: boolean;
onMultiTabChange?: (
isMultiTab: boolean,
tabCount: number,
activeTabUrls: Map<string, string>
) => void;
}
interface UseMultiTabDetectionResult {
isMultiTab: boolean;
tabCount: number;
tabId: string;
isSupported: boolean;
activeTabUrls: Map<string, string>;
}Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT © TabBridge https://github.com/tabbridge
Links
Made with ❤️ by the TabBridge team