前言
目前我的網站domain是透過Cloudflare這個佛心公司代理,Cloudflare提供了一個很強大的功能 「Worker」,Worker可以理解是一個很輕量的伺服器腳本,架設非常的簡單,只需要透過Cloudflare的Dashboard很容易就可以部署,本篇文章就要透過Cloudflare Worker來追蹤文章訪問人數。
Cloudflare Worker 部屬
-
創建 Workers KV
Workers KV可以理解為Cloudflare提供的簡易No Sql 簡易資料庫,我們需要創建一個用於儲存文章(key)瀏覽次數(value)的物件。
{ "view:<文章對應唯一值>": <瀏覽次數>" }先訪問Cloudflare 「儲存空間和資料庫」 => 「Workers KV」

點選右上角 「Create Instance」, 輸入要得KV Name 這邊輸入 「BLOG_VIEWS」

-
建立Worker
先訪問Cloudflare 「Worker 和 Pages」頁面

接著按右上角的「建立應用程式」=> 「從 Hello World 開始」 => 「部署」,看到下面的Worker總覽頁面以及Worker網址就算成功了!
-
綁定 Workers KV
這個步驟需要將剛剛建立的Workers KV 給予一個變數,讓這個Workers的腳本可以以變數的形式取得KV物件。


點選右上角「編輯代碼」,即可進入編輯頁面

- 編輯腳本
將下列的程式碼貼入後,點選右上角「部署」即可完成Worker的部署。(稍後會解釋整個流程需搭配下個章節的前端程式碼觀看)
export default { async fetch(request, env) { const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }; if (request.method === "OPTIONS") return new Response(null, { headers: corsHeaders }); const { searchParams } = new URL(request.url); const slug = searchParams.get('slug'); const mode = searchParams.get('mode') || 'increment'; if (!slug) return new Response("Missing slug", { status: 400, headers: corsHeaders }); const key = `view:${slug}`; let currentViews = parseInt(await env.BLOG_VIEWS.get(key) || "0"); if (mode === 'increment') { currentViews += 1; await env.BLOG_VIEWS.put(key, currentViews.toString()); } return new Response(JSON.stringify({ views: currentViews }), { headers: { ...corsHeaders, "Content-Type": "application/json" } }); } };
更新瀏覽人數流程
假設使用者訪問了/post/1這篇文章的時候,我會先讀取cookie中的 viewed_1的值,viewd存入的會是最近閱讀的日期,若上次閱讀的日期在24小時之內則判斷為"read"否則判斷為"increment",接著在將判斷後的值透過請求worker api帶入交由worker判斷是否要新增該篇文章的閱讀人數。
前端的程式碼如下:
"use client"; import { useEffect, useState } from "react"; export default function ViewCounter({ slug }: { slug: string }) { const [views, setViews] = useState<number | null>(null); useEffect(() => { const fetchViews = async () => { try { const workerUrl = "https://billowing-dawn-f73e.a2251296.workers.dev/"; const storageKey = `viewed_${slug}`; const lastViewed = localStorage.getItem(storageKey); const now = Date.now(); const ONE_DAY = 24 * 60 * 60 * 1000; const shouldIncrement = !lastViewed || (now - parseInt(lastViewed) > ONE_DAY); const mode = shouldIncrement ? "increment" : "read"; const res = await fetch( `${workerUrl}?slug=${encodeURIComponent(slug)}&mode=${mode}` ); const data = await res.json(); setViews(data.views); if (shouldIncrement && data.views) { localStorage.setItem(storageKey, now.toString()); } } catch (e) { console.error("Counter error:", e); } }; fetchViews(); }, [slug]); if (views === null) return null; return ( <span className="bg-muted px-2.5 py-1 rounded-md border border-border/50"> 閱讀次數: {views.toLocaleString()} </span> ); }