A personal site has a small audience and one or two questions: who found it, and did they reach the page that matters. You do not need an enterprise suite, a consent banner or a monthly bill to answer them.
A portfolio needs five numbers: visitors, top pages, where they came from, how far they scrolled, and whether anyone clicked the contact link. Everything else is decoration for a site with a few hundred visits a month.
<head>, about 3 KB compressed on the wire. It works on any host and any static generator.utm_campaign on the link in your resume or email tells you which application produced a visit.Enterprise analytics is built for teams with funnels, ad budgets and a dozen stakeholders. A portfolio has a different shape. One person owns it, traffic arrives in bursts (a job application, a conference talk, a post that lands on a forum), and the useful question is almost always some version of "did the thing I sent someone to get seen?"
That shapes the requirements. You want a tool that is cheap or free, because a site with no revenue should not carry a recurring bill. You want nothing to maintain: no server, no database, no upgrades. You want no consent banner, because a cookie prompt on a three-page portfolio is a worse first impression than any number the tool will ever show you. And you want enough depth to answer a few specific questions, which is a much shorter list than the feature grid of a big platform suggests.
The questions worth answering, in order of how often they come up:
Anything past that list is optional. For the general case, see how to measure website traffic.
Most portfolio owners install GA4 because it is the first result and it is free. The costs show up later. GA4 sets cookies by default, so serving visitors in the EU or UK generally means a consent banner. The gtag.js script measured about 150 KB compressed in our own test, against roughly 3 KB for the GhostMetrics script, which matters on a site whose main selling point may be that it is fast. And the interface is built around properties, streams, audiences and explorations, which is a lot of vocabulary for a site with a handful of pages.
GA4 does have real strengths: Google Ads attribution, ecommerce funnels, audiences and a free BigQuery export. If your personal site sells a product through ads, those matter. For a portfolio, a blog or a freelance landing page, you are carrying the weight without using the strengths. The longer breakdown is in Google Analytics alternatives, and if the consent angle is what bothers you, web analytics without a consent banner goes through it in detail.
These four cover most of what a personal site owner shortlists. The table sticks to well-established facts about how each tool is built and sold.
| Tool | Price model | Hosting | Cookieless | Custom events | UTM breakdown | Effort |
|---|---|---|---|---|---|---|
| GhostMetrics | Free plan for one site, unlimited pageviews, no card. Pro $9.99/mo flat for more sites and a public dashboard | Hosted only, not open source | Yes | Yes (data-gm-event or ghostmetrics.track) | Yes (source, medium, campaign) | One script tag |
| Cloudflare Web Analytics | Free | Hosted only, not open source | Yes | No | No | A beacon tag, or automatic if the site is proxied through Cloudflare |
| Plausible | No free plan; 30-day trial without a card, then paid plans tiered by monthly pageviews, from $9/month | Hosted (EU) or self-hosted Community Edition; open source | Yes | Yes | Yes | One script tag; official WordPress plugin |
| Umami | Free to self-host (you pay for the server); Umami Cloud is the hosted option, check current pricing | Self-hosted or Umami Cloud; open source (MIT) | Yes | Yes | Yes | Server and database to run if self-hosting |
Cloudflare Web Analytics is the best zero-cost backstop if your site already sits behind Cloudflare. It reports visits, pageviews, top pages, referrers, countries, browsers, devices and Core Web Vitals. What it lacks is exactly what a portfolio owner eventually wants: custom events (no way to count the "Contact" click), a UTM breakdown (no way to see which application link worked) and a CSV button. History is also limited, so check current Cloudflare docs for the window. We cover the gap in Cloudflare Web Analytics alternatives.
Plausible is the right call if you want open source and EU-only processing. It has no free plan, so a personal site pays from the start, though the starting tier is priced for small traffic. See Plausible alternatives for where it fits and where it does not.
Umami suits people who enjoy running their own infrastructure and want the data on a machine they control. It is free in license, not in labor: you provide the server, apply upgrades and take backups. For a portfolio that is a hobby project in itself, which is fine if that is the point. Our Umami alternatives page covers the trade.
GhostMetrics is ours, so weigh this accordingly. It is the only one of the four that combines a free hosted plan, custom events, UTM breakdown and real-user Core Web Vitals (FCP, LCP, CLS and TTFB; INP is not captured). It is not open source and not self-hostable, which rules it out if either is a requirement for you.
The install is the same on every host, because the tool is a single script tag. This is the whole procedure for a portfolio on GitHub Pages, Netlify, Vercel, Cloudflare Pages or plain hosting.
<head>, replacing YOUR-SITE-ID with your own ID. In a static generator put it in the shared layout, so every page inherits it. The snippet is below.data-gm-event to the two or three elements that matter: contact link, resume download, project links. No further code is needed.<head>
<!-- ...your existing tags... -->
<script defer src="https://ghostmetrics.nullagency.io/gm.js" data-site="YOUR-SITE-ID"></script>
</head>
If you use Astro, mark the tag is:inline so the data-site attribute survives the build. In Next.js App Router, a plain <script defer ...> element in the root layout.tsx head works. If the site is a single-page app, route changes through pushState and popstate are counted as new pageviews automatically. More host-specific snippets live in how to add analytics to a static site.
One limit worth knowing before you start: a hosted site builder that does not let you edit the head or add custom code cannot run any script-based analytics tool. If that is your situation, check your builder's current docs for header injection or custom code, and if it is not offered, the builder's own built-in stats are the only option.
Two small additions turn a visitor counter into something that answers real questions. Both take minutes.
When you apply for a role, post in a community or sign an email, the link you share is a chance to label the visit. GhostMetrics reads utm_source, utm_medium and utm_campaign from the landing URL and keeps the first set it sees in that tab. The UTM Breakdown table then lists source, medium, campaign and visits. A scheme that stays readable:
| Where the link lives | utm_source | utm_medium | utm_campaign |
|---|---|---|---|
| Resume PDF | resume | job-search | |
| Cold email to a studio | outreach | studio-batch-1 | |
| Conference talk slide | talk | slides | meetup-talk |
| Social profile bio | profile | evergreen |
Keep names lowercase and consistent, because Resume and resume are different strings to a table that groups on exact values. utm_term and utm_content are not stored, so do not rely on them. The UTM parameters guide covers naming conventions and common mistakes in more depth. Also note that nothing is remembered between sessions: a recruiter who clicks your resume link today and returns tomorrow from a bookmark shows up as a new session, with the second visit unattributed unless they come through a tagged link again.
Untagged visits with no referrer land in the "direct" source type. GhostMetrics cannot tell a typed URL from a stripped referrer (privacy-stripped, some messaging apps, https to http), which is why tagging your own links is the reliable fix. Direct traffic explained goes through the causes.
Pick the two or three actions that tell you the site did its job. For a portfolio that is usually the contact link, the resume download and the outbound click to a flagship project. Add an attribute to each element:
<a href="mailto:you@example.com" data-gm-event="contact-click">Email me</a>
<a href="/resume.pdf" data-gm-event="resume-download">Download resume</a>
<a href="https://github.com/you/project" data-gm-event="project-github-click">Source on GitHub</a>
A single delegated listener on the document handles these, so elements rendered after page load by a router or a modal are tracked too. For anything that is not a click, call it from code:
window.ghostmetrics.track('contact-form-submit', { section: 'contact' });
Only section is stored as metadata. Events carry the page path and any session UTM. There is no numeric value or revenue field and no per-visitor funnel across sessions, which is fine for a site where one meaningful action is a message in your inbox. The custom events guide has naming patterns that keep the Custom Events panel readable (event name, count, last fired) as the list grows.
The point of installing analytics on a personal site is to look at it sometimes and decide something. A fixed routine keeps it from turning into compulsive refreshing. Once a week, in this order:
| Panel | What to read | What to do about it |
|---|---|---|
| Visitors and pageviews | Compare this week with the last. Look for a spike and its date. | If there was a spike, find its source in the next row and note what caused it. |
| Referrers and source types | Search, social, direct, referral. Which referrer names are new? | Reply where you were mentioned. Link back from the places that send readers. |
| Entry pages | The pages people land on first. | If a blog post is your front door, put a clear next step on it: another post, your contact link. |
| UTM Breakdown | Which tagged link produced visits. | Stop spending effort on channels with zero visits. Repeat what worked. |
| Scroll depth and time on page | Whether readers get past the fold on case studies. | If most leave at the top, rewrite the opening or move the strongest image up. |
| Custom Events | Contact, resume and project clicks, with last-fired time. | A page that is read but never clicked needs a clearer call to action. |
| Core Web Vitals | Per-page FCP, LCP, CLS and TTFB from real visitors. | Fix the slowest page first. |
| Organizations | Organization-level networks the dashboard could identify. | Treat it as a hint about who is looking, not as a list. |
Two of those rows deserve a short note. The Organizations panel ("who's looking") does organization detection with residential ISPs excluded, so a visit from a company network can show up by name, and a visit from a home connection or a phone usually shows no company. It will not catch everyone.
The Core Web Vitals panel measures real visits, not a lab test. TTFB follows Google's definition (responseStart from navigation start, so it includes redirects, DNS, TCP and TLS as well as server time) and is rated against the 800 ms "good" threshold. INP is not captured. If your portfolio is image-heavy, per-page LCP is the number most likely to surprise you.
The bounce rate on the dashboard counts a single-page session in that tab, the classic definition, not GA4's engaged-session rule. On a one-page portfolio it will look high even when the visit went well, so lean on scroll depth and Custom Events instead.
For a personal site, the privacy point is practical, not philosophical. GhostMetrics sets no cookies, uses no localStorage and stores no IP addresses (they are hashed), so it collects no personal data and keeps no cross-session identifier. The only browser storage is sessionStorage holding a per-tab pageview counter and any first-touch UTM, which clears when the tab closes; scroll depth is held in page memory and sent with the leave beacon. Consent for cookie-based tools is generally required in the EU and UK, which is why many sites running a cookieless tool do without the banner. If you add other trackers, ad networks or embedded players, those carry their own obligations; do I need a cookie banner walks through the cases.
Because the data is not tied to a person, there is also little to worry about if you want to be open about your numbers. On Pro, each site has a Share button in the dashboard header with a "Make dashboard public" switch. It publishes a read-only copy of the same dashboard, every panel and every period, at a public URL with a small "Powered by GhostMetrics" banner. Viewers can read the panels and download a CSV of an individual panel, but cannot pull the raw event export or change anything. There is no per-viewer password. On the free plan the switch answers with an upgrade prompt, so a free portfolio keeps its numbers private. Share website analytics publicly covers what to expose and what to leave out, such as event names that hint at private projects.
To see what a public view looks like, open the live demo: the same view for nullagency.io's real traffic, with our own promo bar and footer added.
The free plan limit is one site, not traffic. That is the right fit for a portfolio on a single domain, including a blog that lives under the same site. Pageviews are unlimited, the dashboard has every view, and history is kept in full, so a spike from a front-page link does not trigger a paywall or a data cap.
You will want Pro at $9.99/mo flat in two situations. The first is that you run more than one site: a portfolio on one domain and a separate blog, a side project or a client landing page on another. Each site gets its own ID and snippet, a site switcher in the dashboard, and Pro covers unlimited websites (fair-use ceiling of 250) under one login. The second is that you want the public dashboard described above. Pro has a 30-day free trial; a card is required to start it, nothing is charged for 30 days, and you can cancel anytime. That is all Pro adds. Raw CSV and JSON export, real-time data, every dashboard view and full history are already on the free plan.
It is also worth being clear about what GhostMetrics does not do, so you can decide whether it fits. There are no heatmaps, no session recordings, no A/B testing, no emailed reports and no revenue field. It does not know which CMS you use, so there is no post-type or author breakdown. It does not follow a visitor across sessions. If your personal site is really a small store running paid ads, GA4's attribution is the honest recommendation; the migration guide lists exactly what carries over and what does not.
GhostMetrics is cookieless web analytics with one script tag, about 3 KB compressed, and no cookies, no localStorage and no stored IP addresses. The free plan is free forever with no card: one site, unlimited pageviews, the real-time dashboard, every view, full history, plus raw CSV or JSON export on every plan. We use it on nullagency.io.
Need more than one site or a public dashboard URL? Pro is $9.99/mo flat with unlimited websites (fair-use ceiling of 250) and a 30-day free trial; a card starts the trial, nothing is charged for 30 days, and you can cancel anytime. Custom events work with data-gm-event="contact-click" on any element or window.ghostmetrics.track('name', { section: 'contact' }) from code.
Disclosure: GhostMetrics is built by Null Agency and we use it on this site, so we're not a neutral party about our own product. We've kept the claims to what the free and paid plans actually do and pointed to Plausible, Umami and Cloudflare Web Analytics where they genuinely fit better. Pricing for third-party products can change; always check the vendor's current pricing page before deciding.