One link for everything. Perfectly minimal.
A high-performance, Linktree-style bio-link platform built with Next.js 16. Create a stunning public profile in seconds — add all your links, connect your socials, pick a theme, and share your single custom URL with the world.
- 🔐 Google OAuth — Sign in instantly with your Google account via NextAuth.js
- 🎨 11 Themes — Choose from Obsidian, Ocean Glow, Terminal, Sunset, Bubblegum, Cyberpunk, Forest, Dracula, Monochrome, Midnight, Lavender, and more
- 🔗 Link Management — Add, toggle, reorder (drag & drop), and delete links with click-count tracking
- 📸 Image Uploads — Upload a profile avatar and per-link thumbnails via Cloudinary
- 🌐 Social Handles — Connect Instagram, Twitter, GitHub, LinkedIn, YouTube & your personal website
- 📊 Analytics Dashboard — Track total profile views and per-link click counts with a visual bar chart
- 📱 Live Phone Preview — Real-time mobile preview of your public profile right inside the dashboard
- 🚀 Public Profile Page — A beautiful, SEO-ready public page at
yoursite.com/@username - 🧭 Guided Onboarding — 5-step onboarding flow for first-time users (goal → theme → platforms → links → profile)
- ⚙️ Settings — Manage profile details, username, banner, layout, and account deletion
- 🔒 Protected Routes — Middleware-enforced auth guards for dashboard, setup, and onboarding pages
- 🔄 Auto Metadata Fetch — Paste a URL when adding a link and the title & thumbnail are fetched automatically
| Route | Description |
|---|---|
/ |
Landing page — hero + features + CTA |
/login |
Google OAuth sign-in |
/setup |
First-time username claiming |
/onboarding |
5-step guided setup wizard |
/dashboard |
Link manager + live phone preview + profile editor |
/dashboard/analytics |
Profile views & per-link click chart |
/dashboard/settings |
Account settings, theme config, account deletion |
/@username |
Public profile page (shareable URL) |
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, Server Components) |
| Language | TypeScript 5 |
| Styling | Tailwind CSS v4 |
| Database | PostgreSQL via Prisma ORM |
| Auth | NextAuth.js v4 (Google Provider, JWT sessions) |
| File Storage | Cloudinary (avatar + link thumbnails) |
| Drag & Drop | @dnd-kit (sortable link list) |
| Charts | Recharts (analytics bar chart) |
| Icons | react-icons (Feather Icons) |
Make sure you have the following installed:
- Node.js (v18 or later)
- npm or pnpm
- A PostgreSQL database (local or hosted e.g. Neon, Supabase)
- A Google Cloud project with OAuth 2.0 credentials
- A Cloudinary account
git clone https://github.com/H-sharma63/onelink.git
cd onelinknpm installCreate a .env.local file in the project root and add the following:
# NextAuth
NEXTAUTH_SECRET=your_nextauth_secret_here # Generate with: openssl rand -hex 32
NEXTAUTH_URL=http://localhost:3000
# Google OAuth (from Google Cloud Console)
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
# Cloudinary (from cloudinary.com dashboard)
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloud_name
NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET=your_upload_preset
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
# PostgreSQL Database URL
DATABASE_URL=postgresql://user:password@localhost:5432/onelinkTip: You can generate a secure
NEXTAUTH_SECRETby runningopenssl rand -hex 32in your terminal.
- Go to Google Cloud Console
- Create a new project (or use an existing one)
- Navigate to APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID
- Set Application type to
Web application - Add the following to Authorized redirect URIs:
http://localhost:3000/api/auth/callback/google(for local development)https://your-production-domain.com/api/auth/callback/google(for production)
- Copy the Client ID and Client Secret into your
.env.local
- Sign up at cloudinary.com
- From your Dashboard, copy your Cloud Name, API Key, and API Secret
- Go to Settings → Upload → Upload presets → Create an unsigned preset (name it e.g.
ml_default) - Add all values to your
.env.local
Run the Prisma migration to create the database tables:
npx prisma migrate dev --name initTo view and manage your data with a GUI:
npx prisma studionpm run devOpen http://localhost:3000 in your browser. 🎉
onelink/
├── app/
│ ├── page.tsx # Landing page
│ ├── layout.tsx # Root layout
│ ├── globals.css # Global styles
│ ├── login/ # Sign-in page
│ ├── setup/ # Username setup (first login)
│ ├── onboarding/ # 5-step onboarding wizard
│ ├── dashboard/
│ │ ├── page.tsx # Main dashboard (links + profile editor)
│ │ ├── layout.tsx # Sidebar layout
│ │ ├── analytics/ # Analytics page (views + clicks)
│ │ └── settings/ # Settings page
│ ├── [username]/
│ │ └── page.tsx # Public profile page
│ └── api/
│ ├── auth/[...nextauth]/ # NextAuth handler
│ ├── profile/route.ts # GET, PATCH, DELETE profile
│ ├── links/
│ │ ├── route.ts # GET all links, POST new link
│ │ ├── [id]/route.ts # PATCH, DELETE a single link
│ │ └── metadata/route.ts # Auto-fetch URL metadata (title, image)
│ ├── upload/route.ts # Cloudinary upload handler
│ └── user/ # User-related endpoints
├── components/
│ ├── Providers.tsx # Session provider wrapper
│ ├── SortableLinkItem.tsx # Drag-and-drop link list item
│ └── PublicLinkButton.tsx # Link button on public profile
├── lib/
│ ├── auth.ts # NextAuth configuration
│ └── prisma.ts # Prisma client singleton
├── prisma/
│ ├── schema.prisma # Database schema (User, Link, Account, Session)
│ └── migrations/ # Migration history
├── types/ # Shared TypeScript types
├── middleware.ts # Auth & routing guards
└── next.config.ts # Next.js configuration
model User {
id String @id @default(cuid())
username String? @unique // Custom public URL handle
name String?
email String? @unique
image String? // Avatar (Cloudinary URL)
bio String?
banner String? // Banner image (Cloudinary URL)
theme String @default("dark")
themeConfig Json? // Layout, advanced theme options
socials Json? // Instagram, Twitter, GitHub, etc.
onboarded Boolean @default(false)
views Int @default(0) // Total profile page views
links Link[]
}
model Link {
id String @id @default(cuid())
title String
url String
clicks Int @default(0) // Click tracking
order Int // Drag-and-drop order
isActive Boolean @default(true)
thumbnail String? // Link thumbnail (Cloudinary URL)
icon String? // Favicon URL (auto-fetched)
userId String
}| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/api/profile |
Get the current user's profile | ✅ Required |
PATCH |
/api/profile |
Update profile (name, bio, theme, socials, etc.) | ✅ Required |
DELETE |
/api/profile |
Delete account and all data | ✅ Required |
GET |
/api/links |
Get all links for the current user | ✅ Required |
POST |
/api/links |
Add a new link | ✅ Required |
PATCH |
/api/links/:id |
Update a link (toggle, reorder, rename) | ✅ Required |
DELETE |
/api/links/:id |
Delete a link | ✅ Required |
GET |
/api/links/metadata?url= |
Fetch title & thumbnail from a URL | ✅ Required |
POST |
/api/upload |
Upload an image to Cloudinary | ✅ Required |
| Theme ID | Name | Description |
|---|---|---|
dark |
Obsidian | Deep black — the default, ultra-minimal |
light |
Clean | Crisp white, soft shadows |
ocean |
Ocean Glow | Dark navy with soft blue tones |
hacker |
Terminal | Black background, neon green text |
sunset |
Sunset | Orange-to-pink gradient |
bubblegum |
Bubblegum | Pastel pink, playful |
cyberpunk |
Cyberpunk | Bright yellow + black high contrast |
forest |
Forest | Deep greens, earthy feel |
dracula |
Dracula | Dark purple with pink accents |
monochrome |
Monochrome | Pure black & white |
midnight |
Midnight | Near-black with teal/cyan accents |
lavender |
Lavender | Soft purple tones |
Contributions are welcome and appreciated! Here's how to get involved:
- Fork the repository on GitHub
- Clone your fork locally:
git clone https://github.com/H-sharma63/onelink.git
- Create a branch for your feature or fix:
git checkout -b feature/your-feature-name
- Follow the Getting Started steps above to get your local environment running
- Keep commits focused and atomic (one change per commit)
- Write clear, descriptive commit messages
- Follow the existing code style (TypeScript, functional components, Tailwind utility classes)
- Add or update types in the
types/folder if needed - When adding API routes, follow the existing pattern in
app/api/
- Push your branch to your fork:
git push origin feature/your-feature-name-yourname
- Open a Pull Request on GitHub against the
mainbranch - Describe what your PR does and why
- Link any related issues
Here are some ideas if you're looking for where to start:
- 🐛 Bug fixes — Check open issues for reported bugs
- 🎨 New themes — Add a new theme to the
getThemeClassesfunction indashboard/page.tsxand[username]/page.tsx - 📊 Analytics improvements — Richer charts, date ranges, top-performing links
- 🧩 New social platforms — Add TikTok, Discord, Twitch, etc.
- 📱 Mobile dashboard — Improve the dashboard layout on smaller screens
- 🌐 Internationalization (i18n) — Multi-language support
- ♿ Accessibility — ARIA labels, keyboard navigation improvements
- 🧪 Tests — Unit or integration tests for API routes and components
- TypeScript is required — avoid
anytypes where possible - Tailwind CSS for all styling — no inline CSS unless absolutely necessary
- Server Components by default; use
"use client"only when you need browser APIs or React hooks - Prisma for all database operations — never write raw SQL
npm run dev # Start the development server (http://localhost:3000)
npm run build # Build for production
npm run start # Start the production server
npm run lint # Run ESLintnpx prisma migrate dev # Apply database migrations in development
npx prisma studio # Open Prisma Studio (DB GUI)
npx prisma generate # Re-generate the Prisma client after schema changesThis project is open source and available under the MIT License.
Built with precision by H-sharma63
If you find this project useful, please consider giving it a ⭐ on GitHub!