The earlier pages in this section, from the element to links people can use, looked at links from the author's side. This one looks from both sides. A reader needs to know how a URL can mislead them. An author needs to know what makes a link trustworthy: an address that says where it goes, no surprise detours, no data about the reader riding along, and a destination that still exists in ten years. The URLs page covers the address itself, including URL security and cool URIs; this page builds on both rather than repeating them.
1. Reading a URL before following it
Most people judge a link by its text, and the text can say anything. The address underneath is the only thing that decides where a click goes, and within the address, one part matters far more than the rest: the host, and within the host, the registrable domain.
Read the host from the right
A host name is a list of labels separated by dots, and its authority runs from right to left. In www.cse134.site, site is the top-level domain, cse134.site is the name someone registered and paid for, and www is a subdomain that the owner of cse134.site created, as they can create any other. The registered part, one label plus its public suffix, is the registrable domain, and it is the answer to the question that matters: who controls this server?
The public suffix is not always one label. Anyone can register a name under co.uk or github.io, so the registrable domain of news.bbc.co.uk is bbc.co.uk, three labels. Browsers know which suffixes are public from the Public Suffix List, a shared file of several thousand entries, and use it for cookies, for the "site" in same-site rules and for what they emphasize in the address bar.
Everything to the left of the registrable domain is chosen by its owner. That is the whole of the subdomain trick: an attacker who owns account-check.example can create example.com.account-check.example, or login.yourbank.com.account-check.example, and the familiar name at the start is just a label. People read left to right and stop when they recognize something, so the trick works.
Lookalikes and typosquats
The registrable domain itself can be the disguise. A lookalike domain differs from a real one by something easy to miss: a digit 1 for a lowercase l, rn for m, an added hyphen, or .co for .com. A typosquat is a domain registered to catch people who mistype the real one. Both are cheap to register and, with a free certificate, show the same padlock as the real site. The padlock means the connection is encrypted to whoever owns that name. It says nothing about who that is.
The userinfo trick
The URL syntax allows a username and password before the host, separated from it by @: https://user:password@host/. It is now mostly a way to put a misleading name at the front of an address. In https://bank.com@evil.example/, bank.com is a username, and the request goes to evil.example. Browsers have grown wary of it. They never send the userinfo in a Referer, they hide it in the address bar, and some show a warning before following such a link: Firefox asks whether you meant to sign in to evil.example as bank.com. In an email or a chat message, nothing hides it, and the first thing a reader sees is the name they trust.
Homographs and punycode
Domain names can be written in most of the world's scripts: 例え.jp and bücher.example are valid. Underneath, DNS still only carries ASCII, so each non-ASCII label is converted to a form called punycode and given the prefix xn--. bücher becomes xn--bcher-kva. The URL API does the conversion, as the browser does, so new URL('https://bücher.example/').hostname is 'xn--bcher-kva.example'.
The problem is that several scripts contain letters that look identical. Cyrillic has its own а, е, о, р and с, each a different character from the Latin letter it resembles. A domain spelled with them is a homograph: it looks exactly like the real name and is a different domain. In 2017 a researcher registered xn--80ak6aa92e.com, which is apple.com spelled entirely in Cyrillic, and showed that Chrome and Firefox displayed it as apple.com with a valid certificate. Chrome fixed it in version 58 that April, and browsers now have rules for when to show the Unicode form and when to show xn-- instead. Chrome shows punycode when a label mixes Latin with Cyrillic or Greek, or is written entirely in letters that could pass for Latin ones, as in that example. The rules differ between browsers, apps and email clients. A link in a message may show the Unicode form even when the address bar would not.
The practical rule: an xn-- in a host you expected to be plain English is a reason to stop. In the address bar, it is the browser telling you it does not trust how the name looks.
The text says one thing, the href another
The oldest trick needs no clever domain at all. A link's text and its href are independent, so the text can be a URL that is not the destination:
Before following a link that matters, look at where it goes. On a computer, rest the pointer on it and read the address the browser shows at the bottom of the window. On a phone, press and hold the link: the menu that appears shows the address, and offers to copy it instead of opening it. The same works in most email clients.
The demo takes a URL apart the way the browser does, using the URL API, and points out the tricks in this section. Every preset uses reserved names such as .example and .test, which cannot be registered, so none of them leads anywhere.
The demo's registrable-domain line is a guess, because it does not load the Public Suffix List. That is the lesson: "is this the same site?" needs a list of thousands of entries, so leave it to the browser or a maintained library, not a regular expression.
2. Short links
Short links are everywhere: on posters, in text messages and posts, under QR codes, and inside links that platforms rewrite without asking. They combine almost every problem on this page in one address.
A short link is a redirect
A short link is a URL whose server answers with a redirect and nothing else. The shortening service keeps a table from short codes to long addresses. When a request arrives for https://bit.ly/3abcXYZ, it looks up 3abcXYZ and answers with a 301 or 302 and a Location header, exactly the redirects described in What a Click Does. This site has its own demo addresses that work the same way:
That redirect is not free. Before the browser can ask for the page the person wanted, it has to look up the shortener's host name in DNS, open a connection and negotiate TLS with it, send a request and wait for the answer. Only then does it start again with the real host: another DNS lookup, another connection, another request. On a phone on a slow network, each of those steps can take a few hundred milliseconds, and the person sees an empty tab the whole time. Before the First Byte follows the same steps for a single host.
Why they exist
Short links solve real problems:
- Character limits. Text messages and some social platforms count characters, and a long address with a query string can use most of a post. Twitter's 140 characters made the first wave of shorteners popular.
- Print and speech. A long address cannot be typed from a poster or read aloud on a podcast.
cse134.site/go/syllabuscan. - QR codes. A QR code is a URL a camera can read, and a shorter URL makes a simpler code that scans more reliably. Most printed codes also point at a short link because the print cannot change and the redirect behind it can.
- Counting clicks. Every click passes through the shortener's server, so it can count them: when, from where, on which device, from which site. For many users that report is the product.
What goes wrong
Every one of those benefits has a cost, and most of the costs fall on the person clicking.
- The destination is hidden. Everything in section 1 depends on reading the address, and a short link has no address worth reading.
bit.ly/3abcXYZcould lead to a university page or to a lookalike login form, and the reader cannot tell which without following it. That is why phishing campaigns use shorteners: they hide the registrable domain, the one part that would give the attack away. - The shortener sees every click. The service records the time, the IP address, the browser and the referring page of everyone who follows the link, and it is a third party to both the author and the reader. Its privacy policy, not yours, governs that record.
- Redirect chains. Links pass through several services on the way to their destination. X wraps every link in a post in its own
t.coaddress, and counts it as 23 characters whatever its length; LinkedIn useslnkd.in; email newsletters rewrite every link through their click-tracking host; corporate mail scanners add another layer. A link shared through two or three of these can take four or five hops before it reaches the page, each a round trip, and each with its own record of the click. - The link dies with the service. A short link works only as long as its company keeps the lookup table and the server running. Shorteners close, get acquired, change their terms, or start charging for links that were free. When that happens, every short link it ever issued breaks at once, in every document, slide and printed poster that used one.
- The destination can change after it is shared. The person who controls a short link can point it somewhere else at any time. That is the feature that makes printed QR codes practical, and it also means a link that was safe when it was checked, shared and approved can lead somewhere else tomorrow, if its account is taken over, or if its domain is sold.
- Filters block them. Because shorteners hide destinations so well, spam filters and email security gateways treat them with suspicion, and some block shortener domains outright. A short link in a class announcement can send the whole message to spam.
The case of goo.gl
Google's own shortener shows how a link can die with its service, even when the service belongs to one of the largest companies on the web. Google launched goo.gl in 2009 and stopped accepting new links in 2019, while promising that existing links would keep redirecting. On 18 July 2024 it announced that they would not: after 25 August 2025, every goo.gl link would stop responding, and from 23 August 2024 some would first show a warning page. Those links had been published for fifteen years, in papers, books, documentation, slides and printed material.
After a year of objections, Google partly reversed the decision on 1 August 2025: only links that had shown no activity in late 2024 would be deactivated, and all others would keep working. Links made by Google's own apps, such as shared Maps locations, were never affected. The result is the worst kind of link rot, because it is invisible: a reader cannot tell from a goo.gl link in an old paper whether it is one of the survivors, and the decision about which links lived was made by someone else, on a measure of popularity the author never saw.
Expand a link before following it
A short link can be read before it is followed, by asking its server where it goes without going there. Several services offer a preview for this. Add a + to the end of a Bitly link, as in https://bit.ly/3abcXYZ+, and instead of redirecting, Bitly shows a page with the destination's address and title and the date the link was made. Not every shortener has a preview, and a preview is only as honest as the service.
The general tool is curl, which is installed on macOS, Windows and most Linux systems. With -I it sends a HEAD request, asking only for the headers, and it does not follow redirects unless told to, so it prints the first Location and stops. Add -L to follow every hop, printing each response along the way:
Neither command runs the destination page or shows it, and without -L curl never contacts the destination at all.
The demo does the same from a web page. The site's Worker follows a link's redirects one hop at a time and reports each one's status, Location and time. It is deliberately limited: it only follows this site's /go/ addresses and a handful of well-known shorteners, sends HEAD requests, never returns a page's content, and stops after five hops. When a hop points anywhere else, it reports the address and does not request it. A server that would fetch any address anyone typed would be an open proxy, a tool for probing other servers with this site's name on the requests. For any other link, use curl.
Short links you publish
The fix for most of these problems, when you are the one publishing a link, is to make the short link yourself, on your own domain. A path like cse134.site/go/syllabus is as short as most shortener links. It shows the reader whose site it leads to. It tells no third party about the click. It lives exactly as long as your domain does, which is the same promise the rest of your site makes. And it can still be changed, by you, if the page behind it moves.
Most hosts make this a one-line job. Static hosts such as Cloudflare and Netlify read a _redirects file, one rule per line: the short path, the destination and the status. A server or Worker can answer the same way in a few lines of code, as this site's inspector shows with its /go/ addresses.
Choose the status by how long the link is meant to last. A 301 says the move is permanent: browsers cache it and search engines transfer the old address's standing to the new one, so use it for links that should always lead to the same place, such as the syllabus. A 302 says "for now", so browsers do not cache it unless the response says to, and it is right for a link you expect to repoint, such as this week's survey. Keep the old short paths working when you change things; a short link printed on a handout is a promise too.
The same applies to QR codes. A QR code that points straight at a long address cannot be fixed when the page moves, and one that points at a third-party QR or shortener service stops working when that service does, or when its free tier ends, which is a common surprise with "dynamic QR code" generators. Point printed codes at a short path on your own domain, and redirect from there. The code stays valid as long as the domain does, and the destination stays in your hands.
3. Tracking parameters
Many links carry more than an address. Copy a link from a newsletter, an ad or a social app and it often ends in a query string like this:
The page is https://shop.example/boots. Everything after the ? is for the site's analytics. Parameters starting with utm_ (from Urchin, the analytics company Google bought and turned into Google Analytics) describe a campaign: where the link was placed, through which medium, as part of which campaign. They are the same for everyone who receives the link. Click identifiers are different: gclid (Google Ads), fbclid (Facebook), msclkid (Microsoft Advertising), ttclid (TikTok), mc_eid (Mailchimp, identifying the email recipient) and many others are unique values that tie this particular click to a particular ad impression or recipient, so that the advertiser can join a visit on one site to a person profiled on another.
A shared link with a click id says something about the person who shared it: that they came from an ad, or, with mc_eid, which email recipient they were. And everyone who follows it is counted as that original click, which corrupts the analytics too.
Browsers that strip them
Because click ids are used to track people across sites, browsers with tracking protection have started removing the best-known ones from addresses as a navigation happens:
- Firefox added query parameter stripping in version 102 (2022). It removes a list of known click ids when Enhanced Tracking Protection is set to Strict, and since version 120 its context menu offers Copy Link Without Site Tracking in every mode.
- Safari added Link Tracking Protection in Safari 17 (2023): known click ids are removed in Private Browsing, and from links opened from Mail and Messages.
- Brave has stripped known click ids by default, on every navigation, since 2020.
All three target parameters that identify a person or a click, such as fbclid, gclid and mc_eid. None of them removes utm_ parameters by default, because those describe a campaign rather than a person, and removing them would break ordinary analytics without much privacy gain. Chrome strips none of them.
Cleaning links before sharing
The reliable fix is to delete them yourself. Before pasting a link into a message or a page, look at the query string and remove anything that is not part of the address. Most pages work without any query string at all; a search results page or a product variant is the exception, so test the cleaned link once. A script can do the same with the URL API:
For links you publish, the same thinking applies from the other side. Campaign tags on your own site's links are a reasonable way to learn which announcement worked, but they make the address longer and uglier, they leak into every copy that gets shared, and they say to every reader that they are being counted. Use them deliberately, mention them in your privacy notice, and never put anything identifying a person in a link's query string. What a Click Does showed what the Referer header sends; tracking parameters send whatever the author chooses, to whoever receives the link. The Privacy page puts both in the larger picture.
4. Link rot
A link that worked when it was written stops working when its target moves or disappears. This is link rot, and its slower cousin, content drift, is a link that still works but leads to a page that no longer says what it said when it was cited. Both are measured, and the numbers are large.
- In 2014, Jonathan Zittrain, Kendra Albert and Lawrence Lessig at Harvard checked the links in legal citations. More than 70% of the URLs in the Harvard Law Review and other law journals, and 50% of those in United States Supreme Court opinions, no longer led to the material originally cited.
- In 2024, the Pew Research Center sampled pages from Common Crawl, a public archive of the web, and found that 38% of the pages that existed in 2013 were no longer available a decade later. About a quarter of all pages that existed at some point between 2013 and 2023 were gone. Of the news and government pages it checked, 23% and 21% contained at least one broken link, and 54% of the English Wikipedia pages it sampled had at least one broken link in their references.
The Cool URIs section of the URLs page covers the author's half of the fix: Tim Berners-Lee's argument that URLs do not change, people change them, and the habits that make an address last. When an address must change anyway, a 301 from the old address to the new one keeps every link anyone ever made to it working. This site does exactly that for every page it moved in its reorganization, as What a Click Does showed. A redirect costs one line of configuration and a round trip; a broken link costs every reader who follows it.
The Wayback Machine and archive links
For links to other people's pages, which you cannot keep alive, the answer is an archive. The Internet Archive's Wayback Machine has saved copies of the web since 1996, and anyone can ask it to save a page now, at web.archive.org/save. An archived copy has an address that includes the time it was saved, such as https://web.archive.org/web/20240101000000/https://example.com/, and a short form, https://web.archive.org/web/https://example.com/, that leads to the most recent copy.
When a link in something you write is the evidence for a claim, link to the live page and archive it at the same time, so a reader has the original if the live page changes or disappears. Wikipedia's bots replace dead references with archived copies, and the Harvard study's authors built Perma.cc for courts and journals to do the same. HTML has no attribute for an archived copy, so the convention is a second link beside the first:
For your own site, run a link checker on a schedule. It reports links that answer 404 or 410, and ones that now redirect to a home page, which is often a missing page in disguise.
5. Showing a page instead of linking to it
A link sends the reader to another document. An <iframe> brings the other document to the reader: it shows a complete, separate page inside a rectangle of the current one. Video players, maps, payment forms, code playgrounds and the <browser-window> demos on this site are all iframes. The framed page keeps its own URL, scripts and origin; the framing page decides only where it goes, how big it is, and what it may do.
titlenames the frame for screen readers, which announce a frame and its name when the reader enters it. Without one, people hear "frame" and nothing more. It should say what is in the frame, not that it is a frame.widthandheight, or a size in CSS, reserve the space before the page loads, as they do for images.loading="lazy"waits to load the frame until it is near the viewport. A framed page is a whole page, often with megabytes of script, so this matters more for frames than for images. It is supported in every current browser.referrerpolicyworks as it does on links, and controls what the framed site learns about the page it is embedded in.
The sandbox attribute
A framed page from another origin cannot read the page around it: the same-origin policy keeps the two documents apart. It can still do a great deal on its own: run scripts, open pop-ups, submit forms, start downloads, show alert() dialogs, and, in some cases, navigate the whole tab to another address. The sandbox attribute takes all of that away. An empty sandbox applies every restriction at once, and each token added to it gives one capability back:
| Token | Gives back |
|---|---|
| (none) | Nothing. No scripts, no forms, no pop-ups, no downloads, no top navigation, and the content gets a unique, opaque origin, so it has no cookies or storage. |
allow-scripts | Running JavaScript |
allow-same-origin | The content's real origin, with its cookies and storage |
allow-forms | Submitting forms |
allow-popups, allow-popups-to-escape-sandbox | Opening new windows, and letting them run without the sandbox |
allow-top-navigation-by-user-activation | Navigating the whole tab, but only when the person clicked something |
allow-top-navigation | Navigating the whole tab at any time |
allow-modals, allow-downloads | alert() and other dialogs; downloads |
Give back only what the content needs. An embedded widget that only displays something needs allow-scripts at most; a payment form may need allow-forms and allow-popups too. The one combination to understand is allow-scripts allow-same-origin on content from your own origin. Scripts in the frame then run with the same origin as the page around them, which lets them reach into the parent document, find their own <iframe> element and remove its sandbox attribute. The sandbox is then decoration. On content from another origin, the pair is safe enough, since the same-origin policy still separates the two pages, which is why many embed codes ask for it. For untrusted HTML you show yourself, such as a preview of what a user wrote, use srcdoc with an empty sandbox, or sandbox="allow-scripts" without allow-same-origin:
The allow attribute
Powerful browser features, the camera, the microphone, location, full screen, payments and the clipboard among them, are governed by Permissions Policy. A frame from another origin does not get most of them by default. The allow attribute grants them, feature by feature, and the person is still asked for permission as usual:
sandbox and allow are independent. One removes general abilities, the other grants specific features, and a frame can have both.
The framed page's side
The framing page controls its frames. The framed page controls whether it can be framed at all, and it should care. In a clickjacking attack, a page frames another site, makes the frame invisible, and lays it over something the person wants to click, such as a "play" button. The person's click lands in the invisible frame, on the real site, where they are signed in: a "delete account", "confirm payment" or "allow camera" button the attacker has positioned under the cursor.
The defense is a response header on every page that should not be framed by other sites, which is most pages that do anything once a person is signed in. The current way is the frame-ancestors directive of Content Security Policy, which lists who may frame the page. The older X-Frame-Options header does the same with two useful values, DENY and SAMEORIGIN; browsers that support frame-ancestors ignore it when both are present, and sending both covers old browsers too:
These must be headers: frame-ancestors is ignored in a <meta> element, because by the time the HTML is read, the page is already framed. Security covers the rest of the headers a site should send.
6. Where JavaScript takes links next
Everything in this section has assumed that a link is followed by the browser. In a single-page application, it often is not. A client-side router listens for clicks on links, cancels the navigation with preventDefault(), fetches the data for the new view, updates the page in place and changes the address bar with the History API. Done badly, it throws away everything a real link provides.
A router has to keep, by hand, what the browser did for free:
- Real
hrefvalues. Every link is still an<a href>with the real address of the view, so it can be opened in a new tab, copied, bookmarked, shared, read by a search engine and followed when the script fails to load. The router enhances links; it does not replace them with<div>s and click handlers. - Real URLs. Every view has its own address, the server answers that address with a working page when it is requested directly, and reloading shows the same view.
- The back button. Each navigation adds a history entry, and going back restores the previous view.
- Modifier keys. A click with Ctrl, Cmd, Shift or the middle button is left alone, so the browser can open the new tab the person asked for.
- Scroll position. A new view starts at the top; going back returns to where the person was.
- Focus and announcement. After a real navigation, a screen reader announces the new page's title and starts reading from the top. After a script swaps the content, nothing happens unless the router moves focus to the new content, usually its heading, and updates
document.title. Without that, a screen reader user activates a link and hears nothing at all.
The Navigation API was designed to make this easier. Instead of listening for clicks on links, a page listens for every navigation, whatever started it, and can choose to handle it with a script. The browser keeps the parts that routers used to get wrong: the address bar, history entries, the back button, the loading indicator, and scroll restoration. It reached every major browser in January 2026 (Chrome 102, Firefox 147, Safari 26.2), so older browsers still need a fallback, which real links already are:
Often, a router is not needed at all. Much of the reason for building one was the look of a navigation: the white flash between pages, and the lack of an animated transition. Cross-document view transitions give ordinary links an animated transition between two separate pages, with two lines of CSS on each, and the links stay ordinary links:
Chromium browsers and Safari 18.2 and later support it; Firefox, at the time of writing, ignores the rule and navigates as it always has. That is the pattern this course has followed throughout: start with HTML that works, a real link to a real address, and let CSS and JavaScript make it better without taking anything away.
HTML is the start. A link is the oldest interaction on the web and still the most reliable one: it works without script, it works for every input method and every assistive technology, it can be bookmarked, shared, archived and cited, and it costs one element and one attribute. The Forms section that follows is about the other half of the conversation, when the reader stops following and starts sending something back.
7. Summary
| Concept | Key points |
|---|---|
| Reading a URL | The registrable domain decides who runs the server; read the host from the right. Subdomains are the owner's choice. Lookalikes and typosquats swap characters. The padlock says the connection is encrypted, not who owns the name. |
| Userinfo and homographs | In https://bank.com@evil.example/, the host is evil.example. Non-ASCII hosts become punycode (xn--); browsers show it when a name looks like a lookalike. Hover or long-press to see a link's real address. |
| Short links | A server that answers with a 301 or 302. Useful for limits, print, speech, QR codes and counting. Costs a round trip per hop, hides the destination, tells a third party about every click, can be repointed, gets filtered, and dies with the service (goo.gl, 2024–2025). |
| Expanding a link | A preview such as Bitly's +; curl -I for one hop, curl -IL for all of them; the inspector demo for /go/ addresses and known shorteners. |
| Publishing short links | Your own domain and short paths, from a _redirects file or server code. 301 for links meant to last, 302 for links you will repoint. Printed QR codes point at your own domain. |
| Tracking parameters | utm_* tags a campaign; click ids (gclid, fbclid, msclkid, mc_eid) tag a person or a click. Firefox, Safari and Brave strip known click ids in some modes; none strips utm_* by default. Remove them before sharing. |
| Link rot | Measured at 50% of Supreme Court opinion links (2014) and 38% of 2013 web pages gone by 2023 (Pew, 2024). Fix your own with 301s; cite others with an archived copy beside the live link; check outgoing links on a schedule. |
iframe | Shows another document in place. Always a title; loading="lazy"; referrerpolicy. sandbox removes abilities, its tokens give them back; never allow-scripts allow-same-origin on same-origin untrusted content. allow delegates features such as the camera. |
| Being framed | Clickjacking hides a framed page under a decoy. Refuse framing with Content-Security-Policy: frame-ancestors (headers only), plus X-Frame-Options for old browsers. |
| Links and JavaScript | A router keeps real hrefs and URLs, the back button, modifier keys, scroll, focus and an announcement. The Navigation API leaves more to the browser; view transitions animate ordinary links. HTML is the start. |
