Small hobby project to learn and play with:
- Next.js (App Router)
- Supabase Realtime Presence
- WebSocket presence only (no REST fetch, no DB polling)
The page shows:
👀 X people are viewing this page right now
When a user opens the page:
- The browser joins a Supabase realtime channel for the current route.
- The client tracks its presence in that channel.
- Every connected client updates the count from shared presence state.
- On tab close, unload, or disconnect, presence is removed and everyone updates.
app/
globals.css
layout.js
page.js
components/
realtime-visitor-counter.js
lib/
supabase-browser.js
docs/
implementation.md
- Node.js 20+
- A Supabase project
- Supabase project URL and anon key
- Install dependencies:
npm install- Create local env file:
cp .env.example .env.local- Set values in
.env.local:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEY
- Start development server:
npm run dev- Open
http://localhost:3000in multiple tabs or browsers and watch the count update live.
npm run dev- start development servernpm run build- build the appnpm run start- start the built app (optional)
- Channel name is derived from route pathname (
visitors:<sanitized-path>). - Presence state is read from
channel.presenceState(). - Count is calculated as the total number of active presence entries.
- The component subscribes to presence events:
syncjoinleave
- No table queries are used.
- Client uses a singleton Supabase browser client.
- Auth session persistence is disabled (
persistSession: false) because this demo does not need user auth state. - Cleanup is explicit:
channel.untrack()on teardownsupabase.removeChannel(channel)on teardownuntrack()also runs onpagehideandbeforeunload
- Error:
Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY.- Set both values in
.env.localand restart dev server.
- Set both values in
- Count does not change across tabs:
- Verify both tabs are on the same route.
- Verify keys point to the same Supabase project.
- Check browser console/network for websocket blocking.
See docs/implementation.md for lifecycle, channel strategy, customization points, and extension ideas.