CodingIdeas.ai
← Browse All Ideas

Build Guide

How to Build a URL Shortener

A URL shortener with click analytics is one of the fastest paths from idea to paying customers — Bitly charges $35/month for features you can build in 2 days.

Beginner1–2 days

URL shorteners teach edge redirects, analytics aggregation, rate limiting, and custom domain routing — a surprisingly rich set of infrastructure patterns in a simple product.

Data model

The core tables you'll need before writing any UI.

Linkid, user_id, slug (unique), original_url, custom_alias, expires_at, created_at
Clickid, link_id, referrer, country, device (mobile/desktop), browser, clicked_at

Build order

The sequence that minimises rewrites — build in this order.

1

Slug generation and redirect

Generate a random 6-character slug (nanoid). Store slug → original_url in your database. Create a catch-all route /[slug] that looks up the slug and returns a 301 redirect. Test this works before anything else.

2

Click tracking middleware

Before the redirect, log a Click row: extract country from the CF-IPCountry header (Cloudflare/Vercel sets this), device from User-Agent, referrer from the Referer header. Do this asynchronously so it doesn't slow the redirect.

3

Dashboard and link management

Build a dashboard showing all your links with total clicks, creation date, and original URL. Add create and delete actions. Add an edit to update the destination URL without changing the slug.

4

Analytics per link

Add a detail page per link: total clicks, clicks over time (bar chart last 30 days), top countries (table), top referrers (table), device split (mobile vs desktop donut). Aggregate from the clicks table.

5

Custom aliases

Let users choose a custom slug (e.g. yourdomain.com/my-product) instead of a random one. Check for uniqueness. Sanitise input to alphanumeric and hyphens only.

6

Link expiry

Add an expires_at datetime to links. In the redirect handler, check if the link is expired before redirecting. Return a 410 Gone page with a friendly message if so.

7

QR code generation

Use the qrcode npm package to generate a PNG QR code for each short URL. Return it as a base64 image. Add a download button to the dashboard next to each link.

Done when

Observable behaviors that confirm V1 is complete — verify each one before you ship.

  • Short URL redirects in under 50ms (measurable in browser DevTools Network tab)

  • Analytics dashboard shows country, device, and referrer breakdown per link

  • Custom alias rejects duplicates with a clear error — no silent overwrite

  • Expired links return a friendly 410 page, not a generic 404

  • Dashboard opens with 3 example short links — never an empty table on first login

First Run Requirement

Seed 3 demo short links on first login pointing to popular docs pages. Dashboard is never empty — user can see analytics layout immediately without creating links first.

Build it with AI — Architect Prompt

Paste this into Claude, Cursor, Windsurf, or any AI coding tool. It includes the full context of this guide — data model, build order, done conditions, and pitfalls — so the AI starts with everything it needs.

ClaudeCursorWindsurfCopilotGemini
architect-prompt.txt
<context>
App: URL Shortener
Difficulty: Beginner
Estimated build time: 1–2 days
Tech stack (intermediate): Next.js + Supabase + Vercel Edge Middleware for sub-ms redirects + Clerk for teams

Data model:
  Link: id, user_id, slug (unique), original_url, custom_alias, expires_at, created_at
  Click: id, link_id, referrer, country, device (mobile/desktop), browser, clicked_at

FIRST RUN REQUIREMENT:
Seed 3 demo short links on first login pointing to popular docs pages. Dashboard is never empty — user can see analytics layout immediately without creating links first.

DONE WHEN — verify each before marking V1 complete:
  □ Short URL redirects in under 50ms (measurable in browser DevTools Network tab)
  □ Analytics dashboard shows country, device, and referrer breakdown per link
  □ Custom alias rejects duplicates with a clear error — no silent overwrite
  □ Expired links return a friendly 410 page, not a generic 404
  □ Dashboard opens with 3 example short links — never an empty table on first login

