Proxying the tracking script
Some ad blockers and privacy extensions block requests to known analytics domains. By proxying the Pageviews tracking script and API calls through your own domain, you can ensure accurate data collection without being affected by these blockers.
How it works
Instead of loading the script directly from pageviews.ai, you configure your web server to proxy two things:
- The tracking script — Served from
https://pageviews.ai/{siteId}.js - The ingest API — Where the script sends analytics data, at
https://data.pageviews.ai
Your visitors' browsers will make requests to your own domain, which forwards them to Pageviews behind the scenes. This is fully transparent and does not affect data accuracy.
Step 1: Set up server-side proxying
Add the following proxy rules to your web server configuration. The examples below proxy the script at /js/tracker.js and the ingest API at /api/event, but you can use any paths you prefer.
Nginx
location /js/tracker.js {
proxy_pass https://pageviews.ai/YOUR_SITE_ID.js;
proxy_set_header Host pageviews.ai;
proxy_ssl_server_name on;
proxy_cache_valid 200 24h;
}
location /api/event {
proxy_pass https://data.pageviews.ai/api/event;
proxy_set_header Host data.pageviews.ai;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_ssl_server_name on;
}
Apache
<Location "/js/tracker.js">
ProxyPass "https://pageviews.ai/YOUR_SITE_ID.js"
ProxyPassReverse "https://pageviews.ai/YOUR_SITE_ID.js"
RequestHeader set Host "pageviews.ai"
</Location>
<Location "/api/event">
ProxyPass "https://data.pageviews.ai/api/event"
ProxyPassReverse "https://data.pageviews.ai/api/event"
RequestHeader set Host "data.pageviews.ai"
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
</Location>
Caddy
handle /js/tracker.js {
reverse_proxy https://pageviews.ai {
header_up Host pageviews.ai
rewrite /YOUR_SITE_ID.js
}
}
handle /api/event {
reverse_proxy https://data.pageviews.ai {
header_up Host data.pageviews.ai
header_up X-Forwarded-For {remote_host}
}
}
Laravel
Add two routes in your routes/web.php:
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
Route::get('/js/tracker.js', function () {
$script = Cache::remember('pageviews_script', 86400, function () {
return Http::get('https://pageviews.ai/YOUR_SITE_ID.js')->body();
});
return response($script, 200)
->header('Content-Type', 'application/javascript');
});
Route::post('/api/event', function () {
$response = Http::withHeaders([
'X-Forwarded-For' => request()->ip(),
])->withBody(
request()->getContent(), 'application/json'
)->post('https://data.pageviews.ai/api/event');
return response($response->body(), $response->status())
->header('Content-Type', 'application/json');
});
Next.js (next.config.js)
module.exports = {
async rewrites() {
return [
{
source: '/js/tracker.js',
destination: 'https://pageviews.ai/YOUR_SITE_ID.js',
},
{
source: '/api/event',
destination: 'https://data.pageviews.ai/api/event',
},
];
},
};
Nuxt (nuxt.config.ts)
export default defineNuxtConfig({
routeRules: {
'/js/tracker.js': {
proxy: 'https://pageviews.ai/YOUR_SITE_ID.js',
},
'/api/event': {
proxy: 'https://data.pageviews.ai/api/event',
},
},
});
Netlify (_redirects)
/js/tracker.js https://pageviews.ai/YOUR_SITE_ID.js 200
/api/event https://data.pageviews.ai/api/event 200
Cloudflare Workers
export default {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === '/js/tracker.js') {
return fetch('https://pageviews.ai/YOUR_SITE_ID.js', {
headers: { Host: 'pageviews.ai' },
});
}
if (url.pathname === '/api/event') {
const newRequest = new Request(
'https://data.pageviews.ai/api/event',
request
);
newRequest.headers.set('Host', 'data.pageviews.ai');
newRequest.headers.set('X-Forwarded-For',
request.headers.get('CF-Connecting-IP'));
return fetch(newRequest);
}
return fetch(request);
},
};
Step 2: Update your tracking snippet
Replace the default script tag with your proxied path and add the data-api attribute to point the ingest calls to your proxy:
<script src="/js/tracker.js" data-api="/api/event" async></script>
Using the NPM module with a proxy
If you use the @pageviews/tracker NPM module, pass the endpoint option to point to your proxy:
import { init } from '@pageviews/tracker'
init({
siteId: 'your-site-id',
endpoint: '/api/event',
})
Important notes
- X-Forwarded-For header — Make sure your ingest proxy passes the visitor's real IP address via the
X-Forwarded-Forheader. Pageviews uses this to determine the visitor's country (the IP address is never stored). Without it, all visitors will appear to come from your server's location. - HTTPS required — Both the script and ingest proxies must be served over HTTPS.
- Caching the script — You can safely cache the tracking script for up to 24 hours. It is updated infrequently and is tied to your site configuration.
- No data difference — Proxied and direct requests produce identical analytics data. The only difference is the request path seen by the browser.
- Replace YOUR_SITE_ID — In all examples above, replace
YOUR_SITE_IDwith your actual site ID from the Pageviews dashboard.