Skip to main content

Overview

shrtnr automatically tracks every click on your short links, recording click counts and detailed analytics data. View total clicks in your dashboard and analyze traffic patterns to understand how your links perform.
Analytics are available for all links, but only authenticated users can view their analytics in the dashboard.

What Gets Tracked

Every time someone clicks a short link, shrtnr records:

Click Counter

  • Total clicks: Incremented for each visit
  • Displayed in dashboard: Shows total lifetime clicks per link
  • Real-time updates: Click count updates immediately

Detailed Analytics

Each click stores:
  • IP Address: Visitor’s IP (from x-forwarded-for or x-real-ip headers)
  • User Agent: Browser and device information
  • Referrer: Where the visitor came from (if available)
  • Timestamp: When the click occurred (automatic)
// From app/lib/links.ts:88-109
export async function recordClick(
  shortCode: string,
  request?: { headers?: Headers }
): Promise<void> {
  try {
    await db`UPDATE urls SET clicks = clicks + 1 WHERE short_code = ${shortCode}`;

    const headers = request?.headers;
    const ip = headers?.get('x-forwarded-for')?.split(',')[0]?.trim()
      ?? headers?.get('x-real-ip')?.trim()
      ?? null;
    const userAgent = headers?.get('user-agent') ?? null;
    const referrer = headers?.get('referer') ?? headers?.get('referrer') ?? null;

    await db`
      INSERT INTO clicks (short_code, ip_address, user_agent, referrer)
      VALUES (${shortCode}, ${ip}, ${userAgent}, ${referrer})
    `;
  } catch (err) {
    console.error('recordClick error:', err);
  }
}
Click recording happens asynchronously and won’t delay redirects. Even if analytics fail to record, the redirect still works.

Click Tracking Flow

When someone visits a short link:
1

User clicks short link

A visitor navigates to yoursite.com/abc123.
2

Lookup original URL

shrtnr finds the original URL from cache or database.
3

Record analytics

Click count increments and detailed analytics are saved to the clicks table.
4

Redirect user

The visitor is immediately redirected to the original URL (307 redirect).
// From app/[shortCode]/route.ts:16-23
const originalUrl = await getOriginalUrl(shortCode);
if (!originalUrl) {
  return new NextResponse('Not found', { status: 404 });
}

await recordClick(shortCode, request);

return NextResponse.redirect(originalUrl, 307);

Viewing Analytics in Dashboard

Authenticated users can view click counts for all their links:

Desktop Table View

The Clicks column shows total clicks with monospace formatting:
// From app/dashboard/page.tsx:221-223
<TableCell>
  <span className="tabular-nums font-medium">{link.clicks}</span>
</TableCell>

Mobile Card View

Clicks appear in the link details below the URL:
// From app/dashboard/page.tsx:276
<span className="tabular-nums">{link.clicks} clicks</span>
The dashboard automatically refreshes when you edit or delete links, ensuring click counts are always current.

Analytics Data Points

IP Address

The visitor’s IP address is extracted from request headers:
  1. x-forwarded-for: If behind a proxy, take the first IP in the chain
  2. x-real-ip: Fallback header for reverse proxies
  3. null: If no IP headers are present
// From app/lib/links.ts:96-98
const ip = headers?.get('x-forwarded-for')?.split(',')[0]?.trim()
  ?? headers?.get('x-real-ip')?.trim()
  ?? null;
IP addresses are stored for analytics but are not displayed in the current dashboard UI. Future updates may include geographic analysis.

User Agent

The browser’s user agent string reveals:
  • Browser type and version (Chrome, Firefox, Safari, etc.)
  • Operating system (Windows, macOS, iOS, Android)
  • Device type (desktop, mobile, tablet)
// From app/lib/links.ts:99
const userAgent = headers?.get('user-agent') ?? null;
Example user agent:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36

Referrer

The referrer header shows where visitors came from:
  • Social media: https://twitter.com/, https://facebook.com/
  • Search engines: https://www.google.com/search?q=...
  • Direct: null (typed URL, bookmarked, or referrer blocked)
  • Email: Often null due to privacy protections
