Build a URL Shortener
Build a URL shortener with custom slugs, click analytics, QR code generation, and an API for programmatic link creation.
URL shortener architecture
A URL shortener is deceptively simple on the surface — take a long URL, give back a short one, redirect when visited. But a production-quality shortener involves unique ID generation, fast redirects, analytics tracking, and abuse prevention. The core data model has one main entity: a Link. Ask Claude Code: Create a Next.js project with TypeScript and Tailwind for a URL shortener. Set up a PostgreSQL database with Prisma ORM. Define the schema with a Link model: id (auto-increment), slug (unique string, 6 to 20 characters), originalUrl (the destination URL, up to 2048 characters), createdAt (timestamp), expiresAt (optional timestamp), clickCount (integer, default 0), creatorIp (for rate limiting), customAlias (boolean, whether the user chose the slug), and isActive (boolean, default true for enabling/disabling links). Also create a Click model for analytics: id, linkId (foreign key), clickedAt (timestamp), referrer (where the click came from), userAgent (browser info), country (derived from IP), device (desktop, mobile, tablet, derived from user agent), and browser (Chrome, Firefox, Safari, derived from user agent). Run: npx prisma migrate dev --name init to create the database tables. Ask Claude Code: Create a slug generation function at src/lib/slug.ts. Generate random 6-character slugs using a URL-safe alphabet (a-z, A-Z, 0-9). Check for collisions against the database — if the generated slug already exists, generate a new one. The probability of collision with 62 characters to the power of 6 is roughly 1 in 56 billion, so collisions are rare but must be handled. Avoid slugs that spell offensive words by checking against a blocklist.
Creating and redirecting short links
The two core operations are creating a link and redirecting. Ask Claude Code: Create an API route at src/app/api/links/route.ts for link creation. The POST handler accepts a JSON body with url (required) and customSlug (optional). Validate the URL: it must be a valid URL with http or https protocol, it must not point to your own domain (prevents redirect loops), and it must not be on a blocklist of known malicious domains. If a customSlug is provided, validate it: 3 to 20 characters, alphanumeric plus hyphens only, not already taken, and not on the reserved list (api, admin, dashboard, login, signup). If no custom slug, generate a random one. Return the created link with the short URL. Ask Claude Code: Create the redirect handler at src/app/[slug]/route.ts. This is the most performance-critical endpoint — every click hits it. Look up the slug in the database. If found and active: record the click asynchronously (do not block the redirect), return a 301 redirect to the original URL. If not found: return a 404 page. If expired: return a gone page explaining the link has expired. The click recording should be non-blocking. Ask Claude Code: Use a fire-and-forget approach for analytics. After sending the redirect response, parse the user agent to extract device and browser, extract the country from a geolocation service or header (Vercel provides this in the x-vercel-ip-country header), and insert the Click record. If the analytics insert fails, log the error but do not affect the redirect. Build the link creation UI. Ask Claude Code: Create a homepage with a large URL input field, an optional Custom Slug input, an optional Expiry Date picker, and a Create Short Link button. After creation, show the short URL with a Copy to Clipboard button, a QR code for the link, and quick share buttons for Twitter and email. Common error: using 302 (temporary redirect) instead of 301 (permanent redirect). Use 301 for SEO benefit — search engines transfer link equity to the destination. Use 302 only if you want to be able to change the destination later.
Click analytics dashboard
Analytics transform a simple redirect tool into a marketing asset. Ask Claude Code: Create a dashboard at src/app/dashboard/page.tsx that shows all created links with their analytics. The main table lists each link with: the short URL, the original URL (truncated with hover to see full), click count, creation date, expiry status, and active/inactive toggle. Click a row to expand detailed analytics. Ask Claude Code: Build a link analytics detail view showing: a click timeline chart (clicks per day for the last 30 days, as a line chart), referrer breakdown (where clicks came from — direct, social media, email, other), geographic distribution (top countries as a bar chart), device breakdown (desktop vs mobile vs tablet as a pie chart), browser breakdown (Chrome, Safari, Firefox, Edge as a bar chart), and top referrer URLs as a table. Add date range filtering. Ask Claude Code: Create a date picker that filters all analytics to a custom range. Preset options: last 7 days, last 30 days, last 90 days, all time. When the date range changes, all charts and metrics update. Add bulk analytics. Ask Claude Code: Create a dashboard overview page showing aggregate metrics across all links: total links created, total clicks across all links, average click-through rate, most popular link (by total clicks), most active day (by total clicks across all links), and geographic heatmap showing clicks by country. Export analytics data. Ask Claude Code: Add an Export CSV button that downloads the click data for a selected link or all links. Include columns for timestamp, short URL, original URL, referrer, country, device, and browser. This data is valuable for marketing analysis in spreadsheets. Common error: analytics queries can be slow on large datasets. Add database indexes on the Click table for linkId, clickedAt, and country. Use aggregate queries instead of fetching all rows and counting in JavaScript.
QR code generation and customisation
QR codes make short links useful in the physical world — on business cards, posters, product packaging, and presentations. Ask Claude Code: Create a QR code generation system at src/lib/qr.ts. Use the qrcode package (npm install qrcode @types/qrcode) to generate QR codes for each short URL. Generate in multiple formats: SVG (scalable, best for web), PNG (for downloads and embedding), and base64 data URL (for inline display). Every link should have a QR code auto-generated on creation. Ask Claude Code: Add QR code customisation options. Let users choose: size (128, 256, 512, 1024 pixels), error correction level (L for 7 percent, M for 15 percent, Q for 25 percent, H for 30 percent — higher levels make the QR code work even when partially obscured), foreground colour (default black), background colour (default white), and an optional centre logo (upload an image that is placed in the middle of the QR code — requires H error correction to compensate for the obscured area). Build the QR code UI. Ask Claude Code: On the link detail page, show a preview of the QR code with customisation controls. Below the preview, show download buttons for SVG and PNG formats. Add a Print button that opens a print-optimised page with just the QR code and the short URL text below it — useful for creating physical materials. Add a batch QR generation feature. Ask Claude Code: Create a Batch QR page where users paste multiple URLs (one per line), generate short links for all of them, and download a ZIP file containing a QR code image for each link. Name each file with the slug. This is valuable for event organisers, product managers, and marketers who need many QR codes at once. Common error: QR codes with a centre logo need testing. Generate the QR code, add the logo, and then scan it with your phone camera to verify it still works. If the logo is too large, the QR code becomes unscannable.
API access and developer features
A URL shortener becomes far more valuable with an API. Developers can programmatically create links from their own applications. Ask Claude Code: Create a REST API at src/app/api/v1/ with proper versioning. Endpoints: POST /api/v1/links (create a link), GET /api/v1/links (list all links for the authenticated user with pagination), GET /api/v1/links/:slug (get link details including analytics summary), PATCH /api/v1/links/:slug (update link — change destination, toggle active, set expiry), and DELETE /api/v1/links/:slug (soft delete — set isActive to false). Add API key authentication. Ask Claude Code: Create an API key system. Users generate API keys from the dashboard (Settings page). Each key is a random 32-character string prefixed with esk_ (Enigmatica short key). Store the hashed key in the database (never store API keys in plain text — hash them like passwords). Validate the key on every API request by checking the Authorization: Bearer esk_... header. Add rate limiting. Ask Claude Code: Implement rate limiting per API key. Free users get 100 link creations per day and 1000 analytics queries per day. Return standard rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. When the limit is exceeded, return 429 Too Many Requests with a Retry-After header. Generate API documentation. Ask Claude Code: Create an API documentation page at src/app/docs/page.tsx. Show each endpoint with its method, URL, request body (as JSON with comments), response body (as JSON with comments), and example curl commands. Include authentication instructions, rate limit details, and error code reference. Common error: API versioning is easy to skip but painful to add later. Start with /api/v1/ from day one so you can introduce breaking changes in /api/v2/ without affecting existing integrations.
Security, abuse prevention, and deployment
URL shorteners are common targets for abuse — phishing, malware distribution, and spam. Ask Claude Code: Implement security measures. URL scanning: before creating a link, check the destination URL against Google's Safe Browsing API. Reject URLs flagged as phishing or malware. If the API is unavailable, allow the link but flag it for manual review. Rate limiting on creation: maximum 50 links per IP per day for anonymous users, 200 per day for authenticated users. This prevents automated spam link generation. Link reporting: add a Report This Link button on the redirect interstitial page. When reported, flag the link for review and increment a report counter. Auto-disable links with 3 or more reports. Ask Claude Code: Create an admin page at /admin that shows flagged links, reported links, and links with suspicious patterns (very high click rates, links created in bursts from the same IP). Add approve and disable buttons for each flagged link. Add click fraud prevention. Ask Claude Code: Implement bot detection in the click tracking. Check the user agent against known bot signatures. Filter out repeated clicks from the same IP within 10 seconds (likely double-clicks or page refreshes). Store raw click data but calculate deduplicated metrics separately. Show both raw and unique clicks in the analytics dashboard. Deploy to Vercel with a PostgreSQL database on Neon or Supabase. Ask Claude Code: Configure the project for production deployment. Set up the database connection string as an environment variable. Configure Prisma for production (use connection pooling). Set up a custom domain — the shorter the domain, the better the shortened URLs. Test the complete flow: create a link, visit the short URL, verify the redirect and that analytics are recorded. Test with high concurrency: use a load testing tool to simulate 100 concurrent redirects and verify the system handles them without errors or significant latency increase.
Building APIs
This guide is hands-on and practical. The full curriculum covers the conceptual foundations in depth with structured lessons and quizzes.
Go to lesson