Recommended build order:
  1. Define API contract + schema + seed data (always first)
  2. Slug generation and redirect — Generate a random 6-character slug (nanoid). Store slug → original_url in your database. Create a catch-all route /[slug] that looks up the slug and returns a 301 redirect. Test this works before anything else.
  3. Click tracking middleware — Before the redirect, log a Click row: extract country from the CF-IPCountry header (Cloudflare/Vercel sets this), device from User-Agent, referrer from the Referer header. Do this asynchronously so it doesn't slow the redirect.
  4. Dashboard and link management — Build a dashboard showing all your links with total clicks, creation date, and original URL. Add create and delete actions. Add an edit to update the destination URL without changing the slug.
  5. Analytics per link — Add a detail page per link: total clicks, clicks over time (bar chart last 30 days), top countries (table), top referrers (table), device split (mobile vs desktop donut). Aggregate from the clicks table.
  6. Custom aliases — Let users choose a custom slug (e.g. yourdomain.com/my-product) instead of a random one. Check for uniqueness. Sanitise input to alphanumeric and hyphens only.
  7. Link expiry — Add an expires_at datetime to links. In the redirect handler, check if the link is expired before redirecting. Return a 410 Gone page with a friendly message if so.
  8. QR code generation — Use the qrcode npm package to generate a PNG QR code for each short URL. Return it as a base64 image. Add a download button to the dashboard next to each link.
  9. End-to-end verification — walk every Done When condition above (always last)

Known pitfalls to avoid:
  - Using auto-increment IDs as slugs — they're sequential and predictable. Users can enumerate all links by guessing IDs. Use nanoid() to generate random slugs instead.
  - Logging clicks synchronously before the redirect — this adds latency to every redirect. Use waitUntil() (Vercel Edge) or a background job to log asynchronously while the redirect fires immediately.
  - Not handling slug collisions on custom aliases — two users might want the same alias. Always check for uniqueness before saving and return a clear error if the alias is taken.
</context>

<role>
You are a Senior Full-Stack Engineer and product architect who has shipped production URL Shorteners before. You know exactly where developers get stuck and how to structure the project to avoid rewrites.
</role>

<task id="step-1-clarify">
Before writing any code or spec, ask me 3–5 clarifying questions that will meaningfully change the architecture. Focus on: scale expectations, auth requirements, platform (web / mobile / both), must-have vs nice-to-have features for the MVP, and any hard constraints (budget, deadline, existing infrastructure).

<example>
BAD: "What tech stack do you want to use?" — too broad, doesn't change architecture decisions.
GOOD: "Do you need real-time sync across devices, or is single-device with periodic refresh acceptable? This decides whether we use WebSockets or simple REST polling and significantly affects infrastructure complexity."
</example>

⚠️ Do NOT start planning or writing code until I answer. Present the questions, then stop and wait.
</task>

<task id="step-2-architect">
After I answer your questions, produce the following in order:

1. TECHNICAL SPECIFICATION
   - System architecture using → to show data and event flow
   - Core data model (refer to data model in <context>)
   - API contract — list ALL routes with request/response shapes BEFORE any implementation
     WHY: agreeing on contracts first prevents rewrites when frontend and backend shapes diverge
   - External APIs and integration points

2. MVP PLAN in three phases:

   SETUP   ✦ Step 1 (always): API contract + schema migration + seed data
     Done when: schema is migrated, seed script runs, app starts with demo data, zero manual setup.

   CORE FEATURES — build in this exact order:
   2. Slug generation and redirect
   3. Click tracking middleware
   4. Dashboard and link management
   5. Analytics per link
   6. Custom aliases
   7. Link expiry
   8. QR code generation

   DONE WHEN — one observable condition per feature:
   □ Short URL redirects in under 50ms (measurable in browser DevTools Network tab)
   □ Analytics dashboard shows country, device, and referrer breakdown per link
   □ Custom alias rejects duplicates with a clear error — no silent overwrite
   □ Expired links return a friendly 410 page, not a generic 404
   □ Dashboard opens with 3 example short links — never an empty table on first login

   STRETCH GOALS — post-launch additions that are explicitly out of V1 scope.

