apps/docs/content/troubleshooting/realtime-heartbeat-messages.mdx
Heartbeat messages are periodic signals sent between the Realtime client and server to verify that the WebSocket connection is alive and functioning properly. The client sends a heartbeat message on the phoenix topic with the event type heartbeat at regular intervals (default: 25 seconds), and the server responds with a phx_reply message.
These messages serve as a keep-alive mechanism to detect connection issues that might not be immediately apparent, such as silent network failures or intermediary proxy timeouts.
Heartbeat messages are critical for maintaining reliable real-time connections:
Without heartbeats, your application might appear connected while being unable to send or receive messages.
You can monitor heartbeat lifecycle events using the onHeartbeat method:
import { useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'
function MyComponent() {
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
useEffect(() => {
supabase.realtime.onHeartbeat((status) => {
console.log('Heartbeat status:', status)
// status can be: 'sent', 'ok', 'error', 'timeout', or 'disconnected'
})
}, [supabase])
return <div>Your app content</div>
}
Or configure the callback during client initialization:
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {
realtime: {
heartbeatCallback: (status) => {
console.log('Heartbeat status:', status)
},
},
})
If you're seeing frequent timeout status in your heartbeat callback:
Connection not reconnecting after timeout
The client automatically attempts reconnection with exponential backoff (1s, 2s, 5s, 10s). If you need to manually reconnect:
import { useState, useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'
function ConnectionStatus() {
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const [isConnected, setIsConnected] = useState(true)
useEffect(() => {
supabase.realtime.onHeartbeat((status) => {
if (status === 'ok') {
setIsConnected(true)
} else if (status === 'timeout' || status === 'disconnected') {
setIsConnected(false)
}
})
}, [supabase])
const handleReconnect = () => {
supabase.realtime.connect()
}
return (
<div>
<p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
{!isConnected && <button onClick={handleReconnect}>Reconnect</button>}
</div>
)
}
You can adjust the heartbeat frequency when creating your Supabase client in React:
import { createClient } from '@supabase/supabase-js'
import { useMemo } from 'react'
function App() {
const supabase = useMemo(
() =>
createClient(SUPABASE_URL, SUPABASE_KEY, {
realtime: {
heartbeatIntervalMs: 15000, // Send heartbeat every 15 seconds (default: 25000)
},
}),
[]
)
return <YourApp supabase={supabase} />
}
Note: Increasing the interval reduces network traffic but delays detection of connection failures. Decreasing it improves detection speed but increases overhead.
For React applications with long-running connections, use Web Workers to prevent browser tab throttling:
import { createClient } from '@supabase/supabase-js'
import { useMemo } from 'react'
function App() {
const supabase = useMemo(
() =>
createClient(SUPABASE_URL, SUPABASE_KEY, {
realtime: {
worker: true,
workerUrl: '/worker.js', // Optional: Place in public folder
},
}),
[]
)
return <YourApp supabase={supabase} />
}
If the worker fails to start, check:
Display connection status with a simple indicator:
import { useState, useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'
function ConnectionIndicator() {
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const [status, setStatus] = useState('healthy')
useEffect(() => {
supabase.realtime.onHeartbeat((heartbeatStatus) => {
if (heartbeatStatus === 'ok') {
setStatus('healthy')
} else if (heartbeatStatus === 'timeout') {
setStatus('poor')
} else if (heartbeatStatus === 'disconnected') {
setStatus('disconnected')
}
})
}, [supabase])
return (
<div>
<span>Connection: {status}</span>
</div>
)
}
Ensure connection is active when user opens your app:
import { useEffect } from 'react'
import { AppState } from 'react-native'
import { createClient } from '@supabase/supabase-js'
function App() {
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
useEffect(() => {
const subscription = AppState.addEventListener('change', (nextAppState) => {
if (nextAppState === 'active') {
if (!supabase.realtime.isConnected()) {
supabase.realtime.connect()
}
}
})
return () => {
subscription.remove()
}
}, [supabase])
return <YourApp />
}
Set up monitoring when you create your Supabase client:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {
realtime: {
heartbeatCallback: (status) => {
if (status === 'ok') {
console.log('Connection healthy')
} else if (status === 'timeout') {
console.warn('Connection slow')
} else if (status === 'disconnected') {
console.error('Disconnected')
}
},
},
})
export default supabase