Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save dthyresson/d0eb316409492dc8aa44e5ace8238e04 to your computer and use it in GitHub Desktop.
Save dthyresson/d0eb316409492dc8aa44e5ace8238e04 to your computer and use it in GitHub Desktop.
Spark! Netlify Edge Handler Idea: Analytics + Redirect aka Let's mashup Bit.ly + Segment!

Netlify Edge Handler Idea: Analytics + Redirect aka Let's mashup Bit.ly + Segment!

Background

At #jamstackconf 2020, Netlfiy announced a preview of Edge Handlers which will allow you to run code directly on the edge server to handle things like custom authentication patterns, localization and personalization.

https://www.netlify.com/products/edge/edge-handlers/

https://www.youtube.com/watch?v=D44n8YVb5iI

Spark Idea

I had previously written some utilities to encode information about a shared url (ala bit.ly) than when decoded would extract metadta about the share (and sharer), pass that information on to Segment for analytics tracking and then redirect to the shared target url.

It's not great, because the encoding uses JWT (I wanted something that could be signed so not tampered with), but that means the link is a little long ... but the issue was that because I was using Netlify redirects as well as Netlify functions to do a second redirect, the behavior was not ideal.

However. I think that by moving the logic to the Edge, we can eliminate one (or maybe both?) redirects and have a much cleaner experience.

How Currently Works

There is an activity that has some location on the web.

When the React app fetches the activity, it enriches it via a Netlify function with is ShareLink getShareLink.js/

It is somewhat short in that the path is just /a.

This ingects some valuable tracking and analytics info like:

  • Who shared it (iss: userId)
  • The shared link to get tracked (url: canonicalUrl)

And campaign info about the share

    utm_source: utmSource,
    utm_medium: utmMedium,
    utm_content: utmContent,
    utm_campaign: utmCampaign,

We'll JWT encode this using a secret.

When the link is visited

const shareLink = `https://${env.SHARE_SUBDOMAIN}.${env.SHARE_HOST}/a?u=${encodedUrl}&ts=${ts}`;

The analyze.js function running on some Netlify app at that location will be run having been redirected to via a Netlify redirect:

# Functions
/a /.netlify/functions/analyze 200

That function then:

  • decodes using the same JWT secret that was used to encode
  • extractes the sahre analytics info
  • sends that to Segment to track
  • redirects to the shared url

Edge Handler

Not out yet, but I thinik can move this to the edge and perhaps even further enrich the analytics more with some Netlify header info (geography, etc).

I don't yet know what edhe headers are provided by Netlify, altough the video about includes some geo-based headers for City/Region/Country.

Ideally, the redirect to the handler might get rid of a second redirect becasuse done all on Edge ... and be less clunky?

Caveats

Yeah, no idea if this is GDPR-ok or whatever.

In fact, ths wasn't put into production due to two reasons:

  • long urls caused some issues with some social platforms
  • the 2 redirects also caused some issues with resolving the link

Hence the odd 307 HTTP status code.

( am looking at you LinkedIn ;) )

# Redirect domain aliases to primary domain
# https://share.DOMAIN.com/* https://analytics.DOMAIN.com/:splat 301!
# https://DOMAIN.link/* https://analytics.DOMAIN.com/:splat 301!
# Optional: Redirect default Netlify subdomain to primary domain
# https://something.netlify.com/* https://analytics.DOMAIN.com/:splat 301!
# Functions
/a /.netlify/functions/analyze 200
// This is the Netlify function that would live in
// functions/analyze/analyze.js
// It relies on Segment's analytics SDK
// It would be a dedicated app that handles all shared urls for an organization
require("dotenv").config();
const Analytics = require("analytics-node");
const jwt = require("jsonwebtoken");
const analytics_client = new Analytics(process.env.SEGMENT_WRITE_KEY);
async function decodeToken(urlToken) {
const secret = new Buffer(process.env.JWT_SECRET, 'base64');
const share = jwt.verify(urlToken, secret, { algorithms: ['HS256'] });
await trackShare(share);
return Promise.resolve(share);
}
async function trackShare(share) {
const response = analytics_client.track({
event: share["event_name"],
userId: share["iss"],
properties: {
utm_medium: share["utm_source"],
utm_medium: share["utm_medium"],
utm_content: share["utm_content"],
utm_campaign: share["utm_campaign"],
url: share["url"]
}
});
return Promise.resolve(response);
}
async function redirect(urlToken) {
const share = await decodeToken(urlToken);
// build url from utm qs instead
return Promise.resolve(share["url"]);
}
exports.handler = async (event, context) => {
try {
const urlToken = event.queryStringParameters.u;
const location = await redirect(urlToken);
return {
statusCode: 307,
body: "",
headers: {
Location: location
}
};
} catch (err) {
console.log(err.toString());
return { statusCode: 500, body: err.toString() };
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment