When designing this portfolio, I thought it would be fun to add a small feature: an online indicator to show visitors if I am currently active. But as I began working on it, I realized that defining "online" was more complicated than I initially expected. Here's how I tackled the challenge and implemented an efficient and accurate online status system.
What Does "Online" Mean?
Before writing any code, I asked myself: What does 'online' mean in this context? Could it mean:
- Being at my computer?
- Actively browsing my portfolio website? (No, because I wouldn’t be refreshing the site that often.)
- Using a messenger platform? (Not relevant for this use case.)
Ultimately, I decided that "online" would mean I was actively using my MacBook. This simplified things while still delivering the intended functionality.
Implementation
I chose a heartbeat approach for this feature, combining several components to keep the solution lightweight and reliable. Here’s the breakdown:
Cloudflare Workers
-
Worker #1: Heartbeat Tracker
- Purpose: Updates a "lastHeartbeat" key in a Cloudflare KV (Key-Value) Storage.
- How it works: This worker exposes a POST endpoint. When called, it updates the "lastHeartbeat" value with the current timestamp.
worker.jsexport default { async fetch(request, env, ctx) { try { const { method } = request; // Handle POST request to record the heartbeat if (method === 'POST') { const currentTime = Date.now(); await env.HEARTBEAT.put('lastHeartbeat', currentTime); // Store the heartbeat timestamp in KV return new Response(JSON.stringify({ status: 'OK' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } if (method === 'GET') { // Handle GET request to check if the system is online const lastHeartbeat = await env.HEARTBEAT.get('lastHeartbeat'); // Check if lastHeartbeat exists and if it was within the last 10 minutes const isOnline = lastHeartbeat && Date.now() - parseInt(lastHeartbeat) < 600000; return new Response( JSON.stringify({ isOnline }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } } catch (err) { // In a production application, you could instead choose to retry your KV // read or fall back to a default code path. console.error(`KV returned error: ${err}`); return new Response(err.toString(), { status: 500 }); } }, };
-
Worker #2: Online Status Checker
- Purpose: Provides a public API endpoint to determine whether I’m online.
- How it works: Compares the current time with the "lastHeartbeat" value from KV Storage. If the difference is within a specified threshold (e.g., 15 minutes), the endpoint returns
isOnline: true
; otherwise, it returnsfalse
.
worker.jsexport default { async fetch(request, env, ctx) { try { // Handle GET request to check if the system is online const lastHeartbeat = await env.HEARTBEAT.get('lastHeartbeat'); // Check if lastHeartbeat exists and if it was within the last 10 minutes const isOnline = lastHeartbeat && Date.now() - parseInt(lastHeartbeat) < 600000; return new Response( JSON.stringify({ isOnline: isOnline }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } catch (err) { // In a production application, you could instead choose to retry your KV // read or fall back to a default code path. console.error(`KV returned error: ${err}`); return new Response(err.toString(), { status: 500 }); } }, };
Cron Job
To send regular heartbeats, I used a cron job:
- Python Script: A small script sends a POST request to the Cloudflare Worker #1 endpoint every 10 minutes.
- Crontab: The job is scheduled with
crontab -e
on my MacBook to ensure it runs reliably.
Here’s the Python script:
heartbeat.pyimport requests # The URL of your Cloudflare Worker (replace with your worker's URL) HEARTBEAT_URL = 'https://heartbeat.yourname.workers.dev/' # Function to send the heartbeat signal def send_heartbeat(): try: response = requests.post(HEARTBEAT_URL) if response.status_code == 200: print('Heartbeat sent successfully') else: print(f'Failed to send heartbeat: {response.status_code}') except Exception as e: print(f'Error sending heartbeat: {e}') # Run the heartbeat once (no infinite loop) send_heartbeat()
With the crontab file containing the following line:
crontab*/10 * * * * /usr/bin/python3 /path/to/heartbeat.py
But I worked in a Python environment and also saved the output to a log file, so I used the following line:
crontab*/10 * * * * /User/Workspace/python/heartbeat/venv/bin/python /User/Workspace/python/heartbeat/heartbeat.py >> /User/Workspace/python/heartbeat/heartbeat.log 2>&1
Frontend Hook
To display the online status, I created a custom React hook. Here’s a simplified version:
use-online-status.tsximport { useState, useEffect } from 'react'; export function useOnlineStatus() { const [isOnline, setIsOnline] = useState(false); useEffect(() => { const fetchStatus = async () => { const response = await fetch('/api/is-online'); const data = await response.json(); setIsOnline(data.isOnline); }; fetchStatus(); }, []); return isOnline; }
Implementation using Jotai for state management
use-online-status.tsx'use client' import { useEffect } from 'react' import { useAtom } from 'jotai' import { onlineAtom } from '@/components/modules/heartbeat/store' const useOnlineStatus = () => { const [online, setOnline] = useAtom(onlineAtom) useEffect(() => { // If the status is already set in sessionStorage, do nothing if (online !== null) return const fetchOnlineStatus = async () => { try { const response = await fetch('https://heartbeat-public.yourname.workers.dev/') const data = await response.json() setOnline(data.isOnline) // Update the atom and sessionStorage } catch (error) { console.error('Failed to fetch online status', error) setOnline(false) // Default to offline if API fails } } fetchOnlineStatus() }, [online, setOnline]) return online } export default useOnlineStatus
store.tsimport { atomWithStorage, createJSONStorage } from 'jotai/utils' // Define a custom storage that uses sessionStorage const sessionStorageWithJSON = createJSONStorage(() => sessionStorage) // Create the atom using the custom storage export const onlineAtom = atomWithStorage( 'online-status', null, // Default value sessionStorageWithJSON )
Optimizations
Initially, my naïve solution involved polling the online status API at regular intervals using setInterval
. While functional, it introduced several problems:
-
Resource Intensity:
- If a visitor left the page open, it could result in hundreds or thousands of API requests per session, driving up cloud costs and straining performance.
-
Accuracy vs. Efficiency:
- Frequent polling improved accuracy but wasn’t sustainable.
Final Approach
I optimized the implementation to balance accuracy, performance, and cost:
- Single API Request Per Session:
- If the API initially returns
isOnline: true
, the status is cached for the remainder of the session. - If it returns
false
, the status is checked again on page reloads.
- If the API initially returns
This approach ensures minimal API calls while still providing accurate and meaningful feedback to users.
Reflections
Implementing an online indicator was a rewarding challenge. Beyond the technical details, it made me consider how to balance accuracy, performance, and user experience. While this feature might seem small, it’s a fun way to make my portfolio feel more personal and dynamic.
If you have any questions, feel free to reach out to me on X / Twitter, LinkedIn, or the contact page!