javascript 43 lines · 8 steps

A deduped page-view counter in Next.js

An edge route that counts unique page views by using a short-lived Redis key to dedupe repeat visitors.

Explained by highlit
1import { NextResponse } from 'next/server';
2import { Redis } from '@upstash/redis';
3 
4export const runtime = 'edge';
5 
6const redis = Redis.fromEnv();
7 
8export async function POST(request) {
9 const { slug } = await request.json();
10 
11 if (typeof slug !== 'string' || !/^[a-z0-9\-\/]+$/.test(slug)) {
12 return NextResponse.json({ error: 'Invalid slug' }, { status: 400 });
13 }
14 
15 const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? '0.0.0.0';
16 const dedupeKey = `dedupe:${slug}:${ip}`;
17 
18 const isNew = await redis.set(dedupeKey, '1', { nx: true, ex: 60 * 60 * 24 });
19 
20 const views = isNew
21 ? await redis.incr(`views:${slug}`)
22 : await redis.get(`views:${slug}`) ?? 0;
23 
24 return NextResponse.json(
25 { slug, views: Number(views), counted: Boolean(isNew) },
26 { headers: { 'Cache-Control': 'no-store' } }
27 );
28}
29 
30export async function GET(request) {
31 const slug = new URL(request.url).searchParams.get('slug');
32 
33 if (!slug) {
34 return NextResponse.json({ error: 'Missing slug' }, { status: 400 });
35 }
36 
37 const views = await redis.get(`views:${slug}`);
38 
39 return NextResponse.json(
40 { slug, views: Number(views ?? 0) },
41 { headers: { 'Cache-Control': 's-maxage=30, stale-while-revalidate=60' } }
42 );
43}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A Redis SET with NX and an expiry is an atomic way to check-and-claim a key, perfect for time-windowed deduplication.
  2. 2Validating and constraining user input before it becomes a database key prevents injection and key pollution.
  3. 3Cache-Control headers should match intent: no-store for writes, stale-while-revalidate for cheap public reads.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A deduped page-view counter in Next.js — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code