Skip to main content

Overview

This guide covers everything you need to self-host the Bookmarks application, including local development setup, environment configuration, and Supabase backend deployment.
The application requires Node.js 16+ and a Supabase account (free tier available).

Prerequisites

Before you begin, ensure you have:
  • Node.js 16.x or higher (download)
  • npm or yarn package manager
  • Git for version control
  • Supabase account (sign up free)

Quick Start

1

Clone the Repository

Clone the project from GitHub and navigate to the directory:
git clone https://github.com/br4adam/bookmarks.git
cd bookmarks
Fork the repository first if you plan to make modifications and contribute back to the project.
2

Install Dependencies

Install all required packages using npm or yarn:
npm install

Key Dependencies

The application uses these core libraries:
package.json:12-24
"dependencies": {
  "@headlessui/react": "^2.2.7",
  "@supabase/supabase-js": "^2.21.0",
  "@vercel/analytics": "^1.5.0",
  "cmdk": "^0.2.1",
  "dayjs": "^1.11.13",
  "iconoir-react": "^7.11.0",
  "react": "^18.2.0",
  "react-dom": "^18.3.1",
  "react-markdown": "^10.1.0",
  "sonner": "^2.0.7",
  "tailwind-merge": "^3.3.1",
  "zustand": "^4.5.6"
}
  • @supabase/supabase-js: Supabase client for authentication and database
  • cmdk: Command menu component for fast search
  • zustand: Lightweight state management
  • sonner: Toast notifications
  • iconoir-react: Icon library
3

Configure Supabase

Create a Supabase Project

  1. Go to supabase.com and sign in
  2. Click New Project
  3. Enter project details and create
  4. Wait for the database to provision (2-3 minutes)

Get Your Credentials

From your Supabase project dashboard:
  1. Go to Settings > API
  2. Copy the Project URL
  3. Copy the anon public key

Create the Database Schema

Navigate to SQL Editor and run:
create table bookmarks (
  id bigint generated by default as identity primary key,
  created_at timestamp with time zone default timezone('utc'::text, now()) not null,
  title text not null,
  domain text not null,
  url text not null,
  description text,
  image text,
  tags text[] default '{}'::text[],
  saved_by uuid references auth.users not null,
  pinned boolean default false
);

-- Enable Row Level Security
alter table bookmarks enable row level security;

-- Create policy for users to read their own bookmarks
create policy "Users can view their own bookmarks"
  on bookmarks for select
  using (auth.uid() = saved_by);

-- Create policy for users to insert their own bookmarks
create policy "Users can insert their own bookmarks"
  on bookmarks for insert
  with check (auth.uid() = saved_by);

-- Create policy for users to update their own bookmarks
create policy "Users can update their own bookmarks"
  on bookmarks for update
  using (auth.uid() = saved_by);

-- Create policy for users to delete their own bookmarks
create policy "Users can delete their own bookmarks"
  on bookmarks for delete
  using (auth.uid() = saved_by);
Row Level Security (RLS) is critical for data protection. Never disable RLS in production.

Configure Authentication Providers

  1. Go to Authentication > Providers
  2. Enable Email provider
  3. Configure email templates (optional)

Enable GitHub OAuth

  1. Create a GitHub OAuth App:
    • Go to GitHub Settings > Developer settings > OAuth Apps
    • Click New OAuth App
    • Set Authorization callback URL to: https://<your-project-ref>.supabase.co/auth/v1/callback
  2. Copy the Client ID and Client Secret
  3. In Supabase, go to Authentication > Providers > GitHub
  4. Enable GitHub and paste your credentials
For local development, you can also add http://localhost:5173/auth/callback as an additional callback URL.
4

Environment Variables

Create a .env file in the project root:
touch .env
Add your Supabase credentials:
.env
VITE_SUPABASE_URL=https://your-project-ref.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key-here
The application uses these variables to initialize the Supabase client:
supabase.ts:1-8
import { createClient } from "@supabase/supabase-js"

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY

const supabase = createClient(supabaseUrl, supabaseAnonKey)

export default supabase
Never commit your .env file to version control. Add it to .gitignore immediately.
5

Start Development Server

Run the development server with Vite:
npm run dev
The application will start at http://localhost:5173

Available Scripts

