Stickybeak Docs Dashboard Get early access

Backends

Express and Node.js

Express analytics with Stickybeak starts by adding its script to your rendered pages; this guide shows you where it belongs and how to track page views, goals and purchase revenue.

Rendering React on the server?

If your HTML comes from Next.js, Remix or another React framework rather than Express views, follow Next.js or React instead.

Add the script

  1. Set the site ID

    Set STICKYBEAK_SITE_ID in your production environment only.

    // Only set in production, so dev and staging aren't counted.
    app.locals.stickybeakSite = process.env.STICKYBEAK_SITE_ID;
    
    // JSON that's safe inside a <script> tag.
    app.locals.toScript = (value) => JSON.stringify(value ?? null).replace(/</g, "\\u003c");
  2. Add it to your layout

    EJS has no layouts of its own, so most apps include a shared head partial. Put this before </head>:

    <script>window.stickybeak = window.stickybeak || function () { (window.stickybeak.q = window.stickybeak.q || []).push(arguments); };</script>
    <% if (stickybeakSite) { %>
      <script
        defer
        data-site="<%= stickybeakSite %>"
        data-domain="example.com"
        src="https://app.usestickybeak.com/_cfw/s.js"
      ></script>
    <% } %>

Pug

doctype html
html
  head
    script.
      window.stickybeak = window.stickybeak || function () { (window.stickybeak.q = window.stickybeak.q || []).push(arguments); };
    if stickybeakSite
      script(defer data-site=stickybeakSite data-domain="example.com" src="https://app.usestickybeak.com/_cfw/s.js")
  body
    block content

Handlebars

With express-handlebars, the default layout is views/layouts/main.handlebars:

<head>
  <script>window.stickybeak = window.stickybeak || function () { (window.stickybeak.q = window.stickybeak.q || []).push(arguments); };</script>
  {{#if stickybeakSite}}
    <script
      defer
      data-site="{{stickybeakSite}}"
      data-domain="example.com"
      src="https://app.usestickybeak.com/_cfw/s.js"
    ></script>
  {{/if}}
</head>

Fastify, Koa, NestJS and others

The templates are the same whichever Node server renders them.

Check it's working

Publish first, then follow Verify your install.

Track goals

Give any button or link a data-goal attribute. Extra data-goal-* attributes become properties. See Custom goals.

<a href="/pricing" data-goal="view_pricing" data-goal-from="hero">See pricing</a>

Fire a goal after a redirect

For a signup or a paid order, the route that saves it doesn't render a page; it redirects.

// After app.use(session(...)): move a pending goal from the session into the view.
app.use((req, res, next) => {
  res.locals.stickybeakGoal = req.session.stickybeakGoal;
  delete req.session.stickybeakGoal;
  next();
});

app.post("/signup", async (req, res, next) => {
  const user = await createUser(req.body);
  req.session.stickybeakGoal = { name: "signup", props: { plan: user.plan } };
  req.session.save((error) => {
    if (error) return next(error);
    res.redirect("/welcome");
  });
});

Saving before the redirect prevents the next request from reaching the session store before the goal has been written.

<% if (stickybeakGoal) { %>
  <script>window.stickybeak(<%- toScript(stickybeakGoal.name) %>, <%- toScript(stickybeakGoal.props || {}) %>);</script>
<% } %>

<%- outputs without HTML escaping, which is right here because toScript has already made the value safe for a script.

Track purchases

req.session.stickybeakGoal = {
  name: "purchase",
  props: { revenue: order.total, currency: order.currency, order_id: String(order.id) },
};
req.session.save((error) => {
  if (error) return next(error);
  res.redirect(`/orders/${order.id}/thanks`);
});

See Revenue.

If it isn't working

  • Confirm STICKYBEAK_SITE_ID reaches the Node process. Server-rendered React should use its framework guide instead.

See Troubleshooting.