A fast, focused bookmark manager built with Next.js, Supabase, and Tailwind CSS.
I wanted a clean, modern space to save links quickly and find them later without clutter. The UI is intentionally minimal and dark, with a strong focus on readability and speed. The goal: make saving links feel instant and revisiting them feel effortless.
- Authentication: Secure Google OAuth login via Supabase.
- Bookmarks: Add, delete, and view your personal bookmarks.
- Optimistic UI: Instant updates for a snappy user experience.
- Real-time Sync: Changes reflect immediately across all devices.
- Smart Fetch: Automatically fetches page titles from URLs.
- Responsive Design: Beautiful UI that works on mobile and desktop.
- Supabase OAuth redirect mismatch
So basically when you click "Sign in with Google," Google authenticates you and then needs to send you back to your app at a specific URL. The problem was that the URL I had set in Supabase and the URL Google was actually trying to redirect to didn't match — even a small difference like a trailing slash or wrong domain would cause it to just fail silently or throw an error. The fix was making sure the callback URL (/auth/callback) was exactly the same everywhere — in the Supabase dashboard, in the Google Cloud Console, and in the code. Then I tested it both locally and on the deployed version because they're different URLs and both need to be registered.
- RLS policies blocking reads and writes
Supabase uses Row Level Security by default, which means even if your database has data, users literally can't see or touch it unless you explicitly write rules saying they can. I ran into this where bookmarks just weren't showing up, and new ones weren't saving — no real error, just nothing happening. Turns out the RLS policies weren't set up. I had to go in and write rules that said "a user can only select, insert, or delete rows where the user_id matches their own auth ID." Once those were in place and I tested with an actual logged-in user, everything worked.
- Metadata fetch blocked by some sites
The app automatically tries to grab the title of a webpage when you paste a URL, which is a nice touch. But a lot of fighting every site's anti-scraping rules, I just made the title fetch a "nice to have." If it works, great — the title gets filled in automatically. If it doesn't, the input just stays empty and the user types the title themselves. No crash, no broken experience.
- Realtime duplicates after optimistic updates
This one was a classic timing bug. When you add a bookmark, I immediately show it in the UI before the server even responds — that's the "optimistic" part, it feels instant. But the app is also listening to Supabase Realtime for any new data. So when the server confirms the insert, Realtime fires an event and the app tries to add the same bookmark again, ending up with two copies. The fix was simple: before adding anything from the Realtime stream, check if a bookmark with that same id already exists in the list. If it does, skip it. Duplicates gone.
- Framework: Next.js 16 (App Router)
- Database: Supabase (PostgreSQL)
- Styling: Tailwind CSS
- Icons: Lucide React
- Animations: Framer Motion
- Utilities: Cheerio (Metadata fetching), Sonner (Toasts)
Create a .env.local file in the root directory:
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key-
Install dependencies:
pnpm install
-
Run the development server:
pnpm dev
-
Open http://localhost:3000 with your browser.
Run the SQL commands in init.sql in your Supabase SQL Editor to set up the database schema and Row Level Security (RLS) policies.
- Push to GitHub: Commit your changes and push to a GitHub repository.
- Import Project: Go to Vercel and import the project.
- Environment Variables: Add
NEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_ANON_KEYin the Vercel project settings. - Deploy: Click deploy and wait for the build to finish.
