FiniCounter: A Website Vistor Counter

Static website such as Hexo/Hugo/Jekyll is very popular recent years. It is fast, easy to write, deploy and host. However, no free lunch: it is non-trivial to store dynamic information such as pageview counts and comments under the serverless architecture. This site uses Waline to implement article view count and comment system.

Accidently I found that we do not have a full site pageview counter. Waline has post-level counter instead of site-level one.

Just DIY: FiniCounter. Use Vercel serverless function as the web API framework, MongoDB as the storage. When a user comes to any page of the site, we invoke the count API through fetch API, increment the counter in the MongoDB, return the updated value and display in the page.

Initially I want to develop a tool for myself. After I finish it, I decide to make it as a public free service :-) . FiniCounter looks like this:

FiniCounter Demo

Backend Implementation

The backend logic is straightforward. The server received a Http GET request and increment the corresponding count in MongoDB.

The API format: https://finicounter.eu.org/counter?host=xxx.com

It uses domain name host as the key to count.

The main code is self-explained:

MONGODB_URL = os.environ.get("MONGODB_URL")
CORS_ORIGINS = os.environ.get("CORS_ORIGINS")
DB_NAME = os.environ.get("DB_NAME") if os.environ.get("DB_NAME") is not None else "MyCounter"
COLLECTION_NAME = "Counter"

cors_headers = {"Access-Control-Allow-Origin": CORS_ORIGINS, "Access-Control-Allow-Method": "POST, GET, OPTIONS"}

mongoClient = pymongo.MongoClient(MONGODB_URL)
db = mongoClient[DB_NAME]
collection= db[COLLECTION_NAME]

app = Sanic("finicounter")

@app.route("/counter")
async def counter(request):
    args = request.get_args()
    host = args["host"][0]
    result = collection.find_one_and_update({"host": host}, {"$inc": {"views": 1}, "$set": { "updateTime": datetime.datetime.utcnow()}}, upsert = True, return_document = pymongo.collection.ReturnDocument.AFTER, maxTimeMS=50)
    return response.json({"views": result['views']}, headers = cors_headers)


app.run(host="0.0.0.0", port=8000, fast=True)

Host By Yourself

Host FiniCounter yourself is easy. What you need is a Vercel account and a MongoDB.

Just fork https://github.com/finisky/finicounter to your GitHub account, create a new project in Vercel, import the private repository and configure 3 environment variables:

Value
CORS_ORIGINS * (or your domain)
DB_NAME MongoDB DB name
MONGODB_URL MongoDB Connection String

The data is stored in the Counter Collection of DB DB_NAME .

Vercel Env Setting

Frontend Implementation

The fronted is implemented by fetch API. The innerText of the element with id finicount_views will be set to the retrieved counter value. The script:

async function getViews() {
    let url = "https://finicounter.eu.org/counter?host=" + window.location.hostname;
    try {
        let res = await fetch(url);
        return await res.json();
    } catch (error) {
        console.log(error);
    }
}

async function renderViews() {
    let res = await getViews();
    let elem = document.getElementById("finicount_views");
    if (typeof elem !== 'undefined' && elem !== null) {
        elem.innerText = formatNumber(res.views);
    }
}

function formatNumber(num) {
    return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
}

renderViews();

The function formatNumber will format the counter like 12,345,678 . Feel free to customize your own script if necessary.

How to Use

Basic Usage

  1. Add the following script to your websites:
<script async src="//finicounter.eu.org/finicounter.js"></script>
  1. Add a container with id finicount_views to show the view counts:
<span id="finicount_views"></span>

You are done! The view count will be shown on your site like this 12,345,678.

Notice: the script should be added to every pages of your website. Otherwise, the page view counter might missing counts.

It is also welcome to directly use the counter API https://finicounter.eu.org/counter?host=xxx.com and customize the javascript yourself.

Each counter will be automatically deleted if not accessed for more than 3 months.

Hexo NexT Theme Integration

For Hexo NexT theme, we can easily integrate FiniCounter by Injects. The total pageview can be displayed in the footer:

FiniCounter Hexo NexT Demo

Just add a new js file totalpageview.js in the Hexo root folder scripts/:

hexo.extend.filter.register('theme_inject', function(injects) {
  injects.footer.raw('totalpageview', '<div><span><a href="https://finicounter.eu.org/" target="_blank">Total Pageview:</a></span><span id="finicount_views" style="display:inline;padding-left:5px;"></span><div> <script async src="//finicounter.eu.org/finicounter.js"></script>', {}, {cache: false});
});

Leave Hexo NexT Plugin as future work :-) .

Demo

Demo

This site is using FiniCounter :-) . Demo on this site .

Source Code

Source code is available here .