如何追蹤 SaaS 註冊用戶的來源:7 步入門分析指南
How to Find Out Where Your SaaS Signups Come From: A 7-Step Beginner Guide to Simple Analytics
這份 7 步入門指南説明如何為 SaaS 追蹤註冊用戶來源,並把流量渠道連到註冊、首次完成核心操作及付費結果。指南以 Umami 示範記錄 signup、first_win、paid 事件,透過 UTM 和用戶資料保存首次來源,並附有程式碼、SQL 查詢及測試流程。
In March 2026, a founder posted on r/SaaS that they were finally getting their first users but had no idea where those users came from. Google Analytics showed mostly "Google" and "direct", and they summed it up as growth "happening in the dark". If you built your app with Lovable, Bolt, Replit, v0, Cursor or Claude Code, there is a good chance you are in the same spot: people sign up, and you are guessing why.
Guessing is expensive. You keep posting where it feels busy instead of where signups actually come from. This guide fixes that in 7 steps, with free or cheap tools, a little copy-paste code and AI prompts you can hand to your coding tool. It takes an afternoon, and the setup is one-time.
Quick glossary
- Pageview: one page loaded by one visitor.
- Event: anything you choose to count, like "signup" or "created first project". A pageview is also an event in most tools.
- Referrer: the site a visitor clicked from, sent by the browser. Sometimes it is missing.
-
UTM parameters: tags you add to the end of a link, like
?utm_source=reddit, so your analytics knows where the click came from even when the referrer is missing. - Direct (or "Direct / None"): visits with no referrer and no UTM. Plausible's docs explain this bucket includes clicks from email, messaging apps like WhatsApp, Slack or Discord, and bookmarks. People often call it "dark traffic".
- First touch: the very first place a person came from, before they ever signed up.
- Conversion: a visitor doing the thing you care about, like signing up or paying.
Step 1: Write down 3 questions and 3 events
Most beginners install a dashboard, look at a pretty traffic chart, and learn nothing. Start with the questions instead. For an early SaaS, three are enough:
- Which channel brings visitors who actually sign up?
- Which channel brings signups who get value (their "first win")?
- Which channel brings paying customers?
Each question needs one event. Keep the names short, lowercase and boring, and never rename them later.
EVENT PLAN (copy into your notes)
signup -> account created successfully
first_win -> user did the core thing once (e.g. created first project)
paid -> first successful payment
Rule: lowercase, underscores, max 3-5 events for now.
If you already track an activated_at date from an onboarding setup (like in our onboarding emails guide), your first_win event is the same moment.
Step 2: Pick one simple tool (and know its limits)
You do not need Google Analytics 4 to answer three questions. A lightweight, privacy-friendly tool is easier to read. Here is what the main options include, as of October 2026, from each vendor's own pricing or docs page:
- Umami Cloud Hobby: $0, 100K events per month, 1 website, 6 months of data. Includes custom events, UTM reports, goals, funnels and an attribution report. Umami says it uses no cookies, so no cookie banner is needed for it. Note that every pageview and every stored event property counts toward the 100K.
- Plausible: 30-day free trial, then Starter at $9/month for up to 10k monthly pageviews, with goals and custom events. No cookies.
- PostHog: free tier with 1M analytics events per month, no card required. It sets cookies by default, but has a cookieless mode you can switch on.
- Vercel Web Analytics (Hobby plan): free, but custom events need the Pro plan and UTM parameters need the Web Analytics Plus add-on. So on Hobby you cannot track signups or UTMs.
- Lovable's built-in Analytics: free and cookieless, groups visits into channels like Social, Referral and Direct. But Lovable's own docs say that to track steps like sign-up and purchase you should connect a dedicated tool such as PostHog.
This guide uses Umami because the free plan covers everything here. The same ideas work in Plausible or PostHog; only the one-line tracking call changes.
Step 3: Install it on your real domain only
Create a free Umami Cloud account, add your website, and copy the tracking snippet it gives you. It looks like this. The data-domains attribute tells Umami to only count visits on the domains you list, so your localhost and preview URLs do not pollute the numbers.
For Vite apps (most Lovable and Bolt projects), paste it inside <head> in index.html:
<script defer src="https://cloud.umami.is/script.js"
data-website-id="YOUR-WEBSITE-ID"
data-domains="yourapp.com,www.yourapp.com"></script>
For Next.js (common with v0 and Cursor), use next/script in app/layout.tsx:
import Script from "next/script";
// inside <body> in your root layout
<Script
src="https://cloud.umami.is/script.js"
data-website-id="YOUR-WEBSITE-ID"
data-domains="yourapp.com"
strategy="afterInteractive"
/>
Umami's docs say single-page apps are tracked automatically when the URL changes, so you do not need extra code for page navigation. Or hand it to your AI tool:
Add the Umami analytics script to this app so it loads on every page.
Script: <paste the snippet from your Umami dashboard>
Rules: add it once only (index.html head for Vite, or next/script in
the root layout for Next.js). Keep data-domains so it only tracks
yourapp.com. Do not add any other analytics or cookies.
Show me the exact file you changed.
Then open your live site, open DevTools, go to the Network tab, and check that script.js loads. If it does not show up, try with your ad blocker off; Umami's docs note ad blockers can block it.
Step 4: Track the signup at the moment it succeeds
The most common mistake is firing "signup" when someone clicks the button. Clicks fail: wrong password format, email already used, network error. Fire the event only after your auth call returns without an error.
With Supabase auth it looks like this:
const { data, error } = await supabase.auth.signUp({ email, password });
if (!error) {
window.umami?.track("signup");
}
Do the same for first_win right after the core action saves successfully, and for paid on your payment success page or webhook-confirmed screen. If you use PostHog instead, the line is posthog.capture("signup"). In Plausible it is plausible("signup"), and you must also create a goal with the exact same name in the dashboard, because Plausible does not show events without a matching goal.
Never put emails or names into event data. Umami's docs warn against sending personal information, and you do not need it to answer your three questions.
Step 5: Save each user's first source in your database
Analytics tools show trends, but they cannot tell you "user 47 came from Reddit and paid". For that, store the source on the user record. Two parts:
Part A: remember where they first came from. This runs on every page load and only saves the first visit's source:
// src/lib/firstTouch.js
const KEY = "first_touch";
export function saveFirstTouch() {
if (localStorage.getItem(KEY)) return;
const p = new URLSearchParams(window.location.search);
let ref = "";
if (document.referrer && !document.referrer.includes(window.location.hostname)) {
ref = new URL(document.referrer).hostname;
}
localStorage.setItem(KEY, JSON.stringify({
signup_source: p.get("utm_source") || ref || "direct",
signup_medium: p.get("utm_medium") || "",
signup_campaign: p.get("utm_campaign") || "",
landing_page: window.location.pathname,
}));
}
export function getFirstTouch() {
return JSON.parse(localStorage.getItem(KEY) || "{}");
}
Part B: attach it at signup. Supabase lets you pass extra user metadata in signUp, and it is stored on the user:
const { data, error } = await supabase.auth.signUp({
email,
password,
options: { data: getFirstTouch() },
});
For Google sign-in, call supabase.auth.updateUser({ data: getFirstTouch() }) once after the first login if signup_source is missing. Users can edit their own metadata, so treat it as a marketing hint, never for permissions.
Also add one optional dropdown to your signup form: "How did you hear about us?" with 5-6 options (Google, Reddit, X, a friend, a newsletter, other). It catches the people your links cannot: the friend who sent your URL on WhatsApp shows up as "direct" in analytics, but the user will happily tell you "a friend".
AI PROMPT
Add an optional "How did you hear about us?" select to the signup
form with options: google, reddit, x, friend, newsletter, other.
Save the value as heard_from in the Supabase signUp options.data,
together with the object returned by getFirstTouch().
Call saveFirstTouch() once when the app loads.
Do not make the field required.
A privacy note: you are storing a small marketing value in the browser and on the user record. Mention it in your privacy policy, and if you have EU users, check whether your consent setup covers it.
Step 6: Tag every link you share with UTMs
Referrers get lost all the time. UTMs fix that for links you control. Google's own help page says to always use utm_source, utm_medium and utm_campaign, and warns that values are case-sensitive, so stick to lowercase.
LINK TEMPLATE
https://yourapp.com/?utm_source=SOURCE&utm_medium=MEDIUM&utm_campaign=CAMPAIGN
SOURCE = the exact place: reddit, x, linkedin, newsletter, youtube
MEDIUM = the type: social, community, email, referral, video
CAMPAIGN = what you were doing: launch_oct, tutorial_post, bio_link
Rules:
- lowercase only, underscores instead of spaces
- one name per platform forever (always "x", never "twitter" or "X")
- one row per link in a sheet: date, where posted, full link, notes
Put a tagged link in your X bio, your newsletter, your Dev.to profile and every community post. Keep a simple sheet of every link so the numbers make sense later. If you use a link shortener or redirect, check that the final URL still has the ?utm_... part, because Plausible's docs note UTMs are lost if a redirect drops the query string.
Step 7: Test it, then review one table every week
First, test end to end:
- Open your site in a private window with
?utm_source=test&utm_medium=test&utm_campaign=test. - Sign up with a throwaway email.
- Check Umami: you should see the visit under UTM and one
signupevent. - Check Supabase: the new user's metadata should show
signup_source: "test".
Then, once a week, run this in the Supabase SQL editor:
select
coalesce(raw_user_meta_data->>'signup_source', 'unknown') as source,
coalesce(raw_user_meta_data->>'heard_from', '-') as heard_from,
count(*) as signups
from auth.users
where created_at > now() - interval '7 days'
group by 1, 2
order by signups desc;
Compare it with Umami's numbers. Your database count is often higher, because ad blockers can stop analytics scripts from loading. Trust the database for "how many", and use analytics for "which pages and links".
Spend 15 minutes on it and make one decision. Hypothetical example: if Reddit sent 400 visitors and 3 signups while a single tutorial post sent 90 visitors and 6 signups, write one more tutorial this week instead of five more Reddit comments. One founder in r/SaaSMarketing shared self-reported numbers like this in May 2026, where a smaller SEO channel produced more paying users than a bigger Reddit one. Your numbers will differ; that is the point of measuring.
Common mistakes
-
Tracking the click, not the success. Fire
signuponly when the auth call returns no error. -
Counting your own visits. Use
data-domainsand test on a private window, not your daily browser. - Messy UTM names. "Twitter", "twitter" and "x" become three sources. Lowercase, one name per platform.
- Expecting UTMs to catch everything. DMs, emails and bookmarks still show as direct. That is what the "How did you hear about us?" field is for.
- Putting personal data in events. No emails or names in analytics.
- Picking a free plan that cannot do the job. Vercel Hobby has no custom events or UTMs; Lovable's built-in analytics does not track signups.
- Looking daily and changing everything. With small numbers, one week is the minimum before you decide anything.
Your action summary
- Write your 3 questions and 3 events:
signup,first_win,paid. - Pick one tool (Umami Hobby is free as of October 2026) and create your site.
- Install the script with
data-domainsand confirm it loads in DevTools. - Fire
signuponly after Supabase returns no error, plusfirst_winandpaid. - Save first-touch source and "How did you hear about us?" on the user record.
- Tag every shared link with lowercase UTMs and log it in a sheet.
- Test with
utm_source=test, then run the weekly SQL and make one decision.
You do not need a data team to stop guessing. Three events, one stored source and one weekly table will tell you more about your growth than any traffic chart, so set it up today and let next week's numbers pick your next move.
來源:DEV Community · dev.to