Every part of a URL does something
The origin — scheme, host and port together — is the unit of security on the web. Two pages share an origin only if all three match; http and https on the same host are different origins, and so are ports 443 and 8443. Almost every rule in this course is phrased in terms of origins.
https://shop.doors.test:8443/catalogue/pivot?colour=steel&sort=price#specs
└─┬─┘ └───────┬───────┘└─┬─┘└──────┬───────┘└────────┬─────────┘└─┬─┘
scheme host port path query fragment
const url = new URL(location.href);
url.protocol // "https:"
url.hostname // "shop.doors.test"
url.port // "8443" (empty string when it is the default)
url.pathname // "/catalogue/pivot"
url.searchParams.get("colour") // "steel"
url.hash // "#specs"
url.origin // "https://shop.doors.test:8443"
Everything after # is for the page, not the server: it is not sent in the request and never appears in a server log. Which also means it is the wrong place to hide anything, and the right place for a scroll target or a client-side route.
Finding the server: DNS
A host name is not an address. Before anything can be sent, the browser has to turn shop.doors.test into an IP address, and it asks in a chain: its own cache, the operating system, then a resolver — usually your ISP's or one like 1.1.1.1 — which walks the root servers, the .test servers and finally the name servers for doors.test.
Each answer carries a TTL, and everyone along the way caches it for that long. That is why a DNS change "takes a while": you are waiting for other people's caches to expire, and nothing you do can shorten it.
A doors.test → 93.184.216.34 (IPv4)
AAAA doors.test → 2606:2800:220:1:: (IPv6)
CNAME www.doors.test → doors.test (an alias)
MX doors.test → mail server (email)
TXT doors.test → verification, SPF, DKIM
What the first request really costs
A first visit to a new host pays all four before a single byte of HTML arrives — which is the whole argument against loading fonts, analytics and widgets from five different domains. Every extra host is another DNS lookup and another handshake.
| Step | Typical cost | Happens again? |
|---|---|---|
| DNS lookup | 20–120 ms | no, until the TTL expires |
| TCP handshake | 1 round trip | no, the connection is reused |
| TLS handshake | 1–2 round trips | no (and resumption makes it cheaper) |
| The request itself | 1 round trip | every request |
<link rel="preconnect" href="https://fonts.example"> starts the DNS and the handshakes before the request is needed; dns-prefetch does the cheapest part only. Use preconnect for the one or two origins that matter, not for ten — each open connection costs something too.
Encoding, and why URLs are so fussy
Concatenating strings to build a URL is how & in a product name becomes an extra parameter. URL and URLSearchParams do the encoding correctly, and they are in every browser and in Node.
encodeURIComponent("steel & glass") // "steel%20%26%20glass" — for one value
encodeURI("https://a.test/a b") // keeps :/?#&= — for a whole URL
// building a query string properly
const url = new URL("https://api.doors.test/search");
url.searchParams.set("q", "steel & glass");
url.searchParams.set("page", "2");
url.toString(); // …/search?q=steel+%26+glass&page=2