Drop-in analytics for any website. No cookies, no consent banners, fully GDPR-compliant. Takes under 2 minutes.
Choose the method that fits your stack. All three give you the same data in your dashboard.
Add this before the closing </head> or </body> tag. That's it — it auto-detects your domain and tracks pageviews immediately.
<script
src="https://analytics.vnoc.com/tracker.js"
defer
></script>
If your site serves multiple subdomains and you want all traffic grouped under one domain name, use data-domain:
<script
src="https://analytics.vnoc.com/tracker.js"
data-domain="yourdomain.com"
defer
></script>
If you're self-hosting or using a custom worker URL, set data-endpoint:
<script
src="https://analytics.vnoc.com/tracker.js"
data-endpoint="https://analytics.vnoc.com"
data-domain="yourdomain.com"
defer
></script>
Pageviews, SPA route changes (pushState/popstate), referrer, screen size, language, country, device type, and browser. All without cookies.
npm install vnoc-analytics
Call init() once when your app loads. For React, put it in your root component or entry file. For Next.js, use _app.js.
import { init } from 'vnoc-analytics';
init({
endpoint: 'https://analytics.vnoc.com',
});
import { init } from 'vnoc-analytics';
init({
endpoint: 'https://analytics.vnoc.com', // Required
domain: 'yourdomain.com', // Override auto-detected domain
autoTrack: true, // Track on init (default: true)
trackSPA: true, // Track route changes (default: true)
honorDNT: false, // Respect Do Not Track (default: false)
});
If you set autoTrack: false, you can fire pageviews manually:
import { init, trackPageview } from 'vnoc-analytics';
init({ endpoint: 'https://analytics.vnoc.com', autoTrack: false });
// Track when ready
trackPageview();
// Track a custom path
trackPageview({ path: '/virtual/thank-you' });
// Force re-track same path
trackPageview({ force: true });
React (useEffect)
import { useEffect } from 'react';
import { init } from 'vnoc-analytics';
export default function App() {
useEffect(() => {
init({ endpoint: 'https://analytics.vnoc.com' });
}, []);
return <div>...</div>;
}
Next.js (_app.js)
import { useEffect } from 'react';
import { init } from 'vnoc-analytics';
export default function MyApp({ Component, pageProps }) {
useEffect(() => {
init({ endpoint: 'https://analytics.vnoc.com' });
}, []);
return <Component {...pageProps} />;
}
Vue 3 (main.js)
import { createApp } from 'vue';
import { init } from 'vnoc-analytics';
import App from './App.vue';
init({ endpoint: 'https://analytics.vnoc.com' });
createApp(App).mount('#app');
For email newsletters, AMP pages, or environments where JavaScript can't run, embed a 1x1 transparent GIF:
<img
src="https://analytics.vnoc.com/pixel.gif?d=yourdomain.com&p=/newsletter/may"
width="1"
height="1"
alt=""
/>
| Param | Description | Example |
|---|---|---|
| d | Domain name | yourdomain.com |
| p | Page path | /newsletter/may |
| r | Referrer (optional) | email |
If you're using the npm package, you can generate pixel URLs:
import { init, pixelUrl } from 'vnoc-analytics';
init({ endpoint: 'https://analytics.vnoc.com' });
const url = pixelUrl('yourdomain.com', '/email/welcome');
// → https://analytics.vnoc.com/pixel.gif?d=yourdomain.com&p=%2Femail%2Fwelcome
All options for the npm package init() function. The script tag only supports data-endpoint and data-domain.
| Option | Type | Default | Description |
|---|---|---|---|
| endpoint | string | — | Required. Your VNOC analytics worker URL. |
| domain | string | location.hostname | Override the auto-detected domain. Useful for grouping subdomains. |
| autoTrack | boolean | true | Automatically track a pageview when init() is called. |
| trackSPA | boolean | true | Track SPA route changes via History API (pushState, replaceState, popstate). |
| honorDNT | boolean | false | If true, disables tracking when the browser's Do Not Track flag is set. |
The /pixel.gif endpoint returns a 1x1 transparent GIF and records a pageview from the query parameters. It works with GET requests, so it can be embedded as an <img> tag anywhere — email clients, AMP pages, RSS readers, or static HTML.
The pixel tracks fewer details than the JavaScript tracker. It records domain, path, referrer, country, and device/browser from the User-Agent, but not screen size or language.
/collect with a 202 response. That means the hit was recorded.
https://analytics.vnoc.com and sign in. Your domain should appear with at least 1 pageview.
GET /api/stats/realtime?domain=yourdomain.com — it should show active_visitors: 1.
Make sure there are no ad blockers or content blockers interfering. Some block requests to /collect paths. You can verify by checking the browser console for errors.
Beyond pageviews, you can track custom events (signups, purchases, button clicks) and turn any event into a conversion goal — recorded with automatic traffic-source attribution on your dashboard.
Once the tracker loads it exposes a global window.vnoc.track(). Call it whenever something meaningful happens. Signature: track(name, category, value) — value is optional (use it for amounts).
window.vnoc.track("signup", "conversion");
window.vnoc.track("purchase", "revenue", "199"); // value = amount
Guard the call so a tracking hiccup never breaks your flow, and make sure the tracker has loaded first. Fire conversion events at the moment of success (e.g. a purchase on your payment success / thank-you page) — this must run client-side; server-side webhooks can't reach window.vnoc.
try { window.vnoc?.track?.("purchase", "revenue", "199"); } catch (e) {}
A goal maps an event (or a pageview path) to a named conversion. Once created, every matching hit is recorded as a conversion and attributed to its traffic source on /stats/yourdomain.com.
curl -X POST https://analytics.vnoc.com/api/goals -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" -d '{"domain":"yoursite.com","name":"Purchase","goal_type":"event","target":"purchase"}'
Fields: domain, name (label on the dashboard), goal_type — "event" (fires on a custom event, target = the event name) or "pageview" (fires on a page visit, target = the path). List with GET /api/goals?domain=yoursite.com; remove with DELETE /api/goals?id=<id>.
1) Fire the event (window.vnoc.track("purchase", "revenue", "199")) · 2) Create a goal with target: "purchase" · 3) Conversions + source attribution appear on your stats dashboard automatically.
Show a floating live-stats badge on your site to build social proof and urgency. Displays active visitors, today's pageviews, and today's unique visitors in real time.
This is a live widget pulling real data from the analytics API right now. Try changing the settings below.
Add one script tag — the widget handles everything else:
<script
src="https://analytics.vnoc.com/widget.js"
data-domain="yourdomain.com"
defer
></script>
| Attribute | Default | Description |
|---|---|---|
data-domain | location.hostname | Domain to show stats for |
data-position | bottom-left | bottom-left, bottom-right, top-left, or top-right |
data-theme | dark | dark or light |
data-label | online now | Custom label text after the active count |
data-endpoint | https://analytics.vnoc.com | Override if self-hosting |
<script
src="https://analytics.vnoc.com/widget.js"
data-domain="yourdomain.com"
data-position="bottom-right"
data-theme="light"
defer
></script>
<script
src="https://analytics.vnoc.com/widget.js"
data-domain="yourdomain.com"
data-label="browsing right now"
defer
></script>
The widget auto-refreshes every 30 seconds using the public /api/widget?domain=... endpoint — no API key needed.
Embed a comprehensive analytics dashboard for any domain — or your entire portfolio. Includes KPI cards, traffic trends, top pages, referrers, countries, device/browser breakdown, and Cloudflare stats.
This is a live dashboard pulling real analytics data for linkagent.com right now:
Single domain:
<iframe
src="https://analytics.vnoc.com/embed/dashboard?domain=yourdomain.com"
width="100%"
height="900"
frameborder="0"
></iframe>
Full portfolio overview (all domains):
<iframe
src="https://analytics.vnoc.com/embed/dashboard"
width="100%"
height="1200"
frameborder="0"
></iframe>
| Parameter | Default | Description |
|---|---|---|
domain | — | Filter to a single domain. Omit for full portfolio. |
theme | dark | dark or light |
The dashboard widget refreshes every 60 seconds. No authentication needed — it uses the public /api/widget/overview endpoint.
Embed a Google Search Console dashboard for any verified domain. Shows clicks, impressions, CTR, average position, top search queries, top pages, and countries — all pulled from the GSC API automatically.
This is a live widget pulling real GSC data for agentmanager.com:
<iframe
src="https://analytics.vnoc.com/embed/gsc?domain=yourdomain.com"
width="100%"
height="600"
frameborder="0"
></iframe>
| Parameter | Default | Description |
|---|---|---|
domain | — | Required. The domain to show GSC data for. |
theme | dark | dark or light |
<iframe
src="https://analytics.vnoc.com/embed/gsc?domain=yourdomain.com&theme=light"
width="100%"
height="600"
frameborder="0"
></iframe>
Fetch GSC data directly via the public API (no auth required):
GET /api/widget/gsc?domain=yourdomain.com
{
"domain": "yourdomain.com",
"clicks": 47,
"impressions": 1250,
"ctr": 3.76,
"position": 18.4,
"top_queries": [
{ "query": "your brand", "clicks": 12, "impressions": 340, "ctr": 3.5, "position": 8.2 }
],
"top_pages": [
{ "page": "https://yourdomain.com/", "clicks": 30, "impressions": 800, "ctr": 3.8, "position": 12.1 }
],
"top_countries": [
{ "country": "usa", "clicks": 25, "impressions": 600 }
],
"period": "2026-05-25 to 2026-06-24",
"updated_at": 1719200000
}
GSC data is refreshed daily via the Worker cron job. The widget caches responses for 5 minutes. No authentication needed for the public widget endpoint.
VNOC Analytics is built to be privacy-friendly by default. No consent banner required.
| Feature | Details |
|---|---|
| Cookies | None. Zero cookies are set. |
| Visitor ID | SHA-256 hash of IP + User-Agent + date + salt. Rotates daily — no cross-day tracking. |
| PII | No personal data is stored. IP addresses are never saved. |
| Country | Detected via Cloudflare's CF-IPCountry header. No GeoIP database. |
| GDPR | Compliant. No consent banner needed since no personal data is collected or stored. |
Query analytics data programmatically for your dashboards, reports, or integrations.
All API requests require a Bearer token in the Authorization header:
Authorization: Bearer YOUR_API_KEY
Contact your administrator to get an API key, or use the same key you use to access the dashboard.
https://analytics.vnoc.com
| Period | Description |
|---|---|
| 24h | Last 24 hours (hourly timeseries) |
| 7d | Last 7 days (daily timeseries) |
| 30d | Last 30 days (daily timeseries) |
| 90d | Last 90 days (daily timeseries) |
Get active visitors in the last 5 minutes.
GET /api/stats/realtime?domain=example.com
{
"domain": "example.com",
"active_visitors": 12
}
Get overview stats with timeseries data for a domain.
GET /api/stats?domain=example.com&period=7d
{
"domain": "example.com",
"period": "7d",
"pageviews": 1523,
"unique_visitors": 487,
"unique_pages": 15,
"cf_requests": 8234,
"source": "tracker",
"timeseries": [
{ "date": "2026-06-03T00:00:00.000Z", "pageviews": 201, "unique_visitors": 65 },
{ "date": "2026-06-04T00:00:00.000Z", "pageviews": 245, "unique_visitors": 78 }
]
}
source indicates data origin: tracker (tracking script), cloudflare (CF zone analytics), or domain_meta (cached stats).
Get top pages by pageviews.
GET /api/stats/pages?domain=example.com&period=7d
{
"domain": "example.com",
"pages": [
{ "path": "/", "pageviews": 523, "unique_visitors": 201 },
{ "path": "/pricing", "pageviews": 187, "unique_visitors": 142 }
]
}
Get top referrers by visits.
GET /api/stats/referrers?domain=example.com&period=7d
{
"domain": "example.com",
"referrers": [
{ "referrer": "google.com/search", "visits": 234 },
{ "referrer": "twitter.com", "visits": 87 }
]
}
Get visitor breakdown by country.
GET /api/stats/countries?domain=example.com&period=7d
{
"domain": "example.com",
"countries": [
{ "country": "US", "visits": 412, "unique_visitors": 156 },
{ "country": "GB", "visits": 89, "unique_visitors": 34 }
]
}
Get visitor breakdown by device type.
GET /api/stats/devices?domain=example.com&period=7d
{
"domain": "example.com",
"devices": [
{ "device": "desktop", "visits": 789 },
{ "device": "mobile", "visits": 234 },
{ "device": "tablet", "visits": 45 }
]
}
Get visitor breakdown by browser.
GET /api/stats/browsers?domain=example.com&period=7d
{
"domain": "example.com",
"browsers": [
{ "browser": "Chrome", "visits": 567 },
{ "browser": "Safari", "visits": 234 },
{ "browser": "Firefox", "visits": 89 }
]
}
const API_KEY = 'your-api-key';
const BASE_URL = 'https://analytics.vnoc.com';
async function fetchStats(domain, period = '30d') {
const res = await fetch(
`${BASE_URL}/api/stats?domain=${domain}&period=${period}`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);
return res.json();
}
async function fetchRealtime(domain) {
const res = await fetch(
`${BASE_URL}/api/stats/realtime?domain=${domain}`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
);
return res.json();
}
// Usage
fetchStats('example.com', '7d').then(console.log);
fetchRealtime('example.com').then(console.log);
const domains = ['site1.com', 'site2.com', 'site3.com'];
async function fetchAllStats() {
const results = await Promise.all(
domains.map(async (domain) => {
const [stats, realtime] = await Promise.all([
fetchStats(domain, '30d'),
fetchRealtime(domain),
]);
return { domain, ...stats, active: realtime.active_visitors };
})
);
return results;
}
fetchAllStats().then(console.log);
Fetch stats for up to 500 domains in a single request. Ideal for portfolio dashboards or domain list pages.
Returns 30-day pageviews, visitors, realtime counts, and Cloudflare data for multiple domains at once. Aggregates www and root domain stats automatically.
POST /api/bulk-stats
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"domains": ["appserver.com", "partneragent.com", "betanetwork.com"]
}
{
"ts": 1718700000,
"count": 3,
"results": {
"appserver.com": {
"domain": "appserver.com",
"status": "active",
"zone_id": "abc123...",
"realtime": 2,
"today_pageviews": 156,
"today_visitors": 43,
"pageviews_30d": 1270,
"visitors_30d": 266,
"tracker_pageviews_30d": 45,
"tracker_visitors_30d": 12,
"cf_requests_30d": 99687,
"cf_pageviews_30d": 1270,
"cf_visitors_30d": 266,
"dashboard_url": "https://analytics.vnoc.com/stats/appserver.com"
}
}
}
You can also use GET with comma-separated domains:
GET /api/bulk-stats?domains=site1.com,site2.com,site3.com&key=YOUR_API_KEY
| Field | Description |
|---|---|
| status | Cloudflare zone status (active, moved, unknown) |
| realtime | Active visitors in last 5 minutes |
| today_pageviews | Pageviews today (tracker data) |
| today_visitors | Unique visitors today (tracker data) |
| pageviews_30d | Best of tracker vs Cloudflare pageviews (30 days) |
| visitors_30d | Best of tracker vs Cloudflare visitors (30 days) |
| tracker_pageviews_30d | Pageviews from tracking script only |
| tracker_visitors_30d | Visitors from tracking script only |
| cf_requests_30d | Total Cloudflare requests (30 days) |
| cf_pageviews_30d | Cloudflare pageviews (30 days) |
| cf_visitors_30d | Cloudflare unique visitors (30 days) |
| dashboard_url | Direct link to the full analytics dashboard |
For large domain lists (e.g. 34K domains), paginate with 500 domains per request:
const API_KEY = 'your-api-key';
const BATCH_SIZE = 500;
async function loadBulkStats(allDomains) {
const results = {};
for (let i = 0; i < allDomains.length; i += BATCH_SIZE) {
const batch = allDomains.slice(i, i + BATCH_SIZE);
const res = await fetch('https://analytics.vnoc.com/api/bulk-stats', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ domains: batch }),
});
const data = await res.json();
Object.assign(results, data.results);
}
return results;
}
// 34K domains → 68 requests (~500ms each)
loadBulkStats(myDomainList).then(stats => {
// Populate your table with stats[domain].pageviews_30d, etc.
});
The API has no strict rate limits, but please be reasonable. For dashboards refreshing frequently, cache responses for at least 30 seconds. The bulk endpoint is designed for large domain lists — use it instead of individual requests.