Lead-gen Pixel Case Study
Caso de estudio: integración del Relo Pixel en un sitio lead-gen con Next.js.
Caso de estudio: lead-gen con pixel
Referencia para integrar el Relo Pixel en un sitio lead-gen con Next.js. Cada paso, archivo y evento descrito aquí es un ejemplo funcional en demo.example.
Why this matters. Este es un funnel lead-gen (no e-commerce, no mobile app). Demuestra que el mismo pixel que trackea compras en una marca de retail puede trackear form submissions en un sitio estático Next.js. La misma infra sirve ambos verticales sin cambios de código — solo cambia la configuración del pixel.
Flujo end-to-end del caso lead-gen
1. The stack at a glance
Component
Tech
Purpose
Site
Next.js 15 on Cloudflare Pages
Static + SPA navigation, 30+ city landing pages (EN + ES)
Pixel
p.relo.mx/r.js
Loaded once in root layout via ReloPixel component
Lead capture
Multi-step React form
Fires relo('lead', ...) on successful submit
City pages
CityViewTracker component
Fires view_product on each city landing
Attribution
t.relo.mx/c/demolead
Partner click wrapper → Redis → resolved on lead_submit
Analytics
Relo admin + partner portal
/c/quote/leads, /p/quote/leads
2. Files that matter
components/ReloPixel.tsx
Single file that owns pixel lifecycle. Three exports:
<ReloPixel />— default export, mount once inapp/layout.tsx. Loadsr.jsasync + firespage.trackReloLead({...})— call on successful form submit. Fireslead_submit.trackCityView(city, language)— call from city landing pages (viaCityViewTracker).trackLeadStageUpdate(newStage)— optional, for client-side stage changes.
Critical: effect ordering. React runs child effects before parent effects. Without the ensureReloQueue() shim in ReloPixel.tsx, a CityViewTracker mounted inside a page would try to call window.relo(...) before the root layout's has initialized the runtime. The shim pushes calls to a queue that the async r.js runtime drains once loaded.
components/CityViewTracker.tsx
'use client'
import { useEffect } from 'react'
import { trackCityView } from './ReloPixel'
export default function CityViewTracker({ city, language = 'en' }) {
useEffect(() => { trackCityView(city, language) }, [city, language])
return null
}
Mount on every city landing page:
<main>
<CityViewTracker city="Aventura" language="es" />
<StructuredData ... />
...
</main>
components/MultiStepForm.tsx
Fires trackReloLead in two spots:
- On successful submit — stage
'raw', passes session + funnel telemetry. - On duplicate 409 — stage
'duplicate', helps partners see returning users.
trackReloLead({
zip: data.zip,
locale,
sessionId: sessionIdRef.current,
timeOnPageSeconds: timeOnPage,
formStartToSubmitSeconds: formTime,
stepsCompleted: Array.from(stepsCompletedRef.current).sort().join(','),
abVariant,
})
app/layout.tsx
Root layout imports + mounts <ReloPixel /> — this covers every route including SPA navigations (pixel listens for pathname changes and fires page automatically).
3. Event map
Event
Fires when
Props captured
page_view
Route change (auto)
path, title, utm, ref
view_product
City landing page mount
product (city), product_category, city, language
form_start
First form focus (auto)
form_id
form_submit
Form submit event (auto)
form_id, field count
form_abandon
pagehide with dirty form (auto)
form_id, fields filled
lead_submit
Successful API response
product, form_id, stage, lead_value, currency, x_zip, x_locale, x_session_id, x_ab_variant, eh, ph
lead_stage_update
Client-side stage change (rare)
new_stage, form_id
Plus ~50 auto-captured UX + bot signals: scroll, click, rage_click, copy, autofill_detected, keystroke_rate, net_quality, battery_state, web_vitals, etc.
4. Backend configuration
Supabase state
-- clients
id=5, name='DemoLead', slug='quote', tracking_type='pixel'
-- client_pixel_config (client_id=5)
allowed_domains=['demo.example', 'www.demo.example']
consent_required=false
jurisdiction='mx'
consent_defaults=15 (bits 0-3: analytics, personalization, dsp, cross-client)
-- partner_clients
partner_id=30 (Demo Partner), client_id=5, status='active'
-- campaigns
id=8, name='Demo Lead Campaign', client_id=5, tracking_type='pixel'
slug='quote-auto-demo', status='active'
Click wrapper KV (Cloudflare)
-- Namespace: 0b04030f85724c3e8d118e3a9efbe2ee (SHORT_URLS)
-- Key: demolead
-- Value:
{
"url": "https://demo.example?utm_source=relo-demo",
"pid": 30, // partner_id
"cid": 5, // client_id
"aid": 8 // campaign_id
}
Tracking link: https://t.relo.mx/c/demolead → 302 to demo.example with _relo_cid cookie set. On lead_submit Go backbone reads the cookie, looks up Redis click cache, populates partner_id + campaign_id server-side.
5. Configure a new pixel partner via admin UI
- Open
/c/quote/partners - Click "Add Pixel Partner"
- Fill in the 3-section modal:
- Partner — create new or link existing, enter company name
- Campaign — create new or pick existing, enter slug
- Tracking URL — short code + destination URL
- Submit. Atomically creates: partner + partner_clients + campaign + partner_campaigns + KV entry.
- Partner receives the
t.relo.mx/c/XXXXXXlink to promote.
6. Commission rates per conversion
For lead-gen clients, commission is per-event (not per-revenue). Configure via PartnerLeadRatesModal:
- Open partner detail → "Lead Rates" button
- Select event (
lead_submit,purchase,signup, etc.) - Optional: stage filter (raw / qualified / sold)
- Set rate (fixed $/event or % of lead_value)
- Optional:
monthly_capto limit payout
Specificity: stage-specific rules beat generic ones. Older rates remain in DB for historical commission calculations.
7. Reporting views
Role
URL
Shows
Admin
/c/quote/dashboard
Sessions, conversions, funnel, cities, campaigns
Admin
/c/quote/leads
All conversions, CSV export, stage + product filters
Admin
/c/quote/audit
Live events stream, event type breakdown, heatmap
Client user
/client/quote
Sessions, conversions, funnel — no partner/commission info
Partner
/p/quote
Per-campaign sessions, leads with hash prefixes, CSV export
8. Verification commands
# Confirm client config
curl "$SUPABASE_URL/rest/v1/clients?id=eq.5&select=id,name,slug,tracking_type" \
-H "apikey: $SUPABASE_KEY"
# Confirm pixel events flowing
ssh root@178.156.195.219 "clickhouse-client -d relo -q \\
\"SELECT event_name, count() FROM events \\
WHERE client_id=5 AND event_time >= now() - INTERVAL 1 DAY \\
GROUP BY event_name ORDER BY count() DESC\""
# Confirm click wrapper entry exists
curl "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/storage/kv/namespaces/$KV_ID/values/demolead" \
-H "Authorization: Bearer $CF_TOKEN"
# Test end-to-end: visit via partner link, submit form, check event landed
curl -I https://t.relo.mx/c/demolead
# → 302 Location: https://demo.example?...
9. Gotchas we hit in production
- React effect ordering. Fixed with
ensureReloQueue()shim that queuesinitsynchronously. See section 2. - Full property persistence.
batch.gooriginally only dumped full props forlead_submit. Extended to all conversion + funnel events (purchase,view_product,add_to_cart,form_submit,signup,subscription,checkout_open,lead_stage_update). - Attribution via cookie. Partner ID only gets set on
lead_submitif the visitor came throught.relo.mx/c/CODEand the_relo_cidcookie survived. Cross-domain navigations break attribution unless cookie is set on.relo.mxwithSameSite=Lax. - Duplicate dedup. Server-side
SETNXin Redis with 10min TTL ondedup:lead_submit:{client}:{device}:{form}:{product}. Refreshes + double-submits count once.
10. Operational runbook
Deploy pixel changes
cd backbone/workers/pixel && npx wrangler deploy
Deploy demo site changes
cd ~/seguros && bash scripts/deploy-cloudflare.sh
Deploy Go batch.go changes
cd backbone && GOOS=linux GOARCH=amd64 go build -o relo-ingest-new ./cmd/ingest/
scp relo-ingest-new root@178.156.195.219:/opt/relo/relo-ingest-new
ssh root@178.156.195.219 "systemctl stop relo-ingest && \\
cp /opt/relo/relo-ingest-new /opt/relo-ingest/relo-ingest && \\
chmod +x /opt/relo-ingest/relo-ingest && systemctl start relo-ingest"
Check live events
ssh root@178.156.195.219 "clickhouse-client -d relo -q \\
\"SELECT event_name, event_properties, event_time \\
FROM events WHERE client_id=5 \\
AND event_time >= now() - INTERVAL 5 MINUTE \\
ORDER BY event_time DESC LIMIT 10 FORMAT Vertical\""
11. See also
- Pixel Setup Guide — general admin + integration
- Pixel Reference — event + command reference
- Integration Overview — all tracking modes
- OpenAPI spec — full API reference