विज़िबिलिटी स्टेट ट्रांज़िशन: भरोसेमंद बीकन के बिना पेज व्यू मापना
pagehide, visibilitychange और Beacon API हर ब्राउज़र में अलग व्यवहार करते हैं। जानिए बिना कुकीज़ और स्थायी पहचानकर्ताओं के सेशन का अंत भरोसेमंद ढंग से कैसे दर्ज करें।
यह दर्ज करना कि कोई पेज देखा गया, आसान है। यह दर्ज करना कि उपयोगकर्ता ने वास्तव में कब छोड़ा — भरोसेमंद ढंग से, टैब बदलने, बैकग्राउंड में जाने और अचानक बंद होने के बावजूद — वेब एनालिटिक्स की अधिक पेचीदा समस्याओं में से एक है। यह मायने रखता है क्योंकि एंगेजमेंट मीट्रिक (ठहराव का समय, बाउंस, स्क्रॉल डेप्थ) एक साफ़ सेशन-अंत संकेत पकड़ने पर निर्भर हैं। ग़लती हुई तो या तो आप डेटा खोते हैं या उसे फुला देते हैं।
Monoid इसे बिना कुकीज़, बिना localStorage और बिना किसी स्थायी पहचानकर्ता के हल करता है। यह बंदिश दरअसल चीज़ों को सरल बना देती है: हमें कभी भी पेज लोड के आर-पार किसी सेशन को जोड़ना नहीं पड़ता, इसलिए हमें बस इतना चाहिए कि हर पेज लाइफ़साइकल में एक सटीक अंतिम बीकन भेजा जाए। नीचे देखिए कि ब्राउज़र की बुनियादी सुविधाएँ कैसा बर्ताव करती हैं, और जाल कहाँ बिछे हैं।
unload ग़लत इवेंट क्यों है
पुराना तरीका था unload या beforeunload सुनना और एक सिंक्रोनस XHR दाग़ देना। आज यह सक्रिय रूप से हानिकारक है। मोबाइल पर unload इवेंट भरोसेमंद नहीं है: जब उपयोगकर्ता टैब को बैकग्राउंड में भेजता है और OS उसे वापस ले लेता है, तो unload अक्सर चलता ही नहीं। इससे भी बुरा यह कि unload हैंडलर रजिस्टर करने से कई ब्राउज़रों में पेज बैक/फ़ॉरवर्ड कैश (bfcache) के अयोग्य हो जाता है, जिससे नेविगेशन का प्रदर्शन गिरता है।
web.dev का Page Lifecycle API मार्गदर्शन स्पष्ट है: visibilitychange के hidden में जाने को वह आख़िरी भरोसेमंद इवेंट मानिए जो आप देख पाएँगे। मोबाइल पर unload या pagehide के चलने पर निर्भर मत रहिए।
visibilitychange + pagehide की जोड़ी
मज़बूत पैटर्न यह है कि visibilitychange सुनें और document.visibilityState === 'hidden' जाँचें, साथ में pagehide को द्वितीयक संकेत के रूप में रखें। जब भी पेज छिपी अवस्था में जाए, जो भेजना है उसे फ़्लश कर दें। Page Visibility API का MDN दस्तावेज़ पुष्टि करता है कि टैब के बैकग्राउंड में जाने या ब्राउज़र के मिनिमाइज़ होने पर visibilitychange चलता है — यह "उपयोगकर्ता ने देखना बंद कर दिया" का सबसे नज़दीकी विकल्प है।
पेच यह है: छिपी अवस्था में जाना एक ही सेशन में कई बार हो सकता है (टैब से हटना, लौटना, फिर हटना)। इसलिए आपको अपने फ़्लश को idempotent बनाना होगा, या उस पर debounce लगाना होगा, या पिछले फ़्लश के बाद का केवल डेल्टा भेजना होगा। चूँकि Monoid प्रति-उपयोगकर्ता कोई स्थिति नहीं रखता, हम एक अकेला आत्मनिर्भर पेलोड भेजते हैं और एज को रिक्वेस्ट-स्कोप पर डीडुप्लिकेट करने देते हैं।
मरती हुई पेज से डेटा भेजना: navigator.sendBeacon
आप visibilitychange के दौरान एसिंक्रोनस fetch चलाकर उसके पूरा होने की उम्मीद नहीं कर सकते — ब्राउज़र उससे पहले ही पेज गिरा सकता है। Beacon API विनिर्देश इसी के लिए मौजूद है। navigator.sendBeacon(url, data) एक छोटा POST क़तार में लगाता है जिसे भेजने की कोशिश ब्राउज़र दस्तावेज़ के जाने के बाद भी करने की गारंटी देता है — मुख्य थ्रेड को रोके बिना और नेविगेशन में देरी किए बिना।
Beacon की कुछ सीमाएँ हैं जिनका सम्मान करना चाहिए:
- पेलोड का आकार. विनिर्देश उपयोगकर्ता एजेंट को इजाज़त देता है कि वह कार्यान्वयन-निर्धारित सीमा (आमतौर पर 64 KB) से बड़े बीकन अस्वीकार कर दे। पेलोड बहुत छोटे रखें — Monoid के कुछ सौ बाइट के हैं।
- मेथड. बीकन हमेशा POST होते हैं। अगर आपका एज एंडपॉइंट GET की अपेक्षा करता है, तो उसे बदलिए।
- कोई प्रतिक्रिया नहीं. आप कुछ भी वापस नहीं पढ़ सकते। भेजिए और भूल जाइए।
एक आधुनिक फ़ॉलबैक है fetch(url, { keepalive: true }), जिसे Fetch मानक इस तरह परिभाषित करता है कि रिक्वेस्ट दस्तावेज़ से ज़्यादा जी सके। keepalive आपको वे हेडर और मेथड देता है जो Beacon में नहीं हैं, पर आकार की वही अधिकतम सीमा साझा करता है। पहले Beacon इस्तेमाल करें, फिर keepalive वाले fetch पर लौटें।
एक न्यूनतम, कुकी-रहित कार्यान्वयन
let sent = false;
function flush() {
if (sent) return;
sent = true;
const payload = JSON.stringify({
path: location.pathname,
// कोई पहचानकर्ता नहीं, कोई कुकी नहीं, कोई फ़िंगरप्रिंट नहीं
visibleMs: Math.round(performance.now())
});
const ok = navigator.sendBeacon('/collect', payload);
if (!ok) {
fetch('/collect', { method: 'POST', body: payload, keepalive: true });
}
}
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') flush();
});
window.addEventListener('pagehide', flush);
ध्यान दीजिए कि यहाँ क्या अनुपस्थित है: कोई unload लिसनर नहीं, इसलिए bfcache की पात्रता बनी रहती है। लोड के बीच कोई संग्रहीत ID नहीं। visibleMs का आँकड़ा performance.now() से आता है — एक मोनोटोनिक घड़ी जिसे न दीवार-घड़ी टाइमस्टैम्प चाहिए, न कोई स्थायी स्थिति।
यह प्राइवेसी-फ़र्स्ट एनालिटिक्स के साथ क्यों बैठता है
क्योंकि Monoid सेशनों को कभी आपस में सिलता नहीं, हर बीकन एक पूर्ण, फेंक देने योग्य प्रेक्षण है। बचाने के लिए कोई क्रॉस-पेज पहचानकर्ता है ही नहीं, इसलिए एक बीकन का छूट जाना आपको एक डेटा बिंदु का नुक़सान देता है, बिगड़ी हुई उपयोगकर्ता प्रोफ़ाइल का नहीं। visibilitychange और Beacon का यह पैटर्न ठहराव का सटीक माप देता है और साथ ही पेज को तेज़ तथा bfcache-अनुकूल रखता है — जो अपने आप उन्हीं Core Web Vitals को सुधारता है जिन्हें शायद आप मापने की कोशिश कर रहे हैं।
जाने को मापिए, व्यक्ति को नहीं। ब्राउज़र आपको ज़रूरत की हर चीज़ पहले से दे रहा है।
Comments
Loading comments…