Your blog is getting traffic. Three posts are pulling in visitors from search and social, and you are writing more each week. But when someone clicks "Join Waitlist" after reading your schema-on-write explainer, your dashboard counts it as a signup. It does not tell you it came from that post.
Your Analytics tab shows Top Pages with visit counts per URL. You can see /blog/guides/schema-on-write had 80 visits this week. What you cannot see is how many of those 80 people signed up.
Closing that gap takes two steps: tagging your CTAs with either UTM parameters or a trackEvent() call, and reading the results in your Analytics tab.
OperatorStack logs every blog post visit automatically. To track conversions, tag your blog CTAs with ?utm_source=blog&utm_content=post-slug (simple) or fire OperatorStack.trackEvent("blog_cta_click", { post: "post-slug" }) on click (precise). Both approaches appear in your Analytics tab within seconds.
What You Get Without Extra Setup
When the OperatorStack script tag is on your blog, it tracks every pageview. The Analytics tab shows:
- Top Pages -- visits per URL, so you can see
/blog/how-to/set-up-waitlisthad 200 visits and/blog/guides/schema-on-writehad 80 - Referral sources -- where those blog visitors came from (search, Twitter, newsletters)
- Unique visitors -- how many distinct people hit each post
This is enough to know which posts attract an audience. It does not tell you which posts convert that audience into signups.
Approach 1: UTM Parameters on CTA Links
The simplest approach requires no code changes to your blog. Add UTM parameters to any link that points visitors toward your signup flow.
If your blog post ends with a link to your landing page:
<!-- Without tracking -->
<a href="https://yoursite.com">Start for Free</a>
<!-- With UTM tracking -->
<a href="https://yoursite.com?utm_source=blog&utm_content=schema-on-write&utm_medium=cta">
Start for Free
</a>
When a visitor clicks that link and signs up, the signup is attributed to utm_source=blog in your Analytics tab. The utm_content value (the post slug) appears under Traffic Sources so you can compare conversions per post.
You can also use this pattern for in-line text links within the post body, not just the closing CTA.
Use consistent utm_content values -- the post slug is ideal. This lets you sort by content value in the Analytics tab and rank posts by signup contribution, not just traffic.
Approach 2: Custom Events on CTA Clicks
UTM parameters require a page navigation to capture attribution. If your blog posts have a waitlist widget on the page itself (not a link to a separate landing page), use trackEvent() to fire a custom event when the visitor clicks the signup CTA.
Add the script tag to your blog layout if it is not there already:
<script src="https://operatorstack.dev/os.js" data-project="pk_your_key" defer></script>
Then attach an event handler to your CTA button:
await OperatorStack.ready;
document.querySelector("#blog-cta").addEventListener("click", () => {
OperatorStack.trackEvent("blog_cta_click", {
post: "guides/schema-on-write",
category: "guides",
keyword: "schema-on-write",
});
});
This fires the event the moment someone clicks, regardless of whether they complete the signup. You get click intent data even from visitors who abandon the form.
To track completed signups specifically, fire a second event inside the signup callback:
const result = await OperatorStack.joinWaitlist({
email: emailInput.value,
});
if (result.success) {
OperatorStack.trackEvent("blog_signup_completed", {
post: "guides/schema-on-write",
category: "guides",
});
}
Both events appear in the Events section of your Analytics tab with their properties.
Using the Post URL Automatically
If you have multiple blog posts using the same template, read the post slug from the URL rather than hardcoding it:
await OperatorStack.ready;
const postSlug = window.location.pathname.replace("/blog/", "").replace(/\/$/, "");
document.querySelector("#blog-cta").addEventListener("click", () => {
OperatorStack.trackEvent("blog_cta_click", { post: postSlug });
});
Add this once to your blog post layout component (Nuxt, Next.js, Astro, or plain HTML template) and every post automatically tags its own clicks.
Approach 3: Combining Both
For the most complete picture, use UTM parameters on outbound links and trackEvent() on in-page CTAs. The two approaches track different moments:
| Signal | What it captures |
|---|---|
utm_content=post-slug | Visitor navigated away from your blog to the landing page from a specific post |
blog_cta_click event | Visitor clicked a CTA on the post (intent, before signup) |
blog_signup_completed event | Visitor signed up directly from the post |
The combination tells you which posts attract browsers vs. which ones convert browsers into signups.
Reading the Results
Open your Analytics tab. Go to your project in OperatorStack and click Analytics. The Top Pages panel shows visit counts per URL. Posts with /blog/ in the path are your blog content.
Check Traffic Sources for UTM attribution. If you used UTM parameters, the Traffic Sources panel shows rows like blog under utm_source. Click through to see utm_content values (your post slugs) ranked by visits and conversion rate.
Check Events for custom event data. The Events panel lists every unique event name. Click blog_cta_click to see a count and a breakdown of the post property -- which posts triggered the most clicks.
Compare traffic to conversions per post. A post with 200 visits and 2 blog_signup_completed events converts at 1%. A post with 80 visits and 8 completions converts at 10%. The second post is ten times more valuable per visitor, even with less traffic. That is the insight that tells you what to write next.
Common Setup for Nuxt Content Blogs
If your blog runs on Nuxt Content (the same stack this blog uses), add the script tag to your default layout and the event handler to a composable:
// composables/useBlogTracking.ts
export function useBlogTracking() {
const route = useRoute();
onMounted(async () => {
if (!window.OperatorStack) return;
await window.OperatorStack.ready;
const postSlug = route.path.replace("/blog/", "").replace(/\/$/, "");
document.querySelector("#blog-cta")?.addEventListener("click", () => {
window.OperatorStack.trackEvent("blog_cta_click", { post: postSlug });
});
});
}
Call useBlogTracking() in your blog post layout component, and every post on the site gets automatic CTA click tracking with zero repeated code.
Frequently Asked Questions
Does OperatorStack automatically track blog post visits?
Yes. The embed script records a pageview for every URL, including blog post URLs. Your Analytics tab shows visit counts per page under Top Pages. That tells you which posts get traffic, but not which ones convert visitors into signups.
What is the simplest way to track which blog post drove a signup?
Add UTM parameters to every CTA link on your blog posts. Use ?utm_source=blog&utm_content=your-post-slug on any link that points to your signup page or waitlist widget. The signup will be attributed to that source in your Analytics tab.
Can I track CTA clicks without a page reload?
Yes. Use OperatorStack.trackEvent() to fire a custom event on the click, with the post slug as a property. This captures the intent even if the visitor does not complete the signup.
How do I see the custom event data in the dashboard?
Custom events fired via trackEvent() appear in the Events section of your Analytics tab. Each event name shows a count and any properties you passed, including the post slug or category.
What if my blog is on a separate subdomain from my landing page?
Add the same OperatorStack script tag to your blog. The project key is the same regardless of domain. Analytics events from your blog and your landing page all feed into the same project dashboard.