volpe/posts/drafts/remove-tracking-params-from-links.md

530 lines
No EOL
20 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

- you should be removing tracking information from all of your links.
parts of a url:
<style>
.url-container {
margin: 1em 0;
}
.url-example {
font-family: monospace;
font-size: 1.2em;
padding: 1em;
background: var(--code-background, #f5f5f5);
border: 1px solid var(--border-color, #ddd);
border-radius: 8px;
margin-bottom: 1em;
overflow-x: auto;
white-space: nowrap;
}
.url-example span,
.url-legend-item {
transition: opacity 0.2s;
cursor: pointer;
}
.url-legend {
display: flex;
flex-direction: column;
gap: 0.75em;
padding: 1em;
background: var(--code-background, #f5f5f5);
border: 1px solid var(--border-color, #ddd);
border-radius: 8px;
}
.url-legend-item {
display: flex;
align-items: flex-start;
gap: 0.5em;
}
.url-legend-item .url-legend-label {
font-weight: 600;
min-width: 8em;
}
.url-legend-item .url-legend-desc {
color: #666;
font-size: 0.9em;
}
.url-legend-color {
width: 1em;
height: 1em;
border-radius: 3px;
flex-shrink: 0;
margin-top: 0.2em;
}
.url-part-protocol { color: #db2777; }
.url-legend-protocol { background: #db2777; }
.url-part-subdomain { color: #0891b2; }
.url-legend-subdomain { background: #0891b2; }
.url-part-domain { color: #2563eb; }
.url-legend-domain { background: #2563eb; }
.url-part-tld { color: #16a34a; }
.url-legend-tld { background: #16a34a; }
.url-part-port { color: #ca8a04; }
.url-legend-port { background: #ca8a04; }
.url-part-path { color: #ea580c; }
.url-legend-path { background: #ea580c; }
.url-part-query { color: #dc2626; }
.url-legend-query { background: #dc2626; }
.url-part-fragment { color: #9333ea; }
.url-legend-fragment { background: #9333ea; }
/* Cross-highlighting using :has() - no JavaScript needed */
.url-container:has([data-part]:hover) [data-part] { opacity: 0.4; }
/* Keep hovered element visible but without glow */
.url-container [data-part]:hover { opacity: 1 !important; }
/* Highlight style with neutral glow */
.url-highlight {
opacity: 1 !important;
background: rgba(0, 0, 0, 0.08);
box-shadow: 0 0 8px 4px rgba(0, 0, 0, 0.12);
border-radius: 4px;
}
/* When hovering URL parts, highlight matching legend items */
.url-container:has(.url-example [data-part="protocol"]:hover) .url-legend [data-part="protocol"],
.url-container:has(.url-example [data-part="subdomain"]:hover) .url-legend [data-part="subdomain"],
.url-container:has(.url-example [data-part="domain"]:hover) .url-legend [data-part="domain"],
.url-container:has(.url-example [data-part="tld"]:hover) .url-legend [data-part="tld"],
.url-container:has(.url-example [data-part="port"]:hover) .url-legend [data-part="port"],
.url-container:has(.url-example [data-part="path"]:hover) .url-legend [data-part="path"],
.url-container:has(.url-example [data-part="query"]:hover) .url-legend [data-part="query"],
.url-container:has(.url-example [data-part="fragment"]:hover) .url-legend [data-part="fragment"] {
opacity: 1 !important;
padding: 0.5em 0.75em;
margin: -0.5em -0.75em;
border-radius: 0.5em;
box-shadow:
inset 0 0 0.5em 0.25em rgba(0, 0, 0, 0.06),
0 0 0.75rem 0.5rem rgba(0, 0, 0, 0.06),
0 0 1.25rem 1rem rgba(0, 0, 0, 0.04),
0 0 2rem 1.5rem rgba(0, 0, 0, 0.02);
}
/* When hovering legend items, highlight matching URL parts */
.url-container:has(.url-legend [data-part="protocol"]:hover) .url-example [data-part="protocol"],
.url-container:has(.url-legend [data-part="subdomain"]:hover) .url-example [data-part="subdomain"],
.url-container:has(.url-legend [data-part="domain"]:hover) .url-example [data-part="domain"],
.url-container:has(.url-legend [data-part="tld"]:hover) .url-example [data-part="tld"],
.url-container:has(.url-legend [data-part="port"]:hover) .url-example [data-part="port"],
.url-container:has(.url-legend [data-part="path"]:hover) .url-example [data-part="path"],
.url-container:has(.url-legend [data-part="query"]:hover) .url-example [data-part="query"],
.url-container:has(.url-legend [data-part="fragment"]:hover) .url-example [data-part="fragment"] {
opacity: 1 !important;
border-radius: 0.25em;
box-shadow:
inset 0 0 0.5em 0.25em rgba(0, 0, 0, 0.06),
0 0 0.75rem 0.5rem rgba(0, 0, 0, 0.06),
0 0 1.25rem 1rem rgba(0, 0, 0, 0.04),
0 0 2rem 1.5rem rgba(0, 0, 0, 0.02);
}
</style>
<div class="url-container">
<div class="url-example" role="figure" aria-labelledby="url-example-caption">
<span class="url-part-protocol" aria-describedby="legend-protocol" data-part="protocol">https://</span><span class="url-part-subdomain" aria-describedby="legend-subdomain" data-part="subdomain">www.</span><span class="url-part-domain" aria-describedby="legend-domain" data-part="domain">example</span><span class="url-part-tld" aria-describedby="legend-tld" data-part="tld">.com</span><span class="url-part-port" aria-describedby="legend-port" data-part="port">:443</span><span class="url-part-path" aria-describedby="legend-path" data-part="path">/products/shoes</span><span class="url-part-query" aria-describedby="legend-query" data-part="query">?utm_source=facebook&utm_campaign=summer_sale&fbclid=abc123</span><span class="url-part-fragment" aria-describedby="legend-fragment" data-part="fragment">#reviews</span>
</div>
<div class="url-legend" role="list" aria-label="URL component legend">
<div class="url-legend-item" role="listitem" id="legend-protocol" data-part="protocol">
<span class="url-legend-color url-legend-protocol" aria-hidden="true"></span>
<span class="url-legend-label">Protocol</span>
<span class="url-legend-desc">How to communicate with the server (https is encrypted, http is not)</span>
</div>
<div class="url-legend-item" role="listitem" id="legend-subdomain" data-part="subdomain">
<span class="url-legend-color url-legend-subdomain" aria-hidden="true"></span>
<span class="url-legend-label">Subdomain</span>
<span class="url-legend-desc">A subdivision of the main domain, often used for different services</span>
</div>
<div class="url-legend-item" role="listitem" id="legend-domain" data-part="domain">
<span class="url-legend-color url-legend-domain" aria-hidden="true"></span>
<span class="url-legend-label">Domain</span>
<span class="url-legend-desc">The human-readable name that identifies the website</span>
</div>
<div class="url-legend-item" role="listitem" id="legend-tld" data-part="tld">
<span class="url-legend-color url-legend-tld" aria-hidden="true"></span>
<span class="url-legend-label">TLD</span>
<span class="url-legend-desc">Top-Level Domain, the suffix like .com, .org, or .net</span>
</div>
<div class="url-legend-item" role="listitem" id="legend-port" data-part="port">
<span class="url-legend-color url-legend-port" aria-hidden="true"></span>
<span class="url-legend-label">Port</span>
<span class="url-legend-desc">Network port number (443 is default for HTTPS, 80 for HTTP)</span>
</div>
<div class="url-legend-item" role="listitem" id="legend-path" data-part="path">
<span class="url-legend-color url-legend-path" aria-hidden="true"></span>
<span class="url-legend-label">Path</span>
<span class="url-legend-desc">The specific page or resource location on the server</span>
</div>
<div class="url-legend-item" role="listitem" id="legend-query" data-part="query">
<span class="url-legend-color url-legend-query" aria-hidden="true"></span>
<span class="url-legend-label">Query Params</span>
<span class="url-legend-desc">Key-value pairs that pass data to the page  often used for tracking</span>
</div>
<div class="url-legend-item" role="listitem" id="legend-fragment" data-part="fragment">
<span class="url-legend-color url-legend-fragment" aria-hidden="true"></span>
<span class="url-legend-label">Fragment</span>
<span class="url-legend-desc">Links to a specific section within the page (not sent to server)</span>
</div>
</div>
</div>
<script>
(function() {
const urlExample = document.querySelector('.url-example');
const legendItems = document.querySelectorAll('.url-legend-item[data-part]');
legendItems.forEach(item => {
item.addEventListener('mouseenter', () => {
const part = item.dataset.part;
const urlPart = document.querySelector(`.url-example [data-part="${part}"]`);
if (urlPart && urlExample) {
const containerRect = urlExample.getBoundingClientRect();
const partRect = urlPart.getBoundingClientRect();
const partCenter = partRect.left - containerRect.left + urlExample.scrollLeft + (partRect.width / 2);
const scrollTarget = partCenter - (containerRect.width / 2);
urlExample.scrollTo({ left: scrollTarget, behavior: 'smooth' });
}
});
});
})();
</script>
## Understanding Query Parameters
Query parameters are the part of a URL that comes after the `?`. They consist of key-value pairs that pass data to the page. Let's break down how they work:
<style>
.query-container {
margin: 1em 0;
}
.query-example {
font-family: monospace;
font-size: 1.2em;
padding: 1em;
background: var(--code-background, #f5f5f5);
border: 1px solid var(--border-color, #ddd);
border-radius: 8px;
margin-bottom: 1em;
overflow-x: auto;
white-space: nowrap;
}
.query-example span,
.query-legend-item {
transition: opacity 0.2s, box-shadow 0.2s;
cursor: pointer;
}
.query-legend {
display: flex;
flex-direction: column;
gap: 0.75em;
padding: 1em;
background: var(--code-background, #f5f5f5);
border: 1px solid var(--border-color, #ddd);
border-radius: 8px;
}
.query-legend-item {
display: flex;
align-items: flex-start;
gap: 0.5em;
}
.query-legend-item .query-legend-label {
font-weight: 600;
min-width: 8em;
}
.query-legend-item[data-qparam] .query-legend-label {
min-width: auto;
}
.query-legend-item .query-legend-desc {
color: #666;
font-size: 0.9em;
}
.query-legend-color {
width: 1em;
height: 1em;
border-radius: 3px;
flex-shrink: 0;
margin-top: 0.2em;
}
/* Structure colors - each UNIQUE */
.query-part-delimiter { color: #db2777; }
.query-legend-delimiter { background: #db2777; }
.query-part-key { color: #2563eb; }
.query-legend-key { background: #2563eb; }
.query-part-equals { color: #ca8a04; }
.query-legend-equals { background: #ca8a04; }
.query-part-value { color: #16a34a; }
.query-legend-value { background: #16a34a; }
.query-part-separator { color: #9333ea; }
.query-legend-separator { background: #9333ea; }
/* Parameter group highlighting in URL */
.query-param-group {
display: inline;
border-radius: 0.25em;
transition: box-shadow 0.2s, background 0.2s;
}
.query-container.hovering .query-param-group.highlighted {
border-radius: 0.25em;
box-shadow:
inset 0 0 0.5em 0.25em rgba(0, 0, 0, 0.06),
0 0 0.75rem 0.5rem rgba(0, 0, 0, 0.06),
0 0 1.25rem 1rem rgba(0, 0, 0, 0.04),
0 0 2rem 1.5rem rgba(0, 0, 0, 0.02);
}
/* Base: dim everything when hovering (NOT param-groups, only their children) */
.query-container.hovering .query-example > span[data-qpart],
.query-container.hovering .query-param-group > span[data-qpart],
.query-container.hovering .query-legend-item {
opacity: 0.3;
}
/* Highlighted spans in URL */
.query-container.hovering .query-example span[data-qpart].highlighted {
opacity: 1 !important;
border-radius: 0.25em;
box-shadow:
inset 0 0 0.5em 0.25em rgba(0, 0, 0, 0.06),
0 0 0.75rem 0.5rem rgba(0, 0, 0, 0.06),
0 0 1.25rem 1rem rgba(0, 0, 0, 0.04),
0 0 2rem 1.5rem rgba(0, 0, 0, 0.02);
}
/* Spans inside highlighted param groups should have full opacity */
.query-container.hovering .query-param-group.highlighted > span[data-qpart] {
opacity: 1 !important;
}
.query-container .query-legend-item.highlighted {
opacity: 1 !important;
padding: 0.5em 0.75em;
margin: -0.5em -0.75em;
border-radius: 0.5em;
box-shadow:
inset 0 0 0.5em 0.25em rgba(0, 0, 0, 0.06),
0 0 0.75rem 0.5rem rgba(0, 0, 0, 0.06),
0 0 1.25rem 1rem rgba(0, 0, 0, 0.04),
0 0 2rem 1.5rem rgba(0, 0, 0, 0.02);
}
.query-legend-section {
margin-top: 1em;
padding-top: 0.75em;
border-top: 1px solid var(--border-color, #ddd);
}
.query-legend-section-title {
font-weight: 600;
font-size: 0.85em;
color: #666;
margin-bottom: 0.5em;
}
/* Parameter legend labels - styled as code blocks */
.query-param-label {
font-family: monospace;
font-size: 0.9em;
background: var(--inline-code-background, rgba(0, 0, 0, 0.06));
padding: 0.15em 0.4em;
border-radius: 4px;
border: 1px solid var(--border-color, rgba(0, 0, 0, 0.1));
min-width: auto;
}
</style>
<div class="query-container" id="query-container">
<div class="query-example" role="figure" aria-label="Query parameter example">
<span class="query-part-delimiter" data-qpart="delimiter">?</span><span class="query-param-group" data-qparam="size"><span class="query-part-key" data-qpart="key" data-qparam="size">size</span><span class="query-part-equals" data-qpart="equals" data-qparam="size">=</span><span class="query-part-value" data-qpart="value" data-qparam="size">medium</span></span><span class="query-part-separator" data-qpart="separator">&</span><span class="query-param-group" data-qparam="color"><span class="query-part-key" data-qpart="key" data-qparam="color">color</span><span class="query-part-equals" data-qpart="equals" data-qparam="color">=</span><span class="query-part-value" data-qpart="value" data-qparam="color">light%20blue</span></span><span class="query-part-separator" data-qpart="separator">&</span><span class="query-param-group" data-qparam="utm"><span class="query-part-key" data-qpart="key" data-qparam="utm">utm_source</span><span class="query-part-equals" data-qpart="equals" data-qparam="utm">=</span><span class="query-part-value" data-qpart="value" data-qparam="utm">facebook</span></span><span class="query-part-separator" data-qpart="separator">&</span><span class="query-param-group" data-qparam="fbclid"><span class="query-part-key" data-qpart="key" data-qparam="fbclid">fbclid</span><span class="query-part-equals" data-qpart="equals" data-qparam="fbclid">=</span><span class="query-part-value" data-qpart="value" data-qparam="fbclid">abc123xyz</span></span>
</div>
<div class="query-legend" role="list" aria-label="Query parameter legend">
<div class="query-legend-section-title">Structure</div>
<div class="query-legend-item" role="listitem" data-qpart="delimiter">
<span class="query-legend-color query-legend-delimiter" aria-hidden="true"></span>
<span class="query-legend-label query-label-delimiter">? Delimiter</span>
<span class="query-legend-desc">Marks the start of query parameters in a URL</span>
</div>
<div class="query-legend-item" role="listitem" data-qpart="key">
<span class="query-legend-color query-legend-key" aria-hidden="true"></span>
<span class="query-legend-label query-label-key">Key</span>
<span class="query-legend-desc">The name of the data being passed</span>
</div>
<div class="query-legend-item" role="listitem" data-qpart="equals">
<span class="query-legend-color query-legend-equals" aria-hidden="true"></span>
<span class="query-legend-label query-label-equals">= Assignment</span>
<span class="query-legend-desc">Connects each key to its value</span>
</div>
<div class="query-legend-item" role="listitem" data-qpart="value">
<span class="query-legend-color query-legend-value" aria-hidden="true"></span>
<span class="query-legend-label query-label-value">Value</span>
<span class="query-legend-desc">The actual data being passed</span>
</div>
<div class="query-legend-item" role="listitem" data-qpart="separator">
<span class="query-legend-color query-legend-separator" aria-hidden="true"></span>
<span class="query-legend-label query-label-separator">& Separator</span>
<span class="query-legend-desc">Separates multiple key-value pairs</span>
</div>
<div class="query-legend-section">
<div class="query-legend-section-title">Parameters in this example</div>
</div>
<div class="query-legend-item" role="listitem" data-qparam="size">
<span class="query-legend-label query-param-label">size=medium</span>
<span class="query-legend-desc">Useful parameter — tells the page which size to select</span>
</div>
<div class="query-legend-item" role="listitem" data-qparam="color">
<span class="query-legend-label query-param-label">color=light%20blue</span>
<span class="query-legend-desc">Useful parameter — tells the page which color variant to show</span>
</div>
<div class="query-legend-item" role="listitem" data-qparam="utm">
<span class="query-legend-label query-param-label">utm_source=facebook</span>
<span class="query-legend-desc">⚠️ Tracking parameter — tells the site you came from Facebook</span>
</div>
<div class="query-legend-item" role="listitem" data-qparam="fbclid">
<span class="query-legend-label query-param-label">fbclid=abc123xyz</span>
<span class="query-legend-desc">⚠️ Tracking parameter — Facebook's click ID to track your activity</span>
</div>
</div>
</div>
<script>
(function() {
const container = document.getElementById('query-container');
const queryExample = container.querySelector('.query-example');
const exampleSpans = container.querySelectorAll('.query-example span[data-qpart]');
const paramGroups = container.querySelectorAll('.query-param-group');
const legendItems = container.querySelectorAll('.query-legend-item');
function clearHighlights() {
container.classList.remove('hovering');
exampleSpans.forEach(s => s.classList.remove('highlighted'));
paramGroups.forEach(g => g.classList.remove('highlighted'));
legendItems.forEach(l => l.classList.remove('highlighted'));
}
// Hovering spans in the example URL - highlight only that single element + related legend items
exampleSpans.forEach(span => {
span.addEventListener('mouseenter', (e) => {
e.stopPropagation(); // Prevent parent group from triggering
clearHighlights();
container.classList.add('hovering');
// Highlight only THIS span
span.classList.add('highlighted');
// Highlight the Structure legend item for this part type
const qpart = span.dataset.qpart;
if (qpart) {
legendItems.forEach(item => {
if (item.dataset.qpart === qpart) {
item.classList.add('highlighted');
}
});
}
// Highlight the Parameter legend item for this parameter
const qparam = span.dataset.qparam;
if (qparam) {
legendItems.forEach(item => {
if (item.dataset.qparam === qparam) {
item.classList.add('highlighted');
}
});
}
});
span.addEventListener('mouseleave', clearHighlights);
});
// Hovering legend items
legendItems.forEach(item => {
item.addEventListener('mouseenter', () => {
clearHighlights();
container.classList.add('hovering');
// Highlight this legend item
item.classList.add('highlighted');
const qpart = item.dataset.qpart;
const qparam = item.dataset.qparam;
// Structure legend: highlight ALL matching URL parts
if (qpart) {
exampleSpans.forEach(span => {
if (span.dataset.qpart === qpart) {
span.classList.add('highlighted');
}
});
}
// Parameter legend (example): highlight the param GROUP container
if (qparam) {
paramGroups.forEach(group => {
if (group.dataset.qparam === qparam) {
group.classList.add('highlighted');
}
});
}
// Scroll to first matching element
let scrollTarget = null;
if (qpart) {
const firstPart = container.querySelector('.query-example [data-qpart="' + qpart + '"]');
if (firstPart) scrollTarget = firstPart;
} else if (qparam) {
const group = container.querySelector('.query-param-group[data-qparam="' + qparam + '"]');
if (group) scrollTarget = group;
}
if (scrollTarget) {
const containerRect = queryExample.getBoundingClientRect();
const partRect = scrollTarget.getBoundingClientRect();
const partCenter = partRect.left - containerRect.left + queryExample.scrollLeft + (partRect.width / 2);
const scrollPos = partCenter - (containerRect.width / 2);
queryExample.scrollTo({ left: scrollPos, behavior: 'smooth' });
}
});
item.addEventListener('mouseleave', clearHighlights);
});
})();
</script>
explain what each part does
explain why a website would want to use query parameters
teach that tech companies then started to use that information to track how users use their websites
explain why that is a bad thing for privacy, and personal life
give example of tracking links and how to protect against them