Skip to content

Build URL Shortener with Analytics Dashboard — Next.js 15 #2

@ibuzzardo

Description

@ibuzzardo

Overview

Build a URL shortener API with a simple analytics dashboard using Next.js 15 (App Router). Users can shorten URLs, get redirected, and view click counts. Dark-themed UI on port 4004.

Requirements

  • Next.js 15 App Router with TypeScript strict mode
  • Server runs on port 4004 (configurable via PORT env var)
  • In-memory Map storage for shortened URLs (no database)
  • API routes:
    • POST /api/shorten — accepts { url: string }, validates URL format, generates a 6-char alphanumeric code, stores mapping, returns { shortCode, shortUrl, originalUrl }
    • GET /api/[code] — looks up code, increments click count, returns 302 redirect to original URL. Returns 404 JSON if code not found.
    • GET /api/stats — returns JSON array of all shortened URLs with click counts: [{ shortCode, originalUrl, clicks, createdAt }]
  • Dashboard page at / with:
    • Form to paste a URL and get a shortened link
    • Table showing all shortened URLs with click counts
    • Copy-to-clipboard button for each short URL
    • Auto-refreshes stats every 10 seconds
  • Input validation: reject invalid URLs (must start with http:// or https://)
  • Dark theme: background #0B1120, cards #111827, borders #374151, text #F9FAFB/#9CA3AF, accent #3B82F6
  • Font: Inter via next/font/google
  • Responsive layout

File Structure

package.json
tsconfig.json
next.config.js
tailwind.config.ts
postcss.config.js
src/lib/store.ts               # In-memory Map: code -> { originalUrl, clicks, createdAt }
src/lib/types.ts               # TypeScript interfaces: ShortenRequest, ShortenResponse, LinkStats
src/lib/utils.ts               # generateCode() helper, URL validation
src/app/globals.css            # Tailwind directives + dark theme
src/app/layout.tsx             # Root layout with Inter font, dark class, metadata
src/app/page.tsx               # Client component: form + stats table + auto-refresh
src/app/api/shorten/route.ts   # POST handler: validate URL, generate code, store, return response
src/app/api/[code]/route.ts    # GET handler: lookup code, increment clicks, 302 redirect or 404
src/app/api/stats/route.ts     # GET handler: return all links with click counts

Dependencies

  • next@^15.0.0 — React framework with App Router
  • react@^18.0.0 — UI library
  • react-dom@^18.0.0 — React DOM renderer
  • tailwindcss@^3.3.5 — Utility CSS framework
  • autoprefixer@^10.4.16 — PostCSS plugin for Tailwind
  • postcss@^8.4.31 — CSS processing
  • typescript@^5.0.0 — Type safety
  • @types/node@^20.0.0 — Node.js type definitions
  • @types/react@^18.0.0 — React type definitions
  • @types/react-dom@^18.0.0 — React DOM type definitions

Design Specification

  • Background: #0B1120
  • Card background: #111827
  • Card border: #374151
  • Primary text: #F9FAFB
  • Secondary text: #9CA3AF
  • Accent/links: #3B82F6
  • Success: #22C55E
  • Error: #EF4444
  • Font: Inter (via next/font/google)
  • Table style: striped rows with border-b border-[#374151]

Acceptance Criteria

  • npm install && npm run build completes without errors
  • npm start serves the app on port 4004
  • POST /api/shorten with valid URL returns { shortCode, shortUrl, originalUrl }
  • POST /api/shorten with invalid URL returns 400 with error message
  • GET /api/[code] with valid code returns 302 redirect
  • GET /api/[code] with invalid code returns 404 JSON
  • GET /api/stats returns array of all links with click counts
  • Dashboard form creates shortened URLs
  • Dashboard table shows all links with click counts
  • Dashboard auto-refreshes every 10 seconds
  • Dark theme renders correctly
  • TypeScript compiles with strict: true

Edge Cases

  • Invalid URL (no protocol) -> 400 with descriptive error
  • Empty URL -> 400
  • Duplicate URL shortened twice -> returns different codes (not deduplicated)
  • Non-existent short code -> 404 JSON response
  • Very long URLs (>2000 chars) -> accept but truncate display in table
  • Special characters in URL -> handle correctly with encodeURIComponent

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions