Next.js Sitenize Sayfa Görüntüleme Sayısı Ekleyelim


yazıldı

·

3 dakika okuma

Next.js'ye geçtiğimden beri imkanlar artınca diğer sitelerde gördüğüm her şeyi yapma gayretine düştüm. Sitemde yazı listesinde ve yazıların içinde göreceğiniz gibi artık sayfa görüntüleme sayıları var ve birileri yazıyı her okuduğunda birer birer artıyor. Böylelikle herhangi bir analiz servisi kullanma gereği kalmıyor.

Sayfa Görüntüleme Sayısı

Sayfa görüntülemelerinde veritabanı olarak Supabase kullanıyorum. Yazı her okunduğunda Supabase'e bir istek gönderip fonksiyon çalıştırıyorum ve böylelikle eski görüntüleme sayısının üzerine +1 eklenmiş oluyor.

Hazırlık Aşaması

Öncelikle Supabase üzerinden bir hesap oluşturuyoruz. Daha sonra New Project ile yeni bir proje oluşturarak Table Editor yan sekmesinden New Table butonuna basarak yeni bir tablo oluşturuyoruz. Tabloyu aşağıdaki şekilde dolduruyoruz.

supabase-table-editor

Daha sonra SQL Editor yan sekmesinde New query butonuna basarak aşağıdaki kodu kopyala yapıştır yaparak yeni bir fonksiyon oluşturuyoruz.

CREATE OR REPLACE FUNCTION increment_views(page_slug TEXT)
RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
    IF EXISTS (SELECT FROM analytics WHERE slug=page_slug) THEN
        UPDATE analytics
        SET views = views + 1,
            updated_at = now()
        WHERE slug = page_slug;
    ELSE
        INSERT into analytics(slug) VALUES (page_slug);
    END IF;
END;
$$;
CREATE OR REPLACE FUNCTION increment_views(page_slug TEXT)
RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
    IF EXISTS (SELECT FROM analytics WHERE slug=page_slug) THEN
        UPDATE analytics
        SET views = views + 1,
            updated_at = now()
        WHERE slug = page_slug;
    ELSE
        INSERT into analytics(slug) VALUES (page_slug);
    END IF;
END;
$$;

Bu fonksiyon her çalıştığında biraz önce oluştuduğumuz tabloya sayfanın belirteci, sayfanın görüntüleme sayısı ve zamanı eklenmiş olacak. Eğer daha önce tabloya eklenmişse de görüntüleme sayısını +1 artıracak.

Sonrasında API bilgilerini almamız gerekiyor. Project Settings yan sekmesinden proje ayarlarını açıyoruz ve buradan API seçeneğini seçiyoruz. Buradaki URL ve anon, public API anahtarlarını .env.local dosyasına aşağıdaki gibi kaydediyoruz.

NEXT_PUBLIC_SUPABASE_URL="Proje URLsi"
NEXT_PUBLIC_SUPABASE_KEY="API Anahtarı"

Netlify veya Vercel kullanıyorsanız bu dosyayı .gitignore'a ekleyerek dosyayı sadece yereldeki çalışmalarınız için kullanıp Production için Netlify/Vercel üzerinden değişken tanımlama bölümünü kullanabilirsiniz.

API Oluşturma

Sonraki aşama pages/api/views/[slug].js dosyası oluşturmak. Bu dosya ile GET ve POST isteklerini kontrol edeceğiz. POST ile görüntüleme sayısını artıracağız ve GET ile mevcut görüntüleme sayısını çekeceğiz.

Öncelik Supabase'i başlatmak için kullanacağımız dosyayı oluşturuyoruz.

lib/hooks/initSupabase.js
import { createClient } from "@supabase/supabase-js";
 
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_KEY || "";
 
export const SupabaseClient = createClient(supabaseUrl, supabaseKey);
lib/hooks/initSupabase.js
import { createClient } from "@supabase/supabase-js";
 
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_KEY || "";
 
export const SupabaseClient = createClient(supabaseUrl, supabaseKey);

Daha sonra APImizi hazırlıyoruz.

pages/api/views/[slug].js
import { SupabaseClient } from "../lib/hooks/initSupabase";
 
export default async (req, res) => {
  if (req.method === "POST") {
    await SupabaseClient.rpc("increment_views", { page_slug: req.query.slug });
    return res.status(200).json({
      message: `Görüntüleme sayısı artırıldı: ${req.query.slug}`,
    });
  }
 
  if (req.method === "GET") {
    const { data } = await SupabaseClient.from("analytics")
      .select("views")
      .filter("slug", "eq", req.query.slug);
 
    if (data) {
      return res.status(200).json({
        total: data[0]?.views || null,
      });
    }
  }
 
  return res.status(400).json({
    message: "Desteklenmeyen istek",
  });
};
pages/api/views/[slug].js
import { SupabaseClient } from "../lib/hooks/initSupabase";
 
export default async (req, res) => {
  if (req.method === "POST") {
    await SupabaseClient.rpc("increment_views", { page_slug: req.query.slug });
    return res.status(200).json({
      message: `Görüntüleme sayısı artırıldı: ${req.query.slug}`,
    });
  }
 
  if (req.method === "GET") {
    const { data } = await SupabaseClient.from("analytics")
      .select("views")
      .filter("slug", "eq", req.query.slug);
 
    if (data) {
      return res.status(200).json({
        total: data[0]?.views || null,
      });
    }
  }
 
  return res.status(400).json({
    message: "Desteklenmeyen istek",
  });
};

Bu kısımdan sonra aslında her şey hazır. Sadece artık bunu nerede kullanacağımıza karar vermemiz gerekiyor. Görüntüleme sayısını birkaç yerde kullanacağımız için PageViews adında bir component (bileşen) oluşturalım.

Birkaç yerde fetch işlemi yaptığım için kullanım kolaylığı açısından fetcher adında bir fonksiyon kullanıyorum, bu dosyada da kullandığım için aşağıdaki şekilde kaydedebilirsiniz ya da direkt kod içine de ekleyebilirsiniz, tercih sizin. Basitçe URL istek yapıp sonucu JSON olarak veriyor.

/lib/fetcher.js
export default async function fetcher(url) {
  return fetch(url).then((r) => r.json());
}
/lib/fetcher.js
export default async function fetcher(url) {
  return fetch(url).then((r) => r.json());
}

Kendi sitemde yüklenme aşamasında göstermek açısından dönen simge kullanıyorum. Bunun için React Icons ve Tailwind kullanıyorum. Eğer bu şekilde kullanmak istemezseniz ya da sitenizde React Icons ve Tailwind kullanmıyorsanız bir sonraki kodda daha sade halini de paylaştım.

/components/PageViews.js
import fetcher from "../lib/fetcher";
import { useEffect } from "react";
import { FaSpinner } from "react-icons/fa";
import useSWR from "swr";
 
export function PageViews({ slug }) {
  const { data, error, isLoading } = useSWR(`/api/views/${slug}`, fetcher);
  const views = new Number(data?.total);
 
  if (error) return <div>yüklenemedi</div>;
  if (isLoading)
    return (
      <>
        <FaSpinner className="inline-block h-4 w-4 animate-spin" />{" "}
        <span className="inline-block"> görüntüleme</span>
      </>
    );
 
  return `${views >= 0 ? views.toLocaleString() : "---"} görüntüleme`;
}
 
export function UpdateViews(slug) {
  useEffect(() => {
    const registerView = () =>
      fetch(`/api/views/${slug}`, {
        method: "POST",
      });
 
    registerView();
  }, [slug]);
}
/components/PageViews.js
import fetcher from "../lib/fetcher";
import { useEffect } from "react";
import { FaSpinner } from "react-icons/fa";
import useSWR from "swr";
 
export function PageViews({ slug }) {
  const { data, error, isLoading } = useSWR(`/api/views/${slug}`, fetcher);
  const views = new Number(data?.total);
 
  if (error) return <div>yüklenemedi</div>;
  if (isLoading)
    return (
      <>
        <FaSpinner className="inline-block h-4 w-4 animate-spin" />{" "}
        <span className="inline-block"> görüntüleme</span>
      </>
    );
 
  return `${views >= 0 ? views.toLocaleString() : "---"} görüntüleme`;
}
 
export function UpdateViews(slug) {
  useEffect(() => {
    const registerView = () =>
      fetch(`/api/views/${slug}`, {
        method: "POST",
      });
 
    registerView();
  }, [slug]);
}

Daha sade hali ise bu şekilde.

/components/PageViews.js
import fetcher from "../lib/fetcher";
import { useEffect } from "react";
import useSWR from "swr";
 
export function PageViews({ slug }) {
  const { isLoading } = useSWR(`/api/views/${slug}`, fetcher);
  const views = new Number(data?.total);
 
  return `${views >= 0 ? views.toLocaleString() : "---"} görüntüleme`;
}
 
export function UpdateViews(slug) {
  useEffect(() => {
    const registerView = () =>
      fetch(`/api/views/${slug}`, {
        method: "POST",
      });
 
    registerView();
  }, [slug]);
}
/components/PageViews.js
import fetcher from "../lib/fetcher";
import { useEffect } from "react";
import useSWR from "swr";
 
export function PageViews({ slug }) {
  const { isLoading } = useSWR(`/api/views/${slug}`, fetcher);
  const views = new Number(data?.total);
 
  return `${views >= 0 ? views.toLocaleString() : "---"} görüntüleme`;
}
 
export function UpdateViews(slug) {
  useEffect(() => {
    const registerView = () =>
      fetch(`/api/views/${slug}`, {
        method: "POST",
      });
 
    registerView();
  }, [slug]);
}

Artık her şeyimiz hazır. Görüntüleme sayısını artırmak istediğimiz yerlerde UpdateViews(slug) fonksiyonunu, sadece mevcut görüntüleme sayısını göstermek istediğimiz yerlerde de <PageViews slug={slug} /> componentini kullanıyoruz. Örneğin ben yazı içinde ikisini birden kullanıyorum her okunduğunda artması için ve başlığın altında da mevcut sayıyı göstermek için fakat yazı listesinde sadece mevcut görüntülenme sayısını gösteriyorum.

Parametre olarak verilen slug sizin kullanımınıza göre değişiyor. Örneğin sitenizde yazı başlığı göstermek için postData.title kullanıyorsanız muhtemelen slug da postData.id gibi bir şeydir. Öğrenmek için getStaticProps kısmına bakın.

İşin kötü yanı bu yerelde çalışırken de sayfaya her girdiğimde arttığı için başlarda yaklaşık 50 görüntülemeyi zaten ben yapıyorum. 😅

© 2024

Taylan Tatlı

TwitterGithubInstagram