Bypassing Ad Blockers with a Rudderstack Proxy using Vercel and Next.js 13
Apr 05, 2023
When using Rudderstack to collect and send data to different destinations, you might run into issues with ad blockers that prevent the collection of JavaScript-based page views. In this blog post, I’ll walk through how to create a proxy for Rudderstack using Verceland Next.js 13 with app directory to get around the problem.
1. Installing the SDK:
First, install the Rudderstack SDK package in your project.
npm install rudder-sdk-js --save
2. Initializing Rudderstack:
Next, initialize Rudderstack and set an API for the data plane and the config. Make sure to wrap this in an async function to avoid calling the window when running in Node.
const WRITE_KEY = process.env.NEXT_PUBLIC_RUDDERSTACK_WRITE_KEY;
const DATA_PLANE_URL = "/api/rudderstack-proxy";
export async function rudderInitialize(callback: () => void) {
// @ts-ignore
window.rudderanalytics = await import("rudder-sdk-js");
// @ts-ignore
window.rudderanalytics.load(WRITE_KEY, DATA_PLANE_URL, {
configUrl: "/api/rudderstack-proxy/sourceConfig",
});
// @ts-ignore
window.rudderanalytics.ready(callback);
}
3. Wrapping the Initialize in a Provider:
Wrap the initialize in a provider that runs only on the client. This component sets some context so you can access the Rudderstack analytics from the context instead of the window directly.
"use client";
import { rudderInitialize } from "@/utils/rudder-initialize";
import { createContext, useContext, useEffect, useState } from "react";
export type RudderStackProviderContext = {
rudderClient: any;
};
const Context = createContext<RudderStackProviderContext>({
rudderClient: null,
});
export default function RudderStackProvider({
children,
}: {
children: React.ReactNode;
}) {
const [ready, setReady] = useState(false);
useEffect(() => {
rudderInitialize(() => {
setReady(true);
});
}, []);
if (!ready) {
return null;
}
return (
<Context.Provider value={{ rudderClient: (window as any).rudderanalytics }}>
<>{children}</>
</Context.Provider>
);
}
export const useRudderStack = () => useContext(Context);
4. Building Two APIs:
Now, we need to create two APIs. One for tracking and the other for the source config.
Tracking API:
The tracking API is a POST request that proxies all the data. Make sure the request method is one of the allowed methods.
import { NextRequest } from "next/server";
const DATA_PLANE_URL = process.env.RUDDERSTACK_DATA_PLANE_URL;
export async function POST(
request: NextRequest,
{ params }: { params: { slug: string[] } }
) {
const method = params.slug[1];
const allowedMethods = [
"identify",
"track",
"page",
"screen",
"group",
"alias",
"ready",
];
if (!allowedMethods.includes(method)) {
console.log("Invalid method:", method); // Log invalid method
return new Response(null, { status: 500 });
}
try {
const body = await request.json();
console.log("Request body:", body); // Log request body
// build a url of the DATA_PLANE_URL plus the slug combined together
const url = DATA_PLANE_URL + "/" + params.slug.join("/");
console.log("Constructed URL:", url); // Log constructed URL
const headers = new Headers();
// Filter out and set only the headers we need to send to the data plane
[
"anonymousid",
"authorization",
"content-type",
"cookie",
"user-agent",
].forEach((header) => {
if (request.headers.get(header)) {
headers.set(header, request.headers.get(header)!);
}
});
console.log("Filtered headers:", headers); // Log filtered headers
const response = await fetch(url, {
//
method: "POST",
// proxy the request headers
headers: headers,
// proxy the body
body: JSON.stringify(body),
});
console.log("Response:", response); // Log response
// forward the response back to the client
return response;
} catch (error) {
console.log("Error:", error); // Log error
return new Response(null, { status: 500 });
}
}
Source Config API:
The source config API checks the configuration from the source.
import { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
try {
const params = new URLSearchParams(new URL(request.url).search);
// make a request to the data plane
const url = process.env.RUDDERSTACK_SOURCE_CONFIG_URL + "?" + params;
console.log("Constructed URL:", url); // Log constructed URL
// copy headers [authorization]
const headers = new Headers();
["authorization"].forEach((header) => {
if (request.headers.get(header)) {
headers.set(header, request.headers.get(header)!);
}
});
console.log("Filtered headers:", headers); // Log filtered headers
const response = await fetch(url, {
method: "GET",
headers: headers,
});
console.log("Response:", response); // Log response
// forward the response back to the client
return response;
// return the response
} catch (error) {
console.log("Error:", error); // Log error
return new Response(null, { status: 500 });
}
}
5. Testing the Proxy:
To test the proxy, open the browser and check that the request to api.rudderlabs.com is blocked. Use request blocking if you want to simulate an Ad Blocker.
After reloading the page, you should see the source config data from the API.
Now, trigger some events (use the JavaScript console and window.rudderAnalytics.track()) and check that the page request and track get a 200 on the backend.
Finally, go to Rudderstack’s live events monitor and verify that your events appear there.

By following these steps, you can create a proxy for Rudderstack using Vercel and Next.js 13 that bypasses ad blockers, allowing you to collect JavaScript-based page views and event data without interruption.