A production-grade URL shortener with real-time analytics, built on Next.js 16, React 19, and MongoDB.
Trimly lets users shorten URLs, track every click with granular analytics (geolocation, device, browser, bot/VPN detection), and provides an admin dashboard for platform-wide monitoring.
Live Demo: trimly-pro.vercel.app
- Features
- Tech Stack
- Architecture
- Getting Started
- Project Structure
- API Reference
- Database Models
- Authentication
- Deployment
- Security
- Contributing
- License
URL Management
- Shorten any URL into an 8-character unique short link
- Copy short URLs to clipboard with one click
- Delete URLs from your dashboard at any time
- Support for optional password-protected links (schema ready)
Click Analytics
- Real-time click tracking on every redirect
- Geographic data: country, city, region, coordinates, ISP
- Device intel: browser, OS, device type, brand
- Security insights: bot detection, VPN/proxy flags, threat level
- Referrer and landing page tracking
- Session and unique visitor identification
Authentication & User Management
- Email/password registration with bcrypt hashing
- Google OAuth sign-in
- 6-digit OTP email verification (via Gmail SMTP)
- Forgot password flow with time-limited OTP (5-minute expiry)
- JWT-based session management
Admin Dashboard
- Platform-wide URL and click statistics
- Browse all shortened URLs across all users
- Deep-dive analytics per URL: top countries, browsers, referral sources, security overview
- Granular per-click traffic table with export capability
UI/UX
- Responsive design with mobile hamburger menu
- Tailwind CSS v4 styling
- Geist Sans + Geist Mono typography
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| UI | React 19 |
| Language | TypeScript 5 |
| Styling | Tailwind CSS 4 |
| Database | MongoDB (Atlas) |
| ODM | Mongoose 9 |
| Authentication | NextAuth.js 4 |
| Nodemailer (Gmail SMTP) | |
| Short ID | nanoid |
| Analytics Parsing | ua-parser-js |
| HTTP Client | Axios |
| GeoIP | ipinfo.io |
Browser
|
v
Next.js App Router (Vercel)
|
+-- Server Components --> Static pages, layouts
+-- Client Components --> Interactive dashboards, auth forms
+-- API Routes --> /api/url, /api/auth, /api/admin/*
|
v
MongoDB Atlas <--> Mongoose ODM
|
v
[shortUrl] route --> Redirect + analytics pipeline
|
+-- IP hashing (bcrypt)
+-- GeoIP lookup (ipinfo.io)
+-- UA parsing (ua-parser-js)
+-- Bot/VPN detection
+-- Write clickHistory to MongoDB
Key design decisions:
- App Router only -- no
pages/directory; all routes useapp/withroute.tshandlers andpage.tsxpages - JWT sessions -- no database session store; session data embedded in signed JWT
- Embedded click history -- click events stored as subdocuments inside the
ShortUrldocument for fast single-document reads - IP hashing -- raw IPs are never stored; bcrypt-hashed IPs protect user privacy
- Node.js v20 or later
- MongoDB instance (Atlas cluster or local)
- Gmail account with app password (for OTP emails)
- Google Cloud project with OAuth credentials (for Google sign-in)
- ipinfo.io account (for geolocation on click tracking)
git clone https://github.com/mabdullah356/trimly.git
cd Trimly
npm installCreate a .env.local file in the project root:
# MongoDB
MONGO_URL=mongodb+srv://<user>:<password>@<cluster>.mongodb.net/<dbname>?retryWrites=true&w=majority
# NextAuth
NEXTAUTH_SECRET=<random-secret-string>
NEXTAUTH_URL=http://localhost:3000
# Google OAuth
GOOGLE_CLIENT_ID=<your-google-client-id>
GOOGLE_CLIENT_SECRET=<your-google-client-secret>
# Email (Gmail SMTP)
USER_EMAIL=your-gmail@gmail.com
USER_PASS=<16-char-gmail-app-password>
# Geolocation
IPINFO_TOKEN=<your-ipinfo-token>
# Application
APP_URL=http://localhost:3000| Variable | Description |
|---|---|
MONGO_URL |
MongoDB Atlas connection string |
NEXTAUTH_SECRET |
Secret for encrypting JWTs (generate with openssl rand -base64 32) |
NEXTAUTH_URL |
Base URL for NextAuth callbacks |
GOOGLE_CLIENT_ID |
Google OAuth 2.0 client ID |
GOOGLE_CLIENT_SECRET |
Google OAuth 2.0 client secret |
USER_EMAIL |
Gmail address used to send OTP emails |
USER_PASS |
Gmail app password (not your account password) |
IPINFO_TOKEN |
API token from ipinfo.io |
APP_URL |
Application base URL |
npm run devOpen http://localhost:3000.
trimly/
|-- app/ # Next.js App Router
| |-- layout.tsx # Root layout (fonts, session provider, header, footer)
| |-- page.tsx # Homepage (hero + URL list)
| |-- globals.css # Tailwind CSS v4 entry
| |
| |-- [shortUrl]/
| | |-- route.ts # Redirect handler + analytics tracking
| |
| |-- admin/
| | |-- page.tsx # Admin dashboard
| | |-- url/[id]/page.tsx # Per-URL analytics detail
| |
| |-- api/
| | |-- auth/
| | | |-- [...nextauth]/route.ts # NextAuth handler
| | | |-- signups/route.ts # User registration
| | |-- url/
| | | |-- route.ts # Create / list URLs
| | | |-- [id]/route.ts # Delete URL
| | |-- admin/urls/
| | | |-- route.ts # All URLs (admin)
| | | |-- [id]/route.ts # URL detail (admin)
| | |-- user/
| | |-- forgot-password/route.ts
| | |-- reset-password/route.ts
| | |-- verify-account/route.ts
| | |-- resend-verification-email/route.ts
| |
| |-- login/page.tsx # Login page
| |-- signups/page.tsx # Registration page
| |-- verify-account/[email]/ # OTP verification
| |-- reset-password/page.tsx # Password reset flow
|
|-- components/
| |-- Header.tsx # Auth-aware navbar
| |-- Footer.tsx # Site footer
| |-- HeroSection.tsx # URL shortening form
| |-- Urls.tsx # User URL grid
|
|-- config/
| |-- mongodb.ts # Mongoose connection helper
| |-- nextAuth.ts # NextAuth configuration
| |-- nodemailer.ts # Email transporter + OTP utilities
|
|-- Models/
| |-- user.model.ts # User schema (with bcrypt pre-save)
| |-- shortUrl.model.ts # ShortUrl schema + click history
|
|-- Providers/
| |-- sessionProvider.tsx # Client-side SessionProvider wrapper
|
|-- types/
|-- next-auth.d.ts # NextAuth type augmentation
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/auth/signups |
Register a new user. Sends verification OTP email. |
GET/POST |
/api/auth/[...nextauth] |
NextAuth handler (sign in, sign out, session) |
POST |
/api/user/verify-account |
Verify email with 6-digit OTP |
POST |
/api/user/resend-verification-email |
Resend verification OTP |
POST |
/api/user/forgot-password |
Send password reset OTP |
POST |
/api/user/reset-password |
Verify OTP and update password |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/url |
Required | Create a shortened URL |
GET |
/api/url |
Required | List current user's URLs |
DELETE |
/api/url/[id] |
Required | Delete a URL |
| Method | Endpoint | Description |
|---|---|---|
GET |
/[shortUrl] |
Redirect to original URL and log click analytics |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/admin/urls |
Admin | List all URLs across all users |
GET |
/api/admin/urls/[id] |
Admin | Full click history for a URL |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/testDB |
Test MongoDB connection health |
| Field | Type | Notes |
|---|---|---|
fullName |
String | Required |
email |
String | Required, unique |
password |
String | Required, bcrypt hashed |
type |
String | "user" or "admin" |
isVerified |
Boolean | Default false |
VerifyOtp |
String | 6-digit OTP code |
VerifyOtpExpire |
Date | 5-minute TTL |
| Field | Type | Notes |
|---|---|---|
userId |
String | Owner reference |
originalUrl |
String | Target URL |
shortUrl |
String | 8-char nanoid, unique, indexed |
customAlias |
String | Optional custom slug |
clicks |
Number | Denormalized click counter |
password |
String | Optional, excluded by default |
isActive |
Boolean | Toggle redirect on/off |
clickHistory |
Array | Embedded click event subdocuments |
Each entry in clickHistory captures:
| Field | Description |
|---|---|
timestamp |
Click time |
hashedIp |
Bcrypt-hashed visitor IP |
country, countryCode, city, region |
Geolocation |
lat, lon |
Coordinates |
browser, browserVersion |
Browser info |
os, osVersion |
Operating system |
device, deviceBrand |
Device type |
isp |
Internet service provider |
referrer, refererDomain |
Traffic source |
language, timezone |
Locale |
isBot, botName |
Bot detection |
isVpn, isProxy, threatLevel |
Security flags |
Trimly uses NextAuth.js with JWT strategy:
- Credentials Provider -- email + password login with bcrypt verification
- Google Provider -- OAuth 2.0 sign-in
- Email Verification -- registration requires OTP verification before login is allowed
- Password Reset -- OTP-based flow with 5-minute expiry
Sessions are stateless JWTs. User data (id, email, fullName, type) is embedded in the token and made available via useSession().
Vercel (recommended):
- Push to GitHub
- Import the repository in Vercel
- Configure environment variables in the Vercel dashboard
- Deploy -- Vercel auto-detects Next.js
npm run build # Production build
npm run start # Start production server locallyOther platforms:
Trimly is a standard Next.js application and can be deployed anywhere that supports Node.js (Railway, Render, AWS, etc.). Ensure the environment variables are configured for your target platform.
- Passwords are hashed with
bcryptjs(10 salt rounds) before database storage - Visitor IP addresses are bcrypt-hashed and never stored in plaintext
- API routes validate authentication and authorization before returning data
- Sensitive environment variables are server-side only and never bundled into client code
- OTP codes expire after 5 minutes
- Admin routes check
session.user.type === "admin"before granting access
Contributions are welcome. Please open an issue first to discuss what you would like to change, then submit a pull request.
git checkout -b feature/your-feature
git commit -m "Add your feature"
git push origin feature/your-featureM Abdullah -- GitHub