How a URL Becomes a Page
The complete journey from keypress to pixels.
I type https://barathraju.com into my browser and hit Enter.
A page appears.
That's it. That's the whole experience. Less than a second. So unremarkable that I do it a hundred times a day without thinking.
But here's the thing — between my finger leaving the Enter key and those first pixels lighting up on my screen, an absurd amount of engineering has to happen. Dozens of systems, protocols, and machines coordinate across the globe, often in under 300 milliseconds.
I build web applications for a living. I deploy them, I debug them, I ship them. And until I sat down to trace this path end to end, I was hand-waving through most of it.
This is that trace.
The Mental Model
Before we dive into each step, here's the complete journey at a glance:
You type a URL and hit Enter
│
▼
┌─────────────────────┐
│ URL Parsing │ → What did you actually type?
└─────────┬───────────┘
▼
┌─────────────────────┐
│ Cache Check │ → Have we been here before?
└─────────┬───────────┘
▼
┌─────────────────────┐
│ DNS Resolution │ → What's the IP address?
└─────────┬───────────┘
▼
┌─────────────────────┐
│ TCP Connection │ → Establish a reliable channel
└─────────┬───────────┘
▼
┌─────────────────────┐
│ TLS Handshake │ → Make it secure
└─────────┬───────────┘
▼
┌─────────────────────┐
│ HTTP Request │ → Ask for the page
└─────────┬───────────┘
▼
┌─────────────────────┐
│ Server Processing │ → Build the response
└─────────┬───────────┘
▼
┌─────────────────────┐
│ HTTP Response │ → Send back the HTML
└─────────┬───────────┘
▼
┌─────────────────────┐
│ Rendering Pipeline │ → Turn HTML into pixels
└─────────┘───────────┘
▼
You see a page.
Each of these steps is its own world. Let's walk through them.
Step 1: URL Parsing — What Did You Actually Type?
The browser's first job is surprisingly mundane: figure out what you meant.
When you type barathraju.com into the address bar, you didn't type a valid URL. There's no protocol. The browser has to guess that you meant https://barathraju.com.
A full URL has structure:
https://barathraju.com:443/blog?page=1#introduction
│ │ │ │ │ │
scheme host port path query fragment
- Scheme (
https): Which protocol to use. - Host (
barathraju.com): The human-readable name of the server. - Port (
443): The door number on the server. HTTPS defaults to 443, HTTP to 80. - Path (
/blog): Which resource on the server. - Query (
?page=1): Parameters. Like filling out a form. - Fragment (
#introduction): A bookmark within the page. This never leaves your browser.
That last point catches people off guard. The fragment is never sent to the server. It's purely a client-side instruction. The server has no idea you wanted to jump to #introduction.
Step 2: Cache Check — Have We Been Here Before?
Before the browser calls anyone, it checks its own memory.
Browsers are aggressive cachers. They store copies of pages, images, scripts, and even DNS lookups. If you visited barathraju.com five minutes ago, the browser might not need to go to the network at all.
There are several cache layers:
Your request
│
▼
┌──────────────┐
│ Memory Cache │ → Did the current tab already fetch this?
└──────┬───────┘
▼
┌──────────────┐
│ Disk Cache │ → Did a previous session fetch this?
└──────┬───────┘
▼
┌──────────────┐
│ Service Worker│ → Is there a programmable cache?
└──────┬───────┘
▼
Network (only if nothing was cached)
The server controls this behavior through cache headers:
Cache-Control: max-age=3600
This tells the browser: "You can reuse this response for 3600 seconds without asking me again."
Other important cache headers:
ETag— A fingerprint of the content. If the content hasn't changed, the server says "use what you have" (304 Not Modified).Last-Modified— When the content was last changed.Vary— "The response might be different based on these request headers" (like language or encoding).
I initially thought caching was simple — just store stuff and reuse it. It's not. Cache invalidation is famously one of the two hardest problems in computer science (the other being naming things). We'll cover this deeply in Day 7 when we talk about caching.
Step 3: DNS Resolution — What's the IP Address?
Assuming the cache didn't help, the browser needs to find the server. But it only has a human-readable name: barathraju.com. Computers don't route traffic to names — they route to IP addresses like 76.76.21.21.
This is where DNS (Domain Name System) comes in.
The Analogy
Think of DNS as the phone book of the internet. You know the name of the person you want to call (the domain), but you need their phone number (the IP address) to actually connect.
Except this phone book is distributed across millions of servers worldwide, is hierarchical, cached at every level, and is one of the most critical pieces of infrastructure on the internet.
The Lookup Chain
DNS resolution doesn't just ask one server. It walks a hierarchy:
Browser
│
▼
Operating System DNS Cache
│ (Has your OS seen this domain recently?)
▼
Router / ISP DNS Resolver
│ (Has your ISP resolved this recently?)
▼
Root Name Server ← "I don't know barathraju.com,
│ but I know who handles .com"
▼
TLD Name Server (.com) ← "I don't know barathraju.com,
│ but here's who does"
▼
Authoritative Name Server ← "barathraju.com? That's 76.76.21.21"
│
▼
Response flows back up the chain
There are only 13 root name server addresses in the entire world (though each address actually runs on hundreds of physical servers via anycast). These 13 addresses are the starting point for every DNS lookup that can't be resolved from cache.
Try It Yourself
Open your terminal and run:
dig barathraju.com
You'll see something like:
;; ANSWER SECTION:
barathraju.com. 300 IN A 76.76.21.21
;; Query time: 23 msec
That 300 is the TTL (Time To Live) in seconds — how long this answer can be cached before someone needs to ask again.
The Hidden Cost
DNS resolution typically takes 20–120 milliseconds. That might sound fast, but remember — nothing else can happen until this completes. The browser can't open a connection, can't send a request, can't render a pixel until it knows where to go.
This is why browsers pre-resolve DNS for links on a page. When you load a page with 20 outbound links, the browser quietly resolves the DNS for those domains in the background, so that if you click one, it's instant.
<!-- Developers can hint at this explicitly -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
Step 4: TCP Connection — Let's Talk Reliably
Now the browser knows the IP address. Time to connect.
The internet is fundamentally unreliable. Packets get lost. They arrive out of order. They get duplicated. Different routes have different speeds.
TCP (Transmission Control Protocol) is the protocol that turns this chaos into a reliable, ordered conversation.
The Three-Way Handshake
Before a single byte of your page is sent, the browser and server perform a handshake:
Browser Server
│ │
│──── SYN ─────────────────────▶│ "Hey, I want to talk"
│ │
│◀─── SYN-ACK ─────────────────│ "Sure, I'm ready too"
│ │
│──── ACK ─────────────────────▶│ "Great, let's go"
│ │
│ Connection established │
This takes one round trip (your message goes there, comes back, and you confirm). For a server 50ms away, that's 50ms of just... agreeing to talk.
Why Not Just Send the Request Immediately?
Because the internet doesn't guarantee anything. TCP needs to establish:
- Both sides are alive — The server might be down.
- Sequence numbers — So packets can be reassembled in order.
- Window sizes — How much data each side can handle before acknowledging.
Without TCP, you'd be sending data into the void and hoping for the best. That's actually what UDP does — and it's great for video calls (a few lost frames don't matter) but terrible for loading a webpage (you need every byte of that JavaScript file).
Step 5: TLS Handshake — Make It Private
If the URL starts with https (and it almost always should), there's another handshake before any data flows: TLS (Transport Layer Security).
Why TLS Matters
Without TLS, your HTTP request travels across the internet in plain text. Every router, ISP, and coffee shop Wi-Fi access point between you and the server can read your passwords, cookies, and credit card numbers.
TLS encrypts everything.
The TLS Handshake (Simplified)
Browser Server
│ │
│── ClientHello ────────────────────────▶│
│ (supported ciphers, TLS version) │
│ │
│◀── ServerHello ────────────────────────│
│ (chosen cipher, certificate) │
│ │
│ Browser verifies certificate │
│ (Is this really barathraju.com?) │
│ │
│── Key Exchange ───────────────────────▶│
│ (both sides derive shared secret) │
│ │
│◀── Finished ──────────────────────────│
│ │
│ Encrypted channel established │
The certificate is crucial. It's a cryptographic proof that the server is who it claims to be. Certificates are signed by Certificate Authorities (CAs) — trusted third parties your browser already knows about.
The Performance Cost
TLS adds 1–2 additional round trips on top of TCP. For a server 100ms away:
DNS: ~50ms
TCP handshake: ~100ms (one round trip)
TLS handshake: ~100-200ms (one to two round trips)
────────────────────────
Total before any data: ~250-350ms
We've spent 250–350 milliseconds and haven't asked for a single byte of the page yet.
This is why TLS 1.3 was a big deal — it reduced the TLS handshake to one round trip (or even zero for repeat connections with 0-RTT resumption). And HTTP/3 eliminates the separate TCP handshake entirely by building on QUIC, which combines transport and encryption into a single handshake.
Step 6: The HTTP Request — Ask for the Page
Finally, the browser can speak.
It sends an HTTP request. At its core, HTTP is just structured text:
GET /blog HTTP/2
Host: barathraju.com
User-Agent: Mozilla/5.0 ...
Accept: text/html
Accept-Language: en-US
Accept-Encoding: gzip, br
Cookie: session=abc123
Connection: keep-alive
Let's break this down:
GET /blog— "I want the resource at/blog."GETmeans "give me something" (as opposed toPOSTwhich means "I'm sending you something").Host— Which website. A single server can host multiple sites (virtual hosting).Accept— "I prefer HTML." The server might also have JSON or XML versions.Accept-Encoding— "I can understand compressed responses." Brotli (br) is more efficient than gzip and is widely supported.Cookie— Previously stored data. This is how the server knows you're logged in.
HTTP/2 — The Multiplexing Revolution
HTTP/1.1 had a fundamental problem: one request at a time per connection. If a page needed 30 resources (HTML, CSS, JS, images), the browser would need multiple connections.
HTTP/2 introduced multiplexing — multiple requests and responses can flow over a single connection simultaneously, interleaved as binary frames:
HTTP/1.1 (sequential):
────────────────────────────────────
│ Request 1 │ Response 1 │ Request 2 │ Response 2 │ ...
────────────────────────────────────
HTTP/2 (multiplexed):
────────────────────────────────────
│ Req1 │ Req2 │ Req3 │ Resp1 │ Resp2 │ Resp3 │
│ (all flowing on the same connection) │
────────────────────────────────────
This was a huge performance win. No more head-of-line blocking at the HTTP level.
Step 7: Server Processing — The Other Side
Your request arrives at the server. Now it has to figure out what to send back.
What happens here depends entirely on the architecture:
Static File Server
Request → Find file on disk → Send file
Nginx serving a static HTML file. Fast, simple, no computation.
Server-Side Rendered (SSR) App
Request → Route matching → Database queries → Template rendering → Send HTML
Your Next.js app. The server runs JavaScript, fetches data, renders React components to HTML, and sends the result.
API + Client-Side Rendering (SPA)
Request → Send a mostly-empty HTML shell
(Then the browser loads JavaScript, which calls APIs to fetch data and renders the UI)
A React SPA. The server does almost nothing. The browser does everything.
Behind the Scenes
Even the simplest response involves layers:
Reverse Proxy (Nginx/Caddy)
│
▼
Load Balancer (if multiple servers)
│
▼
Application Server (Node.js, Go, Python)
│
▼
Database (if data is needed)
│
▼
Cache Layer (Redis, if frequently accessed)
│
▼
Response assembled and sent back
For a site like barathraju.com deployed on Vercel, the "server" is actually an edge function running in a data center close to you — so the response time is minimized by physical proximity.
Step 8: The HTTP Response — Here's Your Page
The server responds:
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Content-Encoding: br
Content-Length: 14832
Cache-Control: public, max-age=3600
ETag: "a1b2c3d4"
Set-Cookie: theme=dark; Path=/; Secure; HttpOnly
<!DOCTYPE html>
<html lang="en">
<head>
<title>Barath Raju</title>
...
Key parts:
200 OK— Success. The most famous status code.Content-Type: text/html— This is an HTML document (not JSON, not an image).Content-Encoding: br— The body is compressed with Brotli. The browser will decompress it.Cache-Control— "Cache this for 1 hour."Set-Cookie— "Remember this cookie for next time."
Status Codes — The Server's Emotional Range
200 → "Here you go" (Success)
301 → "It moved permanently" (Redirect)
304 → "Nothing changed, use your cache" (Cache hit)
400 → "Your request made no sense" (Client error)
401 → "Who are you?" (Not authenticated)
403 → "I know who you are. No." (Forbidden)
404 → "That doesn't exist" (Not found)
429 → "Slow down" (Rate limited)
500 → "I broke" (Server error)
502 → "The thing behind me broke" (Bad gateway)
503 → "I'm overwhelmed" (Service unavailable)
Step 9: The Rendering Pipeline — Pixels at Last
The browser now has raw HTML bytes. Turning this into the visual page you see involves one of the most sophisticated pipelines in all of software engineering.
The Critical Rendering Path
HTML bytes
│
▼
Parse HTML → Build DOM (Document Object Model)
│
│ (encounters <link> for CSS)
▼
Parse CSS → Build CSSOM (CSS Object Model)
│
│ (encounters <script>)
▼
Execute JavaScript (may modify DOM and CSSOM)
│
▼
Combine DOM + CSSOM → Render Tree
│ (only visible elements — display:none is excluded)
▼
Layout (calculate position and size of every element)
│
▼
Paint (fill in pixels — colors, text, borders, shadows)
│
▼
Composite (layer management, GPU acceleration)
│
▼
Pixels on screen
Why CSS Blocks Rendering
The browser won't paint anything until it has processed all the CSS. Why? Because without CSS, it doesn't know what anything looks like. It can't paint a heading if it doesn't know the font size, color, or margins.
This is why CSS should be in the <head> and loaded as early as possible. A slow-loading stylesheet delays everything.
Why JavaScript Is (Usually) Render-Blocking
When the browser encounters a <script> tag, it stops everything and executes the script before continuing to parse HTML. The script might use document.write() to inject HTML, so the browser can't assume the rest of the HTML is safe to parse yet.
This is why we use async and defer:
<!-- Blocks rendering (bad for performance) -->
<script src="app.js"></script>
<!-- Downloads in parallel, executes when ready (doesn't block) -->
<script src="app.js" async></script>
<!-- Downloads in parallel, executes after HTML is parsed (best for most scripts) -->
<script src="app.js" defer></script>
Layout Thrashing — A Performance Trap
Here's something that trips up even experienced developers. If your JavaScript reads a layout property and then writes to the DOM repeatedly, the browser is forced to recalculate layout on every read:
// BAD — triggers layout thrashing
for (let i = 0; i < 100; i++) {
const height = element.offsetHeight; // forces layout calculation
element.style.height = height + 1 + 'px'; // invalidates layout
}
// GOOD — batch reads then batch writes
const height = element.offsetHeight; // one read
for (let i = 0; i < 100; i++) {
element.style.height = height + 1 + i + 'px'; // writes only
}
Experiment: Watch It Happen in Real Time
Open Chrome DevTools on any website and look at the Network tab. You'll see every step we discussed:
- Timing breakdown — Click on any request and look at the "Timing" tab:
Queueing ████ 2ms
DNS Lookup ████████ 23ms
Initial Connection (TCP) ████████████ 45ms
TLS Handshake ████████████████ 67ms
Waiting (TTFB) ████████████████████ 112ms
Content Download ████ 8ms
This is the entire journey — right there in your browser.
- DNS resolution: The DNS Lookup row shows you exactly how long name resolution took. If you refresh the page, it'll likely be 0ms (cached).
- Connection reuse: Notice how the first request shows TCP + TLS times, but subsequent requests on the same domain show 0ms for connection. That's HTTP/2 connection reuse.
- Render timeline: Switch to the Performance tab and record a page load. You'll see the exact sequence — Parse HTML, Parse CSS, Layout, Paint, Composite.
Try This in Your Terminal
# See the full DNS resolution chain
dig +trace barathraju.com
# Watch the complete HTTP exchange, including TLS
curl -v --trace-time https://barathraju.com 2>&1 | head -50
# Measure total time broken down by phase
curl -o /dev/null -w "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nFirst byte: %{time_starttransfer}s\nTotal: %{time_total}s\n" -s https://barathraju.com
That last command is my favorite. It gives you a timing breakdown of the entire request lifecycle in your terminal.
When Things Go Wrong
Understanding the happy path is useful. Understanding failure modes is where real engineering depth lives.
DNS Failure
If DNS resolution fails (the domain doesn't exist, or DNS servers are unreachable), you get the dreaded:
ERR_NAME_NOT_RESOLVED
This is why DNS outages are catastrophic. In 2021, Facebook's DNS records were withdrawn during a misconfiguration, and the entire platform (including Instagram, WhatsApp, and internal tools) went dark for 6 hours. Engineers couldn't even badge into their own buildings because the badge system relied on Facebook's internal DNS.
TLS Certificate Expired
If the server's TLS certificate is expired or invalid:
NET::ERR_CERT_DATE_INVALID
The browser refuses to connect. This is by design — a compromised certificate could mean a man-in-the-middle attack. Companies like Let's Encrypt solved the cost and complexity problem, but certificate rotation is still a common operations failure.
Slow TTFB (Time to First Byte)
If the server takes too long to respond:
TTFB > 800ms → Poor user experience
TTFB > 2s → Users start leaving
TTFB > 5s → Users are gone
Common causes: slow database queries, no caching, cold server starts, or the server is physically far from the user (hence CDNs and edge computing).
The Complete Timeline
Here's roughly what happens for a first visit to a typical HTTPS website:
0ms → You hit Enter
5ms → URL parsed, cache checked
25ms → DNS resolution complete
75ms → TCP connection established
175ms → TLS handshake complete
180ms → HTTP request sent
300ms → First byte of response arrives (TTFB)
350ms → HTML parsed, CSS requested
400ms → CSS loaded, render tree built
450ms → First Paint — user sees something
600ms → Largest Contentful Paint — main content visible
1000ms → Page fully interactive
Sub-second. And the entire internet's infrastructure made it happen.
On a repeat visit, caching eliminates DNS, TCP, TLS, and possibly the entire request — bringing the page up in under 100ms.
What I Learned
- The gap between typing a URL and seeing a page is enormous. There are at least 9 major stages, each with their own protocols, failure modes, and optimization strategies.
- Most of the time is spent on ceremony, not content. DNS, TCP, and TLS handshakes can account for 60-70% of the total load time. The actual data transfer is often the fastest part.
- Caching is everywhere and it matters. DNS caching, browser caching, CDN caching, connection reuse — every layer tries to avoid repeating work.
- The browser's rendering pipeline is its own world. DOM, CSSOM, render tree, layout, paint, composite — understanding this is the key to writing performant frontend code.
- Understanding these fundamentals changes how you debug. When a page is slow, I now know exactly where to look: is it DNS? TCP? TLS? Server processing? Rendering? The answer changes the fix completely.
Why This Matters for the Agentic AI Journey
This might seem unrelated to building AI agents, but it's not.
Every agent that interacts with the web — calling APIs, scraping pages, making tool calls — goes through this exact pipeline. When an agent makes an HTTP request to a tool, it's doing DNS resolution, TCP handshakes, TLS negotiation, and parsing responses.
Understanding this is the difference between an engineer who can build agents and an engineer who can debug, optimize, and reason about agents in production.
We're building from the ground up. And this is the ground.
Coming Up Next
Day 2: What Actually Happens When You Call an API
We'll go deeper into HTTP methods, request/response bodies, authentication, rate limiting, and why APIs are the fundamental building blocks of tool calling in agentic systems.
This is Day 1 of an ongoing engineering learning series. I'm a full-stack developer on a journey to deeply understand software engineering from first principles to agentic AI. Follow along as I learn, experiment, and explain.
The Engineering Journey
This article is part of our 50-day engineering sprint to build bedrock software intuition from networking to production agentic AI systems.