RRelo Docs
API & GuíasGuías

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

Diagrama interactivo
Cargando diagrama…

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 in app/layout.tsx. Loads r.js async + fires page.
  • trackReloLead({...}) — call on successful form submit. Fires lead_submit.
  • trackCityView(city, language) — call from city landing pages (via CityViewTracker).
  • 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 ... />
  ...
&lt;/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

  1. Open /c/quote/partners
  2. Click "Add Pixel Partner"
  3. 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
  4. Submit. Atomically creates: partner + partner_clients + campaign + partner_campaigns + KV entry.
  5. Partner receives the t.relo.mx/c/XXXXXX link 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_cap to 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 queues init synchronously. See section 2.
  • Full property persistence. batch.go originally only dumped full props for lead_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_submit if the visitor came through t.relo.mx/c/CODE and the _relo_cid cookie survived. Cross-domain navigations break attribution unless cookie is set on .relo.mx with SameSite=Lax.
  • Duplicate dedup. Server-side SETNX in Redis with 10min TTL on dedup: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

On this page