Taking Cloudflare Worker Analytics Engine for a spin
Introduction
Cloudflare Workers is a serverless platform that allows you to run JavaScript code on Cloudflare’s edge network.
If you are using Cloudflare workers and are looking to show analytics from your workers in your SaaS application, you can make use of Cloudflare Worker Analytics.
Use Case
For my app — SlashURL, I need to show link analytics. SlashURL is a fast, scalable URL link shortener built using Cloudflare Workers and Workers KV store.
Some graphs that I need:
- Time series data for clicks
- Top countries
- Top cities
- .. and more
Sending Data
Steps:
- Create analytics data set binding
- Define metrics & dimensions to send
- Call from within the workers
export interface Env {
...
URL\_CLICK\_TRACKING: AnalyticsEngineDataset
}
// request.url contains the short original url
log(request: Request<unknown, CfProperties<unknown>>): Promise<void> {
const cfProperties = request.cf
if (!cfProperties) {
return Promise.resolve()
}
this.env.URL\_CLICK\_TRACKING.writeDataPoint({
'blobs': \[
request.url,
cfProperties.city as string,
cfProperties.country as string,
cfProperties.continent as string,
cfProperties.region as string,
cfProperties.regionCode as string,
cfProperties.timezone as string
\],
'doubles': \[
cfProperties.metroCode as number,
cfProperties.longitude as number,
cfProperties.latitude as number
\],
'indexes': \[
cfProperties.postalCode as string,
\]
})
return Promise.resolve()
}
Querying Data
Steps:
- Generate API token to access worker analytics (from Cloudflare console)
- Make a cURL request to the endpoint using the API token like:
Example SQL Query to get the time series data of clicks:
The query is trying to count the clicks on a specific URL within 24-hour intervals over the past 30 days, providing a time-series-like result with the number of clicks in each interval (day).
intDiv
is the function to create these 24-hour intervals
SELECT intDiv(toUInt32(timestamp), 86400) \* 86400 AS t, COUNT() as clicks FROM URL\_CLICK\_TRACKING WHERE blob1 = 'https://slsh.ws/ankit-linkedin' AND timestamp >= NOW() - INTERVAL '30' DAY GROUP BY t ORDER BY t, clicks DESC
curl --location --request POST 'https://api.cloudflare.com/client/v4/accounts/7bc1ff972e6f19acd7874cd1f12eff9b/analytics\_engine/sql' \\
\--header 'Authorization: Bearer <token>' \\
\--header 'Content-Type: text/plain' \\
\--data-raw '<query>'
Integrating into Next.js App
If you want to feed this data into your Next.js App, you can create a route analytics/[url].tsx
using the page router in Next.js like below:
import { ClickStream } from "@/components/analytics/ClickStream";
import Layout from "@/components/layout";
import { getClickStream } from "@/lib/queries";
import {
Alert,
AlertIcon,
Box,
HStack,
Heading,
Stack,
} from "@chakra-ui/react";
import { format, fromUnixTime } from "date-fns";
import { GetServerSidePropsContext } from "next";
export default function Analytics({ props, analytics }) {
const clickStream = analytics.clickStream;
const noData = clickStream.data.length === 0;
const data = clickStream.data.map((d) => {
return {
date: format(fromUnixTime(d.t), "dd/M"),
clicks: parseInt(d.clicks),
};
});
return (
<Box bg={"white"} py={7} px={7}>
<Stack spacing={2}>
<Heading as="h1" fontSize="3xl">
Analytics
</Heading>
<HStack mt={5}>
{noData ? (
<Alert status="info" mt={5}>
<AlertIcon />
Detailed Analytics for your links will be available soon.
</Alert>
) : (
<ClickStream data={data} />
)}
</HStack>
</Stack>
</Box>
);
}
Analytics.getLayout = function getLayout(page: any) {
return <Layout>{page}</Layout>;
};
export const getServerSideProps = async (
context: GetServerSidePropsContext
) => {
const { params } = context;
const clickStream = await getClickStream(params.url as string);
return {
props: {
analytics: {
clickStream,
},
},
};
};
To get the data from the Worker Analytics Engine:
import axios from "axios";
const TIMESERIES\_GET\_CLICKS = function (url: string) {
return \`
SELECT
intDiv(toUInt32(timestamp), 86400) \* 86400 AS t,
COUNT() as clicks
FROM URL\_CLICK\_TRACKING
WHERE
blob1 = '${url}' AND
timestamp >= NOW() - INTERVAL '30' DAY
GROUP BY t
ORDER BY t, clicks DESC\`
}
export async function getClickStream(url: string) {
const API\_ENDPOINT = \`https://api.cloudflare.com/client/v4/accounts/${process.env.CLOUDFLARE\_ACCOUNT\_ID}/analytics\_engine/sql\`;
const options = {
headers: {
"Authorization": \`Bearer ${process.env.CLOUDFLARE\_ANALYTICS\_API\_TOKEN}\`,
"Content-Type": "text/plain",
},
};
const payload = TIMESERIES\_GET\_CLICKS(url);
let data = null
try {
const response = await axios.post(API\_ENDPOINT, payload, options);
data = response.data;
console.log(data);
} catch (error) {
console.error(error);
}
return data
}
For rendering the charts, we are using the recharts react library. There are other libraries like https://react-chartjs-2.js.org/ as well, but I went with this as it supports a lot of charts.
To create a time series click-stream chart component
// Clickstream.tsx
import { useEffect, useState } from "react";
import {
CartesianGrid,
Legend,
Line,
LineChart,
Tooltip,
XAxis,
YAxis,
} from "recharts";
// This is needed as nextjs throws hydration error with recharts rendered on server/client
// More: https://github.com/recharts/recharts/issues/2272
// So we render it only on client here
export const useIsServerSide = () => {
const \[isServerSide, setIsServerSide\] = useState(true);
useEffect(() => {
setIsServerSide(false);
}, \[setIsServerSide\]);
return isServerSide;
};
export const ClickStream = (props: Props) => {
const isServerSide = useIsServerSide();
if (isServerSide) return null;
const { data } = props;
return (
<LineChart
width={500}
height={300}
data={data}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="clicks" stroke="#82ca9d" />
</LineChart>
);
};
Demo
Below is how it looks. Link to the dashboard here: slashurl.co
Analytics for Clicks
References
https://developers.cloudflare.com/analytics/analytics-engine/get-started/
https://developers.cloudflare.com/analytics/analytics-engine/sql-api/