Your Beautiful Bookmark Manager
A modern, real-time bookmark management application built with Next.js 16, Supabase, and TypeScript. Save, organize, and sync your bookmarks instantly across all devices.
- 🔐 Google OAuth Authentication - Secure login with Google
- 📱 Real-time Sync - Bookmarks update instantly across all open tabs
- 🎨 Beautiful UI - Modern gradient design with smooth animations
- 📲 Fully Responsive - Optimized for mobile, tablet, and desktop
- ⚡ Lightning Fast - Built with Next.js 16 and Turbopack
- 🗄️ Supabase Backend - PostgreSQL database with Row Level Security
- 🔄 Live Updates - WebSocket-based real-time synchronization
- 🎯 Type-Safe - Full TypeScript support
| Category | Technology | Version |
|---|---|---|
| Framework | Next.js | 16.1.6 |
| UI Library | React | 19.2.3 |
| Language | TypeScript | ^5 |
| Styling | Tailwind CSS | ^4 |
| Backend | Supabase | 2.95.3 |
| Database | PostgreSQL | (via Supabase) |
| Auth | Supabase Auth | (Google OAuth) |
| Realtime | Supabase Realtime | WebSockets |
This section documents the key challenges encountered during development and how they were solved.
After implementing the Supabase real-time listener, bookmarks were not syncing across tabs. The subscription status showed CLOSED instead of SUBSCRIBED.
- Supabase client was not configured with real-time options
- Real-time was not enabled on the
bookmarkstable in Supabase dashboard - Channel configuration had unnecessary options causing conflicts
// ❌ Before: Missing realtime config
const supabase = createClient(url, key);
// ✅ After: Added realtime configuration
const supabase = createClient(url, key, {
realtime: {
params: {
eventsPerSecond: 10,
},
},
});Additional Steps:
- Enabled real-time in Supabase Dashboard → Database → Replication
- Simplified channel name from
"realtime-bookmarks"to"public:bookmarks" - Removed unnecessary broadcast/presence config options
- Added comprehensive error logging to diagnose connection issues
Result: Real-time sync now works perfectly. Adding a bookmark in one tab instantly appears in all other open tabs.
Console showed hydration mismatch error:
A tree hydrated but some attributes of the server rendered HTML didn't match the client properties
The error pointed to the <body> tag with an unexpected cz-shortcut-listen="true" attribute.
Browser extensions (like Chrome extensions) inject attributes into the DOM after server-side rendering but before React hydration, causing a mismatch.
// Added suppressHydrationWarning to body tag
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
suppressHydrationWarning
>
{children}
</body>Why This Works: The suppressHydrationWarning prop tells React to ignore hydration mismatches on this specific element, which is safe since the attributes are added by browser extensions and don't affect functionality.
Result: Clean console with no hydration warnings.
The app looked great on desktop but was unusable on mobile:
- Text was too large and overflowed
- Layout didn't stack properly
- Touch targets were too small
- Spacing was inconsistent across screen sizes
Implemented Tailwind Breakpoints:
// ❌ Before: Fixed desktop sizes
<h1 className="text-8xl">MARKSYNC</h1>
// ✅ After: Responsive scaling
<h1 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl">
MARKSYNC
</h1>Users could potentially see or modify other users' bookmarks without proper security policies.
Implemented comprehensive RLS policies:
-- Users can only view their own bookmarks
CREATE POLICY "Users can view their own bookmarks"
ON bookmarks FOR SELECT
USING (auth.uid() = user_id);
-- Users can only insert their own bookmarks
CREATE POLICY "Users can insert their own bookmarks"
ON bookmarks FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- Users can only delete their own bookmarks
CREATE POLICY "Users can delete their own bookmarks"
ON bookmarks FOR DELETE
USING (auth.uid() = user_id);Security Benefits:
- Database-level security (not just client-side)
- Automatic enforcement by PostgreSQL
- Protection against API manipulation
- Works seamlessly with real-time subscriptions
- Node.js 18+ installed
- npm or yarn package manager
- A Supabase account (Sign up free)
git clone https://github.com/Soumik-R/marksync.git
cd marksyncnpm install- Create a new project on Supabase
- Go to Settings > API to get your credentials
- Create a
bookmarkstable with the following SQL:
-- Create bookmarks table
CREATE TABLE bookmarks (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
title TEXT NOT NULL,
url TEXT NOT NULL,
user_id UUID REFERENCES auth.users NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);
-- Enable Row Level Security
ALTER TABLE bookmarks ENABLE ROW LEVEL SECURITY;
-- Create policies
CREATE POLICY "Users can view their own bookmarks"
ON bookmarks FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can insert their own bookmarks"
ON bookmarks FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can delete their own bookmarks"
ON bookmarks FOR DELETE
USING (auth.uid() = user_id);
-- Enable Realtime (CRITICAL for real-time sync)
ALTER PUBLICATION supabase_realtime ADD TABLE bookmarks;-
Enable Real-time in Dashboard:
- Go to Database → Replication
- Find
bookmarkstable - Toggle Enable Realtime to ON
- Click Save
-
Enable Google OAuth:
- Go to Authentication → Providers
- Enable Google
- Add your Google OAuth credentials
Create a .env.local file in the root directory:
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key.env.local to version control!
npm run devOpen http://localhost:3000 to see the app.
| Command | Description |
|---|---|
npm run dev |
Start development server with hot-reload |
npm run build |
Create optimized production build |
npm run start |
Start production server |
npm run lint |
Run ESLint to check code quality |
- Push your code to GitHub
- Import your repository on Vercel
- Add environment variables in Vercel dashboard:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEY
- Deploy! 🚀
Important Deployment Notes:
- ✅ Environment variables must use
NEXT_PUBLIC_prefix for client-side access - ✅ Configure Supabase redirect URLs in Authentication → URL Configuration
- ✅ Add your Vercel domain to allowed OAuth redirect URLs
- Row Level Security (RLS) - Users can only access their own bookmarks
- Environment Variables - Sensitive data kept secure
- OAuth 2.0 - Secure authentication via Google
- HTTPS-only - Encrypted connections in production
- Database-level Security - PostgreSQL policies enforce access control
Symptoms: Bookmarks don't sync across tabs
Solutions:
- Check browser console for subscription status
- Verify real-time is enabled in Supabase Dashboard → Database → Replication
- Ensure RLS policies allow SELECT for authenticated users
- Check that
ALTER PUBLICATION supabase_realtime ADD TABLE bookmarks;was run
Solutions:
- Add your domain to Supabase Auth → URL Configuration
- Ensure redirect URL matches your deployment URL
- Check Google OAuth settings in Google Cloud Console
Solution: Already handled with suppressHydrationWarning on body tag
Soumik Roy
- LinkedIn: mesoumikr
- Email: soumikroy7272@gmail.com
- Instagram: @soumik.roy_
This project is open source and available under the MIT License.
- Built with Next.js
- Backend powered by Supabase
- Styled with Tailwind CSS
Made with ❤️ by Soumik Roy