+ Ad blockers
+
+ The tracker already sends events to neutral URLs for this site, which most blocker rules don't match. Some blockers block the Supermetry domain itself.
+ To count those visitors too, serve tracking from your own domain.
+
+
+ Option 1: Proxy through your own domain (recommended)
+
+ Your server forwards the tracking requests under /sm/ to {trackerHost(input.scriptUrl)}. Blockers then only see your own domain.
+
+
+ {managed ? (
+ -
+
{++step}. Create the site's proxy key
+
+ With the key, Supermetry Cloud counts each visitor's own location instead of your server's. Keep it on the server only.
+
+ Create the proxy key
+
+ ) : null}
+ -
+
{++step}. Add the proxy to your server
+
+
+ {recipes.map((recipe) => {recipe.label})}
+
+ {recipes.map((recipe) => (
+
+ ))}
+
+
+ -
+
{++step}. Replace the snippet on your pages
+ Load the script from your own domain. With the Next.js proxy, render the component with host="" instead.
+ {tag}
+
+
+
+
+ Option 2: Send events from your server
+
+ Events your backend sends can't be blocked, but they miss what only the browser knows: screen size, engagement time, scroll depth and session replay.
+ Use them alongside the script. See From your server in the install recipes above.
+
+
+ )
+}
diff --git a/web/src/features/settings/SettingsPages.tsx b/web/src/features/settings/SettingsPages.tsx
index 7ce78b5a..aa3ca801 100644
--- a/web/src/features/settings/SettingsPages.tsx
+++ b/web/src/features/settings/SettingsPages.tsx
@@ -426,6 +426,7 @@ export function InstallationPage({
refetchInterval: (query) => installationStatusInterval(query.state.data?.install_status),
enabled: !isDemo,
});
+ const config = useInstanceConfig();
const snippet = useQuery({
queryKey: ["site-snippet", orgId, siteId],
queryFn: () =>
@@ -455,6 +456,7 @@ export function InstallationPage({
snippetLoading={snippet.isPending}
snippetFailed={snippet.isError}
onRetrySnippet={() => void snippet.refetch()}
+ managed={config.data?.managed === true}
verify={async () => {
const result = await status.refetch();
return { status: result.data?.install_status, lastSeenAt: result.data?.last_seen_at };
diff --git a/web/src/features/settings/SiteSettings.tsx b/web/src/features/settings/SiteSettings.tsx
index e26e00f7..32775fa4 100644
--- a/web/src/features/settings/SiteSettings.tsx
+++ b/web/src/features/settings/SiteSettings.tsx
@@ -7,7 +7,7 @@ import type { Role } from '@/components/Sidebar'
import { Button } from '@/components/ui/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { refetchSiteIcon } from '@/features/sites/api'
-import { AiInstallPrompt, InstallRecipes, SiteDetails, type VerifyResult } from './InstallRecipes'
+import { AdBlockerSetup, AiInstallPrompt, InstallRecipes, SiteDetails, type VerifyResult } from './InstallRecipes'
import { TrackingSnippet } from './TrackingSnippet'
import { InstallStatusCard, InstallTroubleshooting, type InstallStatus } from '@/features/sites/InstallStatus'
import { uiFlows } from '@/messages/uiFlows'
@@ -169,8 +169,10 @@ function FeatureSetup({ currency }: { currency?: string }) {
)
}
-export function InstallGuide({ publicId, snippet, fullSnippet, installStatus, lastSeenAt, revenueCurrency, verify, now = new Date(), dashboardHref, domainsHref, snippetLoading = false, snippetFailed = false, onRetrySnippet }: {
+export function InstallGuide({ publicId, snippet, fullSnippet, installStatus, lastSeenAt, revenueCurrency, verify, now = new Date(), dashboardHref, domainsHref, snippetLoading = false, snippetFailed = false, onRetrySnippet, managed = false }: {
publicId: string; snippet?: string; fullSnippet?: string; installStatus?: InstallStatus; lastSeenAt?: string | null; revenueCurrency?: string
+ /** Managed (Cloud) instance: the proxy recipes use the site's proxy key. */
+ managed?: boolean
verify?: () => Promise