Introduction

Vercel (previously zeit.now) is a great platform to host personal websites/blogs, and this is where this blog is also hosted on ; )

Vercel offers Serverless Functions for free with generous usage. You can use Serverless Functions to handle user authentication, form submission, database queries, custom slack commands, and more.

Serverless Functions Usage

We can use serverless functions in currently supported languages like Node.js, Go, Python and Ruby. These small pieces of code are placed under /api directory of the project.

HTTP Headers used by vercel

Vercel by-default uses some Request Headers which are sent to each deployment for each request, and can be used to process the request before sending back a response.


Basic Analytics πŸ“‰

Most of the times we use 3rd-party analytics services such as Google, for getting site analytics, but because of ad-blockers and extensions such as Privacy badger, we hardly get this info. Also this provides users to not track all over the internet just because they visited our website, protecting some of their privacy.

By this you may collect the number of actual visits your website gets (with clients having js enabled), and also use it with data analytics to understand which page/article got most visits, at what time and which part of the globe, yay !

So for basic analytics we are going to use Serverless Functions and HTTP Headers as you might have guessed.

Part 1: Getting info

For each request sent vercel sends some HTTP header listed here: https://vercel.com/docs/edge-network/headers#request-headers

When logged an actual request in dev environment we can see what all headers are present here:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
    "host": "localhost:34847",
    "x-forwarded-host": "accept-language",
    "x-forwarded-proto": "http",
    "x-forwarded-port": "3000",
    "x-forwarded-for": "::1",
    "x-vercel-id": "dev1::dev1::ge55g-3544578678986-ji9tf40olk5h",
    "x-vercel-forwarded-for": "::1",
    "x-vercel-deployment-url": "localhost:3000",
    "x-real-ip": "::1",
    "accept-language": "en,en-US;q=0.9",
    "accept-encoding": "gzip, deflate, br",
    "referer": "http://localhost:3000/",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-origin",
    "origin": "http://localhost:3000",
    "accept": "*/*",
    "dnt": "1",
    "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
    "content-length": "0",
    "connection": "close"
}

From all of these headers, user specific headers which can be used are:

  • x-forwarded-for (same as x-real-ip) ex. 1.121.23.1
  • referer ex. http://localhost:3000/
  • dnt
  • user-agent
  • accept-language

dnt is do-not-track header :p ex. 1 or null.

User-Agent Header shows the device and browser info ex. Mozilla/5.0 (Linux; U; Android 10; en-us; Redmi Note 7 Pro Build/QKQ1.190915.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.141 Mobile Safari/537.36 XiaoMi/MiuiBrowser/12.4.1-g

Accept-Language Header show info about user’s preferred languages. ex. en,en-US;q=0.9

so these headers provide a pretty basic info about the user …! We can further use services such as IP Geolocation to get more location info for the IP.

Part 2: Processing info and Storing it

For processing the data, we are going to use Node.js with a single serverless function, and store the received data in MongoDB.

For this create a folder named /api in root of your project folder and add file name analytics.js inside ‘api’ folder, so that the folder structure looks like :

folder structure
.
β”œβ”€β”€ api
β”‚   └── analytics.js
β”œβ”€β”€ package.json
└── vercel.json

Below is the basic code that we are going to use.

analytics.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// ----- /api/analytics.js -----
// Import Dependencies
const url = require("url");
const MongoClient = require("mongodb").MongoClient;

// Create cached connection variable
let cachedDb = null;
const uri = process.env.VISITORSDB;

// A function for connecting to MongoDB,
// taking a single parameter of the connection string
async function connectToDatabase() {
    // If the database connection is cached,
    // use it instead of creating a new connection
    if (cachedDb) {
        return cachedDb;
    }

    // If no connection is cached, create a new one
    const client = await MongoClient.connect(uri, {
        useNewUrlParser: true,
        useUnifiedTopology: true,
    });

    // Select the database through the connection,
    // using the database path of the connection string
    const db = await client.db(url.parse(uri).pathname.substr(1));

    // Cache the database connection and return the connection
    cachedDb = db;
    return db;
}

// The main, exported, function of the endpoint,
// dealing with the request and subsequent response
module.exports = async (req, res) => {
    try {
        // get all user details and store them
        const referer = req.headers["referer"];
        const ip = req.headers["x-forwarded-for"];
        const ua = req.headers["user-agent"];
        const ul = req.headers["accept-language"];
        const dnt = req.headers["dnt"];

        d = new Date(); // time of logging
        d.toLocaleTimeString();

        info = { ip, ua, ul, dnt, referer, dt: "" + d }; // as a json5 object

        const db = await connectToDatabase();
        const collection = await db.collection(process.env.COLLECTION);
        await collection
            .insertOne(info)
            .then(() => {
                // just return the status as 200
                res.status(200).send();
            })
            .catch((err) => {
                throw err;
            });
    } catch (error) {
        // log the error so that owner can see it in vercel's function logs
        console.log(error);
        // return 500 for any error
        res.status(500).send();
    }
};

The above code “parses user info headers” from any POST request and ignores it’s data. This does not affect page load times and keeps, working in background !

So we just need to include a simple js script at end of each page, so that it sends a blank POST request to https://<your domain>/api/analytics, hosted on vercel.

index.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!DOCTYPE html>
<html>
    <head></head>
    <body></body>

    <script>
        var xhttp = new XMLHttpRequest();
        xhttp.open("POST", "/api/analytics", true);
        xhttp.send();
    </script>
</html>

For this code to work we also need a package.json file and add dependencies to it.

You can checkout the final code template here: https://github.com/adityatelange/vercel-basic-analytics

We also need to setup the environment variables mentioned in the code, so that mongoDB is connected without any hassle.

Read about how to add those here: Vercel Docs | Environment Variables

Env variables are:

nametypevalue
VISITORSDBstring<ex. mongodb+srv://vercelvisits:jidpoauhdilepodj@cluster0.lolol.mongodb.net
COLLECTIONstring<ex. bloglytics>

Great !!! πŸŽ‰

After we have added all these, we are all set up and we can push the website to vercel and let it collect basic user analytics. :P

This is how your stored data in MongoDB might look like:


References