Configuración de proxy
Los bloqueadores de anuncios a veces bloquean scripts de analíticas de terceros. Al hacer proxy de BetterMeter a través de su propio dominio, el script y los eventos aparecen como solicitudes de primera parte -- haciéndolos invisibles para los bloqueadores. Esto funciona porque el navegador ve solicitudes a yourdomain.com/bm/... en lugar de bettermeter.com/api/....
Cómo funciona
Configure reescrituras de URL en su servidor para que tres rutas de su dominio redirijan a BetterMeter:
/bm/s→ https://bettermeter.com/api/sScript de rastreo/bm/e→ https://bettermeter.com/api/eEndpoint de eventos/bm/p→ https://bettermeter.com/api/pPixel noscriptLuego actualice su código de rastreo para usar las rutas del proxy:
<script defer data-site="example.com" data-api="/bm/e" src="/bm/s"></script>
<img src="/bm/p?s=example.com" alt="" style="position:absolute;width:0;height:0;overflow:hidden" />El atributo data-api le indica al script que envíe eventos a su endpoint proxy en lugar de directamente a BetterMeter. El src carga el script desde su proxy. Para el navegador (y los bloqueadores de anuncios), todo es una solicitud del mismo origen.
Guías por plataforma
Next.js
module.exports = {
async rewrites() {
return [
{ source: "/bm/s", destination: "https://bettermeter.com/api/s" },
{ source: "/bm/e", destination: "https://bettermeter.com/api/e" },
{ source: "/bm/p", destination: "https://bettermeter.com/api/p" },
];
},
};Nuxt
export default defineNuxtConfig({
routeRules: {
"/bm/s": { proxy: "https://bettermeter.com/api/s" },
"/bm/e": { proxy: "https://bettermeter.com/api/e" },
"/bm/p": { proxy: "https://bettermeter.com/api/p" },
},
});SvelteKit
const config = {
kit: {
// SvelteKit doesn't have built-in rewrites.
// Use a server hook instead:
},
};import type { Handle } from "@sveltejs/kit";
const PROXY_MAP: Record<string, string> = {
"/bm/s": "https://bettermeter.com/api/s",
"/bm/e": "https://bettermeter.com/api/e",
"/bm/p": "https://bettermeter.com/api/p",
};
export const handle: Handle = async ({ event, resolve }) => {
const target = PROXY_MAP[event.url.pathname];
if (target) {
const url = new URL(target);
url.search = event.url.search;
const res = await fetch(url, {
method: event.request.method,
headers: event.request.headers,
body: event.request.method !== "GET" ? event.request.body : undefined,
// @ts-expect-error — needed for streaming
duplex: "half",
});
return new Response(res.body, {
status: res.status,
headers: res.headers,
});
}
return resolve(event);
};Astro
import type { APIRoute } from "astro";
const PROXY_MAP: Record<string, string> = {
s: "https://bettermeter.com/api/s",
e: "https://bettermeter.com/api/e",
p: "https://bettermeter.com/api/p",
};
export const ALL: APIRoute = async ({ params, request }) => {
const target = PROXY_MAP[params.proxy ?? ""];
if (!target) return new Response("Not found", { status: 404 });
const url = new URL(target);
url.search = new URL(request.url).search;
return fetch(url, {
method: request.method,
headers: request.headers,
body: request.method !== "GET" ? request.body : undefined,
// @ts-expect-error — needed for streaming
duplex: "half",
});
};Remix / React Router
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
const PROXY_MAP: Record<string, string> = {
s: "https://bettermeter.com/api/s",
e: "https://bettermeter.com/api/e",
p: "https://bettermeter.com/api/p",
};
async function proxy({ request, params }: LoaderFunctionArgs | ActionFunctionArgs) {
const target = PROXY_MAP[params["*"] ?? ""];
if (!target) return new Response("Not found", { status: 404 });
const url = new URL(target);
url.search = new URL(request.url).search;
return fetch(url, {
method: request.method,
headers: request.headers,
body: request.method !== "GET" ? request.body : undefined,
// @ts-expect-error — needed for streaming
duplex: "half",
});
}
export const loader = proxy;
export const action = proxy;Nginx
location /bm/s {
proxy_pass https://bettermeter.com/api/s;
proxy_ssl_server_name on;
proxy_set_header Host bettermeter.com;
}
location /bm/e {
proxy_pass https://bettermeter.com/api/e;
proxy_ssl_server_name on;
proxy_set_header Host bettermeter.com;
}
location /bm/p {
proxy_pass https://bettermeter.com/api/p;
proxy_ssl_server_name on;
proxy_set_header Host bettermeter.com;
}Apache
RewriteEngine On
RewriteRule ^bm/s$ https://bettermeter.com/api/s [P,L]
RewriteRule ^bm/e$ https://bettermeter.com/api/e [P,L]
RewriteRule ^bm/p$ https://bettermeter.com/api/p [P,L]
# Requires mod_proxy and mod_proxy_http enabled:
# a2enmod proxy proxy_httpCaddy
example.com {
handle_path /bm/s {
reverse_proxy https://bettermeter.com/api/s {
header_up Host bettermeter.com
}
}
handle_path /bm/e {
reverse_proxy https://bettermeter.com/api/e {
header_up Host bettermeter.com
}
}
handle_path /bm/p {
reverse_proxy https://bettermeter.com/api/p {
header_up Host bettermeter.com
}
}
}Cloudflare Workers
export default {
async fetch(request) {
const url = new URL(request.url);
const map = {
"/bm/s": "https://bettermeter.com/api/s",
"/bm/e": "https://bettermeter.com/api/e",
"/bm/p": "https://bettermeter.com/api/p",
};
const target = map[url.pathname];
if (!target) return fetch(request);
const dest = new URL(target);
dest.search = url.search;
return fetch(dest.toString(), {
method: request.method,
headers: { ...Object.fromEntries(request.headers), Host: "bettermeter.com" },
body: request.method !== "GET" ? request.body : undefined,
});
},
};Vercel (without Next.js)
{
"rewrites": [
{ "source": "/bm/s", "destination": "https://bettermeter.com/api/s" },
{ "source": "/bm/e", "destination": "https://bettermeter.com/api/e" },
{ "source": "/bm/p", "destination": "https://bettermeter.com/api/p" }
]
}Netlify
[[redirects]]
from = "/bm/s"
to = "https://bettermeter.com/api/s"
status = 200
force = true
[[redirects]]
from = "/bm/e"
to = "https://bettermeter.com/api/e"
status = 200
force = true
[[redirects]]
from = "/bm/p"
to = "https://bettermeter.com/api/p"
status = 200
force = trueWordPress
Agregue estas reglas de reescritura al functions.php de su tema o a un plugin personalizado:
add_action('init', function() {
add_rewrite_rule('^bm/s$', 'index.php?bm_proxy=s', 'top');
add_rewrite_rule('^bm/e$', 'index.php?bm_proxy=e', 'top');
add_rewrite_rule('^bm/p$', 'index.php?bm_proxy=p', 'top');
});
add_filter('query_vars', function($vars) {
$vars[] = 'bm_proxy';
return $vars;
});
add_action('template_redirect', function() {
$proxy = get_query_var('bm_proxy');
if (!$proxy) return;
$map = [
's' => 'https://bettermeter.com/api/s',
'e' => 'https://bettermeter.com/api/e',
'p' => 'https://bettermeter.com/api/p',
];
if (!isset($map[$proxy])) return;
$url = $map[$proxy];
if ($_SERVER['QUERY_STRING']) $url .= '?' . $_SERVER['QUERY_STRING'];
$args = ['method' => $_SERVER['REQUEST_METHOD'], 'timeout' => 10];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$args['body'] = file_get_contents('php://input');
$args['headers'] = ['Content-Type' => $_SERVER['CONTENT_TYPE'] ?? 'application/json'];
}
$response = wp_remote_request($url, $args);
if (is_wp_error($response)) { status_header(502); exit; }
status_header(wp_remote_retrieve_response_code($response));
foreach (wp_remote_retrieve_headers($response) as $k => $v) {
if (in_array(strtolower($k), ['content-type', 'cache-control'])) header("$k: $v");
}
echo wp_remote_retrieve_body($response);
exit;
});
// After adding, flush rewrite rules: Settings → Permalinks → SaveDjango
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("bm/<slug:endpoint>", views.bm_proxy),
]
# views.py
import requests
from django.http import HttpResponse
PROXY_MAP = {
"s": "https://bettermeter.com/api/s",
"e": "https://bettermeter.com/api/e",
"p": "https://bettermeter.com/api/p",
}
def bm_proxy(request, endpoint):
target = PROXY_MAP.get(endpoint)
if not target:
return HttpResponse("Not found", status=404)
url = target + ("?" + request.META["QUERY_STRING"] if request.META.get("QUERY_STRING") else "")
resp = requests.request(
method=request.method, url=url,
headers={"Host": "bettermeter.com", "Content-Type": request.content_type},
data=request.body if request.method == "POST" else None,
timeout=10,
)
return HttpResponse(resp.content, status=resp.status_code, content_type=resp.headers.get("Content-Type"))Rails
# config/routes.rb
match "/bm/:endpoint", to: "bm_proxy#proxy", via: [:get, :post], constraints: { endpoint: /[sep]/ }
# app/controllers/bm_proxy_controller.rb
class BmProxyController < ApplicationController
skip_before_action :verify_authenticity_token
PROXY_MAP = { "s" => "/api/s", "e" => "/api/e", "p" => "/api/p" }.freeze
def proxy
path = PROXY_MAP[params[:endpoint]]
return head(:not_found) unless path
uri = URI("https://bettermeter.com#{path}")
uri.query = request.query_string.presence
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = (request.post? ? Net::HTTP::Post : Net::HTTP::Get).new(uri)
req["Host"] = "bettermeter.com"
if request.post?
req.body = request.body.read
req["Content-Type"] = request.content_type
end
resp = http.request(req)
render body: resp.body, status: resp.code.to_i, content_type: resp["Content-Type"]
end
endVerificación
Después de configurar el proxy, verifique que funcione:
# Should return the tracker JavaScript
curl -s https://yourdomain.com/bm/s | head -5
# Should return 202 (event accepted)
curl -s -o /dev/null -w "%{http_code}" -X POST https://yourdomain.com/bm/e \
-H "Content-Type: application/json" \
-d '{"site_id":"yourdomain.com","event_name":"test"}'