Adicionando contador de visitas em seu blog

Neste exemplo mostrarei como criar um contador de visitas com Vercel Edge Functions e Redis API.

201 views


Já que não utilizo mais Google Analytics, decidi criar um contador de visitas para os posts do meu blog.

Decidi utilizar as Edge Functions da Vercel com uma database Redis para essa funcionalidade, e no blog da Upstash, há um tutorial para criar essa funcionalidade. Porém, o código não está atualizado com as últimas atualizações do Next.js, e além disso, encontrei alguns bugs na contagem de visitas únicas (ou quase isso).

Depois de algumas alterações, consegui fazer o contador funcionar corretamente. Você pode ler o post original que conta porquê utilizar Redis, e usar o código atualizado abaixo 😎

Após adicionar a URL e o TOKEN de sua database Redis no seu .env, adicione a biblioteca da Upstash ao seu projeto, junto com a biblioteca edge da Vercel:

pnpm add @upstash/redis @vercel/edge

Depois, vamos criar nosso Route Handler para registrar as visitas de cada post. No meu caso, criei no diretório /api/views/route.ts:

import { NextResponse } from 'next/server';

import { Redis } from '@upstash/redis';
import { ipAddress } from '@vercel/edge';

export const runtime = 'edge';

const redis = Redis.fromEnv();

export async function POST(request: Request) {
if (request.headers.get('Content-Type') !== 'application/json')
return new Response('must be json', { status: 400 });

const body = await request.json();

let slug: string | undefined = undefined;

if ('slug' in body) slug = body.slug;

if (!slug) return new NextResponse('Slug not found', { status: 400 });

const ip = ipAddress(request) || undefined;

if (ip) {
const buf = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(ip),
);
const hash = Array.from(new Uint8Array(buf))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');

const alreadySeen = await redis.exists(
['deduplicate', hash, slug].join(':'),
);

if (alreadySeen) return new NextResponse(null, { status: 202 });

await redis.set(['deduplicate', hash, slug].join(':'), true, {
nx: true,
ex: 24 * 60 * 60,
});

await redis.incr(['pageviews', 'posts', slug].join(':'));
}

return new NextResponse(null, { status: 202 });
}

A lógica é bastante simples, basicamente pegamos o IP do usuário através da function ipAddress da Vercel, criptografamos ele e criamos uma hash para verificarmos se para a slug enviada já existe algum registro, através do método EXISTS do Redis. Caso positivo, retornamos um NextResponse sem incrementar o número de views.

Caso negativo, fazemos um SET com a mesma hash anterior para registrar a visita do usuário, com validade de 24 horas, e em seguida, incrementamos a contagem de visualizações do post através do método INCR.

Após isso, vamos criar o componente responsável por fazer uma requisição à nossa API dentro do post do nosso blog. Em meu caso, criei o arquivo no diretório: app/post/[slug]/:

'use client';

import { useCallback, useEffect } from 'react';

export const ReportView: React.FC<{ slug: string }> = ({ slug }) => {
const registerView = useCallback(async () => {
await fetch('/api/views', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ slug }),
});
}, [slug]);

useEffect(() => {
registerView();
}, [registerView]);

return null;
};

Depois inserimos esse componente em nossa página, junto com uma prop slug, que será o slug/identificador único da sua página. No meu caso, a slug vem dos parâmetros da URL:

<ReportView slug={params.slug} />

Por fim, podemos buscar a quantidade de views através da requisição abaixo:

const views =
(await redis.get<number>(['pageviews', 'posts', params.slug].join(':'))) ??
0;

E é isso! Com esse código você já consegue criar um contador de visitas em sua aplicação criada com Next.js e hospedada na Vercel 🤘🏼