A real-time bookmark manager built with Next.js + Supabase + Socket.IO.
Users can:
- Sign in with Google (Supabase Auth)
- Add and delete personal bookmarks
- See bookmark updates sync across multiple tabs in real time
- Frontend: Next.js 14 (App Router), React 18, Tailwind CSS
- Auth + Database: Supabase (OAuth + Postgres)
- Realtime transport: Socket.IO (custom Node server)
.
├── app/ # Next.js App Router pages
│ ├── page.js # Landing + auth gate
│ └── dashboard/page.js # Protected dashboard UI
├── components/ # Reusable UI components
├── lib/
│ ├── auth.js # Sign-in/sign-out helpers
│ ├── bookmarks.js # Bookmark state + socket sync hook
│ ├── socket.js # Socket.IO client
│ └── supabase-client.js # Browser Supabase client factory
├── server/
│ ├── index.js # Socket.IO server + Supabase writes
│ └── package.json # Server dependencies
└── README.md
- npm
- Node.js 18+
- A Supabase project
Create a .env file in the project root:
# Frontend socket endpoint
NEXT_PUBLIC_SOCKET_URL=http://localhost:3001
# Supabase (frontend)
NEXT_PUBLIC_SUPABASE_URL=https://YOUR_PROJECT_REF.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
# Optional app URL used in some OAuth/callback setups
NEXT_PUBLIC_BASE_URL=http://localhost:3000The Socket.IO server (server/index.js) currently reads:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY
Add this to root .env and load accordingly:
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=YOUR_SUPABASE_SERVICE_ROLE_KEYIn Supabase Dashboard:
Authentication->Providers->Google-> enable- Configure Google client ID/secret and authorized redirect URLs
Run this SQL in Supabase SQL editor:
create table if not exists public.bookmarks (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users(id) on delete cascade,
title text not null,
url text not null,
created_at timestamptz not null default now()
);
create index if not exists idx_bookmarks_user_id on public.bookmarks(user_id);
alter table public.bookmarks enable row level security;
create policy "Users can view own bookmarks"
on public.bookmarks
for select
using (auth.uid() = user_id);
create policy "Users can insert own bookmarks"
on public.bookmarks
for insert
with check (auth.uid() = user_id);
create policy "Users can delete own bookmarks"
on public.bookmarks
for delete
using (auth.uid() = user_id);Install frontend deps:
npm installInstall socket server deps:
cd server
npm install
cd ..Start frontend (terminal 1):
npm run devStart websocket server (terminal 2):
cd server
node index.jsApp URLs:
- Frontend:
http://localhost:3000 - Socket server:
http://localhost:3001
- User signs in on
/using Google OAuth. - App routes authenticated users to
/dashboard. useBookmarkshook connects to Socket.IO withsession.access_token.- Socket server verifies token (
supabase.auth.getUser(token)). - Add/delete events are written to DB server-side and broadcast to all user tabs.
- Tabs update local state instantly from websocket events.
Frontend scripts (root package.json):
npm run dev- start Next.js dev servernpm run build- production buildnpm run start- run production server
This is handled by an auth-check grace flow in app/dashboard/page.js. If you still see flicker, verify auth callback/session persistence in Supabase settings.
- Ensure Socket.IO server is running on
NEXT_PUBLIC_SOCKET_URL - Confirm frontend and server are using the same Supabase project
- Check browser console for socket
connect_error
- Check websocket server logs
- Verify
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEYis valid - Confirm RLS policies exist and table name is
public.bookmarks
- Socket auth is token-based and user-scoped on the server.
- The server currently expects a service role key via
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY(name is misleading). For production, prefer a non-NEXT_PUBLIC_env name and keep it server-only. - CORS is currently permissive (
origin: "*") inserver/index.js. Lock this down before production.