// From app/lib/links.ts:100
const referrer = headers?.get('referer') ?? headers?.get('referrer') ?? null;
The HTTP header is spelled “referer” (misspelled in the HTTP spec), but some frameworks use “referrer” - shrtnr checks both.

Database Schema

URLs Table

Click counts are stored in the main urls table:
CREATE TABLE urls (
  id SERIAL PRIMARY KEY,
  short_code VARCHAR(20) UNIQUE NOT NULL,
  original_url TEXT NOT NULL,
  clicks INTEGER DEFAULT 0,
  -- other columns...
);

Clicks Table

Detailed analytics are stored separately:
CREATE TABLE clicks (
  id SERIAL PRIMARY KEY,
  short_code VARCHAR(20) NOT NULL,
  ip_address VARCHAR(45),
  user_agent TEXT,
  referrer TEXT,
  created_at TIMESTAMP DEFAULT NOW()
);
Storing clicks in a separate table allows for detailed historical analysis without bloating the main URLs table.

Click Count Updates

The click counter uses an atomic increment:
// From app/lib/links.ts:93
await db`UPDATE urls SET clicks = clicks + 1 WHERE short_code = ${shortCode}`;
Benefits of atomic increment:
  • No race conditions: Multiple simultaneous clicks increment correctly
  • No read-modify-write: Direct SQL increment is faster and safer
  • Accurate counts: Even under high traffic, counts remain accurate

Privacy Considerations

shrtnr respects visitor privacy while providing useful analytics:
  • IP addresses: Stored but not displayed; useful for fraud detection
  • No cookies: shrtnr doesn’t set tracking cookies
  • No fingerprinting: Only standard HTTP headers are used
  • Anonymous clicks: Individual clicks aren’t linked to user accounts
Depending on your jurisdiction, storing IP addresses may require privacy disclosures. Consult local privacy laws (GDPR, CCPA, etc.).

Error Handling

Click recording is designed to never break redirects:
// From app/lib/links.ts:106-108
catch (err) {
  console.error('recordClick error:', err);
}
  • Non-blocking: Analytics failures are caught and logged
  • Redirect continues: Even if click recording fails, users still get redirected
  • Silent failure: Users never see analytics errors

Current Limitations

The current analytics implementation has some limitations:
FeatureStatusNotes
Total click count✅ AvailableShown in dashboard
Click history❌ Not accessibleStored in DB but no UI
Geographic data❌ Not availableIP stored but not analyzed
Device breakdown❌ Not availableUser agent stored but not parsed
Referrer analysis❌ Not availableReferrer stored but not displayed
Time-series data❌ Not availableTimestamps stored but no charts
Export to CSV❌ Not availableNo export feature yet
Future updates may include detailed analytics dashboards with charts, geographic maps, and device breakdowns.

Best Practices

Analytics Tips

  • Monitor popular links: Check which links get the most clicks
  • Track campaigns: Compare clicks across different custom slugs
  • Watch for anomalies: Sudden spikes might indicate viral content or abuse
  • Use custom slugs: Easier to identify links in your dashboard analytics
  • Check before expiry: Review performance before links expire

Use Cases

Marketing Campaigns

Track which campaigns drive the most traffic:
Link: /spring-sale → 1,247 clicks
Link: /summer-sale → 892 clicks
Link: /fall-sale → 1,534 clicks

Social Media Performance

Compare engagement across platforms:
Link: /instagram-bio → 3,421 clicks
Link: /twitter-thread → 567 clicks
Link: /linkedin-post → 234 clicks

Content Distribution

See which content resonates:
Link: /blog-post-1 → 89 clicks
Link: /blog-post-2 → 456 clicks
Link: /whitepaper → 167 clicks

Future Enhancements

Planned analytics features:
  • Click timeline: Visualize clicks over time with charts
  • Geographic distribution: Map showing where clicks come from
  • Device breakdown: Pie chart of desktop vs mobile vs tablet
  • Referrer sources: Table of top traffic sources
  • Export functionality: Download analytics as CSV or JSON
  • Real-time dashboard: Live click notifications and counters
Interested in advanced analytics? Consider integrating with tools like Google Analytics or Plausible by adding tracking parameters to your original URLs.

Build docs developers (and LLMs) love