As a casual browser of this site, you may have noticed the section at the bottom of each page showing a word count, estimated reading time, and view counter.
The word count and reading time are generated by Hugo, but Hugo can’t track view counts by nature; it’s a static site generator, only able to smash bits of HTML together at lightspeed.
I had to implement a backend to handle this dynamic page data.
At first, I had great ambitions for a set of services to connect all my sites,
and started a Django project called Auxilliary Website Services. The original
intention was to bring user accounts, bookmarking, comments, and other gizmos to
ryanfleck.ca
, manuals.ryanfleck.ca
, smallminds.dev
, etc. Unfortunately,
there just hasn’t been a real need or time to do this, and users have had to
wait upwards of thirty seconds for the Django view-counter to boot. In
addition, I have added utteranc.es
recently, which was crazy easy to set up
and provides the perfect authentication method (Github login) for a
corporate-facing tech site.
To solve this problem, I decided to take half an hour to rewrite the view-counter in Repl.it, an integrated code editor and PaaS. Using the included key-value database, it’d be super easy to associate numbers with website paths.
…and that’s exactly what I did. Here’s the source code. Scroll to the end of the page to see the view counter in action.
#Hack the REPL at https://replit.com/@RyanFleck/analytics#main.py
#Bottle Template created by @rediar
#bottle is good for smale scale web applications. It is easy to quickly make API's or small personal webpage using Bottle.
from bottle import request, Bottle
from truckpad.bottle.cors import CorsPlugin, enable_cors
from json import dumps
from replit import db
app = Bottle()
#this is the main page
@app.route('/')
def index():
return '<pre>RCF Analytics Mk 0.0.0.9</pre>'
#this can only be accesed with a post request (visiting in browser will return 405 Method Not Allowed error)
@enable_cors
@app.post('/api/view-counts/page-tracker/')
def post_request_only():
print("Analytics POST, collecting data...")
path: str = str(request.json.get('page_url'))
site: str = str(request.headers['origin'])
if not path or not site:
return dumps({})
# ReplIT Database HATES '/', so remove 'em
key: str = f"{site}-{path}".replace('/', ' ').strip().replace(' ', '-')
print(f"key: {key}")
try:
views = db[key]
print(views)
db[key] = views + 1
return dumps({'page_views': views + 1})
except KeyError:
print("KeyError, adding...")
db[key] = 1
return dumps({'page_views': 1})
#A 404 page if the url doesn't exist
@app.error(404)
def error404(error):
return (
"oops! the page you were looked for isn't here. <a href='/'>Return Home?</a>"
)
app.install(
CorsPlugin(origins=['https://ryanfleck.ca']))
app.run(
host='0.0.0.0',
port=1234) #this starts the webpage. any code after this line won't be run
Page views are cached in Session Storage to prevent multiple views during a single session from ratcheting up the view count and inflating the popularity of the page.
All that said, this is certainly the most privacy-respecting way to implement something like a page view counter, and a good first step towards rolling your own page analytics.
Comments