package.json:6-10
"scripts": {
  "dev": "vite",
  "build": "tsc && vite build",
  "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
  "preview": "vite preview"
}
  • npm run dev - Start development server with hot reload
  • npm run build - Build for production (includes TypeScript check)
  • npm run lint - Run ESLint for code quality
  • npm run preview - Preview production build locally

Production Deployment

Build for Production

Create an optimized production build:
npm run build
This command:
  1. Runs TypeScript compiler to check types
  2. Builds optimized bundles with Vite
  3. Outputs to the dist/ directory

Deploy to Vercel

1

Install Vercel CLI

npm install -g vercel
2

Deploy

vercel
Follow the CLI prompts to link your project.
3

Add Environment Variables

In Vercel dashboard:
  1. Go to Settings > Environment Variables
  2. Add VITE_SUPABASE_URL
  3. Add VITE_SUPABASE_ANON_KEY
  4. Redeploy

Deploy to Netlify

  1. Connect your GitHub repository
  2. Set build command: npm run build
  3. Set publish directory: dist
  4. Add environment variables in site settings

Project Structure

bookmarks/
├── src/
│   ├── components/          # React components
│   │   ├── AddBookmark.tsx  # Bottom fixed input
│   │   ├── Login.tsx        # Auth modal
│   │   ├── CommandMenu.tsx  # Search (⌘K)
│   │   ├── BookmarkTags.tsx # Tag editor
│   │   └── ...
│   ├── stores/              # Zustand state management
│   │   ├── BookmarkStore.ts # Bookmark CRUD operations
│   │   ├── AuthStore.ts     # Authentication state
│   │   └── ModalStore.ts    # Modal visibility
│   ├── utils/               # Utility functions
│   │   ├── supabase.ts      # Supabase client
│   │   ├── getMetadata.ts   # URL metadata fetcher
│   │   └── showToast.ts     # Toast notifications
│   ├── views/               # Page components
│   │   ├── Showcase.tsx     # Landing page
│   │   └── Bookmarks.tsx    # Main app view
│   ├── App.tsx              # Root component
│   └── main.tsx             # Entry point
├── public/                  # Static assets
├── package.json             # Dependencies
├── vite.config.ts           # Vite configuration
├── tailwind.config.js       # Tailwind CSS config
└── tsconfig.json            # TypeScript config

Configuration Files

Vite Configuration

vite.config.ts:1-9
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  assetsInclude: ['**/*.md']
})

TypeScript Configuration

The project uses strict TypeScript settings for type safety:
  • Strict mode enabled
  • No implicit any
  • Strict null checks
  • ES2020 target with ESNext module resolution

State Management

The application uses Zustand for efficient state management:

Bookmark Store

Manages all bookmark CRUD operations:
BookmarkStore.ts:24-44
export const useBookmarkStore = create<BookmarkState>(set => ({
  bookmarks: [],
  loading: false,
  fetch: async (userId) => {
    try {
      set({ loading: true })
      const { data, error } = await supabase
        .from("bookmarks")
        .select("*")
        .eq("saved_by", userId)
        .order("pinned", { ascending: false })
        .order("created_at", { ascending: false })
        .returns<Bookmark[]>()
      if (error) throw new Error(`Error fetching bookmarks: ${error.message}`)
      set({ bookmarks: data })
      return { data, success: true }
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : "Something went wrong."
      return { data: errorMessage, success: false }
    } finally {
      set({ loading: false })
    }
  },

Auth Store

Handles authentication state and Supabase auth methods. Tracks which modals are open for proper UI layering.

Troubleshooting

Common Issues

Run npm run lint to identify type errors. Make sure all components are properly typed.
Verify your .env file has the correct VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY. Restart the dev server after changing environment variables.
Check that:
  • Email provider is enabled in Supabase
  • GitHub OAuth is properly configured with correct callback URLs
  • RLS policies are created on the bookmarks table
Some websites block metadata scraping. This is expected behavior - the bookmark will save with minimal information (just the URL and domain).

Development Tips

Use React DevTools and the Zustand DevTools for debugging state changes during development.
The application uses Tailwind CSS with a custom design system. All colors, spacing, and typography follow the zinc color palette for consistent dark mode theming.

Next Steps

Quick Start Guide

Learn how to use the application features

GitHub Repository

Star the project and contribute

Build docs developers (and LLMs) love