3. BLOCKER ANALYSIS
   Flag: API rate limits, auth complexity, scope risks, cold-start problems, top 2–3 failure modes.

   FIRST RUN REQUIREMENT: Seed 3 demo short links on first login pointing to popular docs pages. Dashboard is never empty — user can see analytics layout immediately without creating links first.
   ✓ Done when: app launches with seed data and the full user journey works without any manual setup.

<self_check>
Before presenting your output, verify:
□ Every answer I gave to clarifying questions is reflected in the spec
□ Step 1 is ALWAYS: API contract + schema + seed — never UI first
□ Done When criteria are observable user behaviors, not internal states
□ The plan is realistic for one person to ship within 1–2 days
□ All known pitfalls from <context> are addressed in the spec
</self_check>
</task>

<task id="step-2.5-agents-md">
Generate an AGENTS.md file at the project root. This file is read automatically by Claude Code, Cursor, Windsurf, and all major AI coding tools at the start of every session.

Include:
- Project overview (2–3 sentences)
- Tech stack with exact version numbers
- Folder structure with one-line descriptions per directory
- Key architectural decisions and the WHY behind each
- Coding conventions: camelCase components, kebab-case files, max 200 lines per file, one concern per file
- Available commands: dev, build, test, lint, db:migrate, db:seed
- MVP scope boundaries — features explicitly out of V1

Note in the file: "Cursor users: symlink .cursorrules → AGENTS.md. Claude Code users: symlink CLAUDE.md → AGENTS.md."
</task>

<task id="step-3-implement">
Implement the full project in the exact order from step 2. For each step:
- Write complete code (no placeholders or TODOs)
- Confirm the step works before moving to the next

<constraints>
- Step 1 is ALWAYS: define all API routes + TypeScript request/response interfaces + run schema migration
  WHY: agreeing on contracts before writing a single component prevents shape mismatches that require rewrites
- Final step is ALWAYS: start the app and walk every Done When condition from <context> end-to-end
  WHY: a spec that passes unit tests but breaks the core user journey is not done
- Max 200 lines per file. WHY: every file must fit in one AI context window for complete reasoning
- One concern per file. WHY: mixing auth logic into API routes makes it impossible to reuse — extract to lib/auth.ts
- TypeScript strict mode, no `any` types. WHY: catches data shape mismatches at compile time, not in production
- All database queries in server components or API routes only. WHY: keeps credentials server-side
- All environment variables documented in .env.example. WHY: next developer sets up in under 5 minutes
</constraints>
</task>

How to use this prompt

  1. 1.Copy the prompt above
  2. 2.Open Claude, Cursor (Cmd+L), or Windsurf
  3. 3.Paste the prompt and send — the AI will ask 3–5 clarifying questions
  4. 4.Answer the questions, then it generates your full project spec
  5. 5.Continue in the same session to implement step by step

Common mistakes

What trips up most developers building this for the first time.

⚠️

Using auto-increment IDs as slugs — they're sequential and predictable. Users can enumerate all links by guessing IDs. Use nanoid() to generate random slugs instead.

⚠️

Logging clicks synchronously before the redirect — this adds latency to every redirect. Use waitUntil() (Vercel Edge) or a background job to log asynchronously while the redirect fires immediately.

⚠️

Not handling slug collisions on custom aliases — two users might want the same alias. Always check for uniqueness before saving and return a clear error if the alias is taken.

Recommended tech stack

Pick the level that matches your experience.

Beginner

Next.js API routes + Supabase — complete in a day, deploy to Vercel for edge redirects

Intermediate

Next.js + Supabase + Vercel Edge Middleware for sub-ms redirects + Clerk for teams

Advanced

Next.js + PlanetScale + Cloudflare Workers for global edge redirects + custom domain CNAME support

Take it further — related ideas

Each comes with revenue math, a full build guide, and a prompt to paste into Claude or Cursor.