[{"data":1,"prerenderedAt":796},["ShallowReactive",2],{"navigation":3,"\u002Fen\u002Farticles\u002F2026\u002Fjuly\u002Fspying-on-your-rivals":177},[4,62],{"title":5,"path":6,"stem":7,"children":8},"About","\u002Fen","en\u002F1.index",[9,10,56],{"title":5,"path":6,"stem":7},{"title":11,"path":12,"stem":13,"children":14},"Blog","\u002Fen\u002Farticles","en\u002F2.articles",[15],{"title":16,"path":17,"stem":18,"children":19,"page":33},"2026","\u002Fen\u002Farticles\u002F2026","en\u002Farticles\u002F2026",[20,34,47],{"title":21,"path":22,"stem":23,"children":24,"page":33},"April","\u002Fen\u002Farticles\u002F2026\u002Fapril","en\u002Farticles\u002F2026\u002Fapril",[25,29],{"title":26,"path":27,"stem":28},"Improving My App's Vibes with DESIGN.md","\u002Fen\u002Farticles\u002F2026\u002Fapril\u002Fexploring-llm-ui","en\u002Farticles\u002F2026\u002Fapril\u002Fexploring-llm-ui",{"title":30,"path":31,"stem":32},"How I Made My Fork Bilingual and Published It Right","\u002Fen\u002Farticles\u002F2026\u002Fapril\u002Fhow-i-made-my-fork-bilingual-and-published-it-right","en\u002Farticles\u002F2026\u002Fapril\u002Fhow-i-made-my-fork-bilingual-and-published-it-right",false,{"title":35,"path":36,"stem":37,"children":38,"page":33},"July","\u002Fen\u002Farticles\u002F2026\u002Fjuly","en\u002Farticles\u002F2026\u002Fjuly",[39,43],{"title":40,"path":41,"stem":42},"From Idea to MS Teams: Deploying an Agent","\u002Fen\u002Farticles\u002F2026\u002Fjuly\u002Ffrom-idea-to-ms-teams-agent","en\u002Farticles\u002F2026\u002Fjuly\u002Ffrom-idea-to-ms-teams-agent",{"title":44,"path":45,"stem":46},"Legally Spying on Your Competition is Possible","\u002Fen\u002Farticles\u002F2026\u002Fjuly\u002Fspying-on-your-rivals","en\u002Farticles\u002F2026\u002Fjuly\u002Fspying-on-your-rivals",{"title":48,"path":49,"stem":50,"children":51,"page":33},"May","\u002Fen\u002Farticles\u002F2026\u002Fmay","en\u002Farticles\u002F2026\u002Fmay",[52],{"title":53,"path":54,"stem":55},"Building an Agent's Personality Around the Customer Journey","\u002Fen\u002Farticles\u002F2026\u002Fmay\u002Fbuilding-agents-with-personality","en\u002Farticles\u002F2026\u002Fmay\u002Fbuilding-agents-with-personality",{"title":57,"path":58,"stem":59,"children":60},"Index","\u002Fen\u002Farticles_index","en\u002F4.articles_index",[61],{"title":57,"path":58,"stem":59},{"title":63,"path":64,"stem":65,"children":66},"Acerca De","\u002Fes","es\u002F1.index",[67,68,153,171],{"title":63,"path":64,"stem":65},{"title":11,"path":69,"stem":70,"children":71},"\u002Fes\u002Farticles","es\u002F2.articles",[72,95,117],{"title":73,"path":74,"stem":75,"children":76,"page":33},"2024","\u002Fes\u002Farticles\u002F2024","es\u002Farticles\u002F2024",[77,86],{"title":78,"path":79,"stem":80,"children":81,"page":33},"Diciembre","\u002Fes\u002Farticles\u002F2024\u002Fdiciembre","es\u002Farticles\u002F2024\u002Fdiciembre",[82],{"title":83,"path":84,"stem":85},"Diciembre es un mes más","\u002Fes\u002Farticles\u002F2024\u002Fdiciembre\u002Fhaikus","es\u002Farticles\u002F2024\u002Fdiciembre\u002Fhaikus",{"title":87,"path":88,"stem":89,"children":90,"page":33},"Octubre","\u002Fes\u002Farticles\u002F2024\u002Foctubre","es\u002Farticles\u002F2024\u002Foctubre",[91],{"title":92,"path":93,"stem":94},"Viaje de regreso","\u002Fes\u002Farticles\u002F2024\u002Foctubre\u002Fhaikus","es\u002Farticles\u002F2024\u002Foctubre\u002Fhaikus",{"title":96,"path":97,"stem":98,"children":99,"page":33},"2025","\u002Fes\u002Farticles\u002F2025","es\u002Farticles\u002F2025",[100,109],{"title":101,"path":102,"stem":103,"children":104,"page":33},"Agosto","\u002Fes\u002Farticles\u002F2025\u002Fagosto","es\u002Farticles\u002F2025\u002Fagosto",[105],{"title":106,"path":107,"stem":108},"Hola Mundo","\u002Fes\u002Farticles\u002F2025\u002Fagosto\u002Fholamundo","es\u002Farticles\u002F2025\u002Fagosto\u002Fholamundo",{"title":87,"path":110,"stem":111,"children":112,"page":33},"\u002Fes\u002Farticles\u002F2025\u002Foctubre","es\u002Farticles\u002F2025\u002Foctubre",[113],{"title":114,"path":115,"stem":116},"El Antipatrón de la Red Veloz","\u002Fes\u002Farticles\u002F2025\u002Foctubre\u002Flatencia","es\u002Farticles\u002F2025\u002Foctubre\u002Flatencia",{"title":16,"path":118,"stem":119,"children":120,"page":33},"\u002Fes\u002Farticles\u002F2026","es\u002Farticles\u002F2026",[121,133,145],{"title":21,"path":122,"stem":123,"children":124,"page":33},"\u002Fes\u002Farticles\u002F2026\u002Fapril","es\u002Farticles\u002F2026\u002Fapril",[125,129],{"title":126,"path":127,"stem":128},"Mejorando las VIBES de mi aplicación con DESIGN.md","\u002Fes\u002Farticles\u002F2026\u002Fapril\u002Fexploring-llm-ui","es\u002Farticles\u002F2026\u002Fapril\u002Fexploring-llm-ui",{"title":130,"path":131,"stem":132},"Cómo volví mi fork multilenguaje y lo publiqué bien","\u002Fes\u002Farticles\u002F2026\u002Fapril\u002Fhow-i-made-my-fork-bilingual-and-published-it-right","es\u002Farticles\u002F2026\u002Fapril\u002Fhow-i-made-my-fork-bilingual-and-published-it-right",{"title":35,"path":134,"stem":135,"children":136,"page":33},"\u002Fes\u002Farticles\u002F2026\u002Fjuly","es\u002Farticles\u002F2026\u002Fjuly",[137,141],{"title":138,"path":139,"stem":140},"De Idea a Ms Teams: Desplegando un Agente","\u002Fes\u002Farticles\u002F2026\u002Fjuly\u002Ffrom-idea-to-ms-teams-agent","es\u002Farticles\u002F2026\u002Fjuly\u002Ffrom-idea-to-ms-teams-agent",{"title":142,"path":143,"stem":144},"Espiar a Tu Competencia de Manera Legal es Posible","\u002Fes\u002Farticles\u002F2026\u002Fjuly\u002Fspying-on-your-rivals","es\u002Farticles\u002F2026\u002Fjuly\u002Fspying-on-your-rivals",{"title":48,"path":146,"stem":147,"children":148,"page":33},"\u002Fes\u002Farticles\u002F2026\u002Fmay","es\u002Farticles\u002F2026\u002Fmay",[149],{"title":150,"path":151,"stem":152},"Construyendo la personalidad de un agente en base a la travesía del cliente","\u002Fes\u002Farticles\u002F2026\u002Fmay\u002Fbuilding-agents-with-personality","es\u002Farticles\u002F2026\u002Fmay\u002Fbuilding-agents-with-personality",{"title":154,"path":155,"stem":156,"children":157},"Galería","\u002Fes\u002Fgallery","es\u002F3.gallery",[158],{"title":96,"path":159,"stem":160,"children":161,"page":33},"\u002Fes\u002Fgallery\u002F2025","es\u002Fgallery\u002F2025",[162],{"title":163,"path":164,"stem":165,"children":166,"page":33},"Septiembre","\u002Fes\u002Fgallery\u002F2025\u002Fseptiembre","es\u002Fgallery\u002F2025\u002Fseptiembre",[167],{"title":168,"path":169,"stem":170},"Cielo de Oyón","\u002Fes\u002Fgallery\u002F2025\u002Fseptiembre\u002Fholamundo","es\u002Fgallery\u002F2025\u002Fseptiembre\u002Fholamundo",{"title":172,"path":173,"stem":174,"children":175},"Lista","\u002Fes\u002Farticles_index","es\u002F4.articles_index",[176],{"title":172,"path":173,"stem":174},{"id":178,"title":44,"author":179,"badges":183,"body":184,"canonical":183,"cover":781,"date":782,"description":783,"extension":784,"layout":785,"meta":786,"navigation":638,"path":45,"seo":792,"stem":46,"__hash__":795},"content\u002Fen\u002Farticles\u002F2026\u002Fjuly\u002Fspying-on-your-rivals.md",{"name":180,"url":181,"avatarUrl":182},"Ever Burga","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Feverburga\u002F","https:\u002F\u002Fwww.linkedin.com\u002F",null,{"type":185,"value":186,"toc":765},"minimark",[187,191,208,211,214,217,220,230,239,244,249,252,255,258,261,269,272,276,279,290,293,296,300,303,306,310,320,323,327,336,339,342,345,348,352,355,359,362,375,384,389,457,468,518,533,537,540,548,551,554,558,696,701,705,740,751,754,758,761],[188,189,44],"h1",{"id":190},"legally-spying-on-your-competition-is-possible",[192,193,196],"div",{"className":194},[195],"hero-breakout",[192,197,200],{"className":198},[199],"hero-breakout-inner",[201,202,203],"p",{},[204,205],"img",{"alt":206,"src":207},"Diagram of the competitor tracking flow using open data","\u002Farticles\u002F2026\u002Fjuly\u002Fspying-on-your-rivals-hero.svg",[201,209,210],{},"How do you get user data from pages you're interested in monitoring?",[201,212,213],{},"By using Google's public data!",[201,215,216],{},"You don't need a scraper or expensive Wappalyzer subscriptions.",[201,218,219],{},"You just need",[221,222,223,227],"ul",{},[224,225,226],"li",{},"The BigQuery database and API for CrUX.",[224,228,229],{},"The HTTP Archive database in BigQuery.",[201,231,232],{},[233,234,238],"a",{"href":235,"rel":236},"https:\u002F\u002Ftheuxreport.kekeros.com",[237],"nofollow","theuxreport",[240,241,243],"h2",{"id":242},"architecture-decisions","Architecture Decisions",[245,246,248],"h3",{"id":247},"first-lets-look-at-the-nature-of-the-data-sources","First, let's look at the nature of the data sources",[201,250,251],{},"The CrUX and HTTP Archive databases are updated monthly. CrUX has a 2-month lag, while HTTP Archive only has a 1-month lag. This difference has an impact on the data analysis. That's why it's worth tagging the provenance month of every piece of data that enters our data lake.",[201,253,254],{},"On the other hand, for the end user who just wants to see reports, a 1-month difference doesn't matter much. It could even be read as processing delay or some insignificant offset between the historical charts.",[201,256,257],{},"Now, if we focus on data volume, each database uses a field called origin as its identifier, which we can think of as the web domain that serves the content and applications.",[201,259,260],{},"Each origin has dozens of columns in total. But at the end of the day, that's a fixed amount of data. This means the data volume grows given the parameters",[221,262,263,266],{},[224,264,265],{},"N: number of origins to analyze",[224,267,268],{},"T: number of time units to analyze,",[201,270,271],{},"... with O(NT) complexity. For my initial list of Peruvian domains I registered 20 thousand domains, and my goal is to keep a 24-week window in the report. I doubt it will exceed 100 thousand domains in the future, so we have a modest volume of data that can be processed on a single node, or my computer.",[245,273,275],{"id":274},"requirements-and-constraints","Requirements and constraints",[201,277,278],{},"Among other more specific requirements,",[221,280,281,284,287],{},[224,282,283],{},"I want to store the historical and enrichment data in a format with low storage cost but high data integrity guarantees.",[224,285,286],{},"My reports page must have high availability and deliver a smooth user experience.",[224,288,289],{},"I want to keep maintenance costs to a minimum.",[201,291,292],{},"AWS S3 + Athena, GCP BigQuery Iceberg Catalog... there are many solutions on the market. This time I decided to use Cloudflare R2 Data Catalog, with a generous free quota of 10GB of storage and a high number of reads and writes.",[201,294,295],{},"As for the site itself, Astro is my preferred choice for these cases. And the CDN will be Cloudflare to keep administration simple.",[240,297,299],{"id":298},"data-transformation","Data Transformation",[201,301,302],{},"The extraction, filtering, and enrichment stages are straightforward.",[201,304,305],{},"However, HTTP Archive doesn't provide normalized technology categories. Since it uses Wappalyzer's signature engine, it just returns whatever categories it detects. You can end up with React.js and Next.js at the same time, even though we know Next.js is a React meta-framework and doesn't exist without it.",[245,307,309],{"id":308},"stage-1-remapping-to-macrocategories","Stage 1: Remapping to Macrocategories",[201,311,312,316],{},[204,313],{"alt":314,"src":315},"Diagram of remapping raw categories into normalized macrocategories","\u002Farticles\u002F2026\u002Fjuly\u002Fspying-on-your-rivals-remapping.svg",[317,318,319],"em",{},"Remapping raw categories into normalized macrocategories.",[201,321,322],{},"Origin_Technologies comes from R2 with a list of categories per technology. After grouping them we have less complexity to work with. But this is the stage where we do the dirty work.",[245,324,326],{"id":325},"stage-2-cleaning-up-ties","Stage 2: Cleaning up ties",[201,328,329,333],{},[204,330],{"alt":331,"src":332},"Diagram of the LinearUntie process to collapse technologies tied to the same macrocategory","\u002Farticles\u002F2026\u002Fjuly\u002Fspying-on-your-rivals-linear-untie.svg",[317,334,335],{},"LinearUntie: collapsing technologies tied to the same macrocategory.",[201,337,338],{},"A single origin can have several technologies for the same category. This is a consequence of what I mentioned before. You can have Next.js and Next App Router classified within the same Web Frameworks category.",[201,340,341],{},"To collapse these ties I use a linear ordering of technologies according to category. If I find both Nest.js and Express, Nest.js is the option that encompasses both concepts.",[201,343,344],{},"A direct improvement to this model would be to rank not just by category, but by category + framework. But that requires a costly relationship-building task, and there are still exceptions stemming from the very nature of how HTTP Archive generates its data.",[201,346,347],{},"Minor exceptions or mis-categorized technologies I add manually to a structure on disk. It's unavoidable work.",[240,349,351],{"id":350},"ranking","Ranking",[201,353,354],{},"Once you have the normalized datasets, ranking becomes a statistics task, and the reports page becomes a design and UI process.",[240,356,358],{"id":357},"the-ux-report","The UX Report",[201,360,361],{},"If you're interested in validating the results you see on the site or in a post, here are the steps.",[245,363,365,366,370,371,374],{"id":364},"how-to-reproduce-your-severity-and-your-score-with-the-public-crux-api","How to reproduce your ",[367,368,369],"strong",{},"severity"," and your ",[367,372,373],{},"score"," with the public CrUX API",[201,376,377,378,383],{},"What shows up in the Core Web Vitals section comes straight from Google's ",[233,379,382],{"href":380,"rel":381},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fcrux\u002Fapi",[237],"Chrome UX Report API",", which is also used in their SEO ranking.",[385,386,388],"h4",{"id":387},"_1-request-a-histogram-through-the-crux-api","1. Request a histogram through the CrUX API",[390,391,396],"pre",{"className":392,"code":393,"language":394,"meta":395,"style":395},"language-bash shiki shiki-themes github-light github-dark","curl --request POST \\\n  'https:\u002F\u002Fchromeuxreport.googleapis.com\u002Fv1\u002Frecords:queryRecord?key=YOUR_API_KEY' \\\n  --header 'Accept: application\u002Fjson' \\\n  --header 'Content-Type: application\u002Fjson' \\\n  --data '{\"origin\":\"https:\u002F\u002Fyour-domain.pe\"}'\n","bash","",[397,398,399,419,427,438,448],"code",{"__ignoreMap":395},[400,401,404,408,412,416],"span",{"class":402,"line":403},"line",1,[400,405,407],{"class":406},"sScJk","curl",[400,409,411],{"class":410},"sj4cs"," --request",[400,413,415],{"class":414},"sZZnC"," POST",[400,417,418],{"class":410}," \\\n",[400,420,422,425],{"class":402,"line":421},2,[400,423,424],{"class":414},"  'https:\u002F\u002Fchromeuxreport.googleapis.com\u002Fv1\u002Frecords:queryRecord?key=YOUR_API_KEY'",[400,426,418],{"class":410},[400,428,430,433,436],{"class":402,"line":429},3,[400,431,432],{"class":410},"  --header",[400,434,435],{"class":414}," 'Accept: application\u002Fjson'",[400,437,418],{"class":410},[400,439,441,443,446],{"class":402,"line":440},4,[400,442,432],{"class":410},[400,444,445],{"class":414}," 'Content-Type: application\u002Fjson'",[400,447,418],{"class":410},[400,449,451,454],{"class":402,"line":450},5,[400,452,453],{"class":410},"  --data",[400,455,456],{"class":414}," '{\"origin\":\"https:\u002F\u002Fyour-domain.pe\"}'\n",[201,458,459,460,463,464,467],{},"The response includes, for each metric, a 3-bin ",[367,461,462],{},"histogram",": good \u002F needs improvement \u002F poor — each with a ",[367,465,466],{},"density"," (the real fraction of real-user visits in that range, not a synthetic average):",[390,469,473],{"className":470,"code":471,"language":472,"meta":395,"style":395},"language-json shiki shiki-themes github-light github-dark","\"largest_contentful_paint\": {\n  \"histogram\": [\n    { \"start\": 0,    \"end\": 2500, \"density\": 0.82 },\n    { \"start\": 2500, \"end\": 4000, \"density\": 0.11 },\n    { \"start\": 4000,               \"density\": 0.07 }\n  ],\n  \"percentiles\": { \"p75\": 2380 }\n}\n","json",[397,474,475,480,485,490,495,500,506,512],{"__ignoreMap":395},[400,476,477],{"class":402,"line":403},[400,478,479],{},"\"largest_contentful_paint\": {\n",[400,481,482],{"class":402,"line":421},[400,483,484],{},"  \"histogram\": [\n",[400,486,487],{"class":402,"line":429},[400,488,489],{},"    { \"start\": 0,    \"end\": 2500, \"density\": 0.82 },\n",[400,491,492],{"class":402,"line":440},[400,493,494],{},"    { \"start\": 2500, \"end\": 4000, \"density\": 0.11 },\n",[400,496,497],{"class":402,"line":450},[400,498,499],{},"    { \"start\": 4000,               \"density\": 0.07 }\n",[400,501,503],{"class":402,"line":502},6,[400,504,505],{},"  ],\n",[400,507,509],{"class":402,"line":508},7,[400,510,511],{},"  \"percentiles\": { \"p75\": 2380 }\n",[400,513,515],{"class":402,"line":514},8,[400,516,517],{},"}\n",[201,519,520,521,524,525,528,529,532],{},"The three metrics that matter are ",[367,522,523],{},"largest_contentful_paint"," (LCP), ",[367,526,527],{},"cumulative_layout_shift"," (CLS), and ",[367,530,531],{},"interaction_to_next_paint"," (INP). Google's official core web vitals.",[385,534,536],{"id":535},"_2-calculate-the-severity-of-each-metric","2. Calculate the severity of each metric",[201,538,539],{},"For each metric, severity is an average weighted by the real density of each band:",[390,541,546],{"className":542,"code":544,"language":545},[543],"language-text","metric_severity = poor × 1.0 + needs_improvement × 0.5 + good × 0.0\n","text",[397,547,544],{"__ignoreMap":395},[201,549,550],{},"With the example above: 0.07 × 1.0 + 0.11 × 0.5 + 0.82 × 0.0 = 0.125.",[201,552,553],{},"Why weighted instead of \"dominant band\"? Because an origin with 51% good \u002F 49% needs-improvement and another with 99% good \u002F 1% needs-improvement are very different real stories",[385,555,557],{"id":556},"_3-average-the-three-metrics","3. Average the three metrics",[390,559,563],{"className":560,"code":561,"language":562,"meta":395,"style":395},"language-python shiki shiki-themes github-light github-dark","def metric_severity(good: float, ni: float, poor: float) -> float:\n    return poor * 1.0 + ni * 0.5 + good * 0.0\n\nseverity = (\n    metric_severity(*lcp_densities)\n    + metric_severity(*cls_densities)\n    + metric_severity(*inp_densities)\n) \u002F 3\n","python",[397,564,565,599,634,640,651,661,674,685],{"__ignoreMap":395},[400,566,567,571,574,578,581,584,586,589,591,594,596],{"class":402,"line":403},[400,568,570],{"class":569},"szBVR","def",[400,572,573],{"class":406}," metric_severity",[400,575,577],{"class":576},"sVt8B","(good: ",[400,579,580],{"class":410},"float",[400,582,583],{"class":576},", ni: ",[400,585,580],{"class":410},[400,587,588],{"class":576},", poor: ",[400,590,580],{"class":410},[400,592,593],{"class":576},") -> ",[400,595,580],{"class":410},[400,597,598],{"class":576},":\n",[400,600,601,604,607,610,613,616,619,621,624,626,629,631],{"class":402,"line":421},[400,602,603],{"class":569},"    return",[400,605,606],{"class":576}," poor ",[400,608,609],{"class":569},"*",[400,611,612],{"class":410}," 1.0",[400,614,615],{"class":569}," +",[400,617,618],{"class":576}," ni ",[400,620,609],{"class":569},[400,622,623],{"class":410}," 0.5",[400,625,615],{"class":569},[400,627,628],{"class":576}," good ",[400,630,609],{"class":569},[400,632,633],{"class":410}," 0.0\n",[400,635,636],{"class":402,"line":429},[400,637,639],{"emptyLinePlaceholder":638},true,"\n",[400,641,642,645,648],{"class":402,"line":440},[400,643,644],{"class":576},"severity ",[400,646,647],{"class":569},"=",[400,649,650],{"class":576}," (\n",[400,652,653,656,658],{"class":402,"line":450},[400,654,655],{"class":576},"    metric_severity(",[400,657,609],{"class":569},[400,659,660],{"class":576},"lcp_densities)\n",[400,662,663,666,669,671],{"class":402,"line":502},[400,664,665],{"class":569},"    +",[400,667,668],{"class":576}," metric_severity(",[400,670,609],{"class":569},[400,672,673],{"class":576},"cls_densities)\n",[400,675,676,678,680,682],{"class":402,"line":508},[400,677,665],{"class":569},[400,679,668],{"class":576},[400,681,609],{"class":569},[400,683,684],{"class":576},"inp_densities)\n",[400,686,687,690,693],{"class":402,"line":514},[400,688,689],{"class":576},") ",[400,691,692],{"class":569},"\u002F",[400,694,695],{"class":410}," 3\n",[201,697,698,700],{},[367,699,369],{}," ends up between 0 (perfect) and 1 (the worst possible case). If your origin is missing one of the three metrics for this window (CrUX requires a minimum amount of traffic per metric before it publishes it), what I do is fill in the value with that window's cohort average. In that case you'd have trouble reproducing your full score, but you can use a placeholder value of 50% to get an approximation.",[385,702,704],{"id":703},"_4-your-score","4. Your score",[390,706,708],{"className":560,"code":707,"language":562,"meta":395,"style":395},"score = round((1 - severity) * 100)\n",[397,709,710],{"__ignoreMap":395},[400,711,712,715,717,720,723,726,729,732,734,737],{"class":402,"line":403},[400,713,714],{"class":576},"score ",[400,716,647],{"class":569},[400,718,719],{"class":410}," round",[400,721,722],{"class":576},"((",[400,724,725],{"class":410},"1",[400,727,728],{"class":569}," -",[400,730,731],{"class":576}," severity) ",[400,733,609],{"class":569},[400,735,736],{"class":410}," 100",[400,738,739],{"class":576},")\n",[201,741,742,743,746,747,750],{},"With ",[367,744,745],{},"severity = 0.125",", the score is ",[367,748,749],{},"87\u002F100",". This number is 100% reproducible by anyone with nothing but Google's public API.",[201,752,753],{},"I hope you enjoyed the article. Remember I update the site report every month. See you in my analytics metrics soon!",[240,755,757],{"id":756},"collaboration","Collaboration",[201,759,760],{},"I plan to open source the project once it reaches a stable stage. If you'd like to contribute a list of URLs you think are worth including, or a metric you'd like to see, reach out to me on my social channels — I'd be glad to hear it.",[762,763,764],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":395,"searchDepth":421,"depth":421,"links":766},[767,771,775,776,780],{"id":242,"depth":421,"text":243,"children":768},[769,770],{"id":247,"depth":429,"text":248},{"id":274,"depth":429,"text":275},{"id":298,"depth":421,"text":299,"children":772},[773,774],{"id":308,"depth":429,"text":309},{"id":325,"depth":429,"text":326},{"id":350,"depth":421,"text":351},{"id":357,"depth":421,"text":358,"children":777},[778],{"id":364,"depth":429,"text":779},"How to reproduce your severity and your score with the public CrUX API",{"id":756,"depth":421,"text":757},"\u002Farticles\u002F2026\u002Fjuly\u002Fspying-on-your-rivals-cover.svg","2026-07-20T10:00:00.000Z","Tracking your competition with open data is a task... not that hard.","md","article",{"head":787},{"meta":788},[789],{"name":790,"content":791},"keywords","crux, http archive, bigquery, competitive analysis, web technologies, wappalyzer, cloudflare r2, data lake, seo, web analytics",{"title":793,"description":794},"Tracking Your Competitors' Users","Tracking your competition with public data is simple.","OAMkQV11xQC9QTViVV-PIcHrRNJPxbKo1nOfDY223fA",1785323411544]