DEV Community

Munna Thakur
Munna Thakur

Posted on

From Page Source Confusion to Understanding How the Browser Really Works

One moment of curiosity while switching from React to Next.js led me down a rabbit hole that changed how I think about the web entirely.


The Moment That Started It All

I was working on a Next.js project one day. Normal day, normal work. Out of curiosity I right-clicked on the browser and hit View Page Source.

What I saw made no sense.

The page source was filled with hundreds of lines of weird scripts, JSON data, token strings, user info — everything. It looked like someone had dumped a database into the HTML. I honestly thought something was broken.

Then I compared it with an older React app (Create React App). The page source there was almost empty:

<div id="root"></div>
<script src="/static/js/main.0ab98b7f.js"></script>
Enter fullscreen mode Exit fullscreen mode

That was it. Just a div. Nothing else.

That confusion was actually the best thing that happened. It pushed me to understand what is actually going on when a browser loads a page.


React vs Next.js: Why Page Source Looks So Different

Old React (Create React App) — The Empty Shell

When you build a React app using Create React App, the server sends almost nothing. Just a bare HTML shell with an empty div and a JavaScript file reference.

<!-- What the server sends -->
<html>
  <body>
    <div id="root"></div>
    <script src="/static/js/main.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The browser receives this empty shell. Then it downloads main.js, runs it, and React builds the entire page inside that empty div. This whole process happens in the browser, on your computer.

This is called Client Side Rendering (CSR). The server does almost nothing. All the real work happens in the browser.

⚠️ Key thing to understand: View Page Source always shows what the server sent — not what JavaScript built later. That's why React's page source looks empty. The content was built by JavaScript after the page loaded.


Next.js (App Router) — The Full Picture

Next.js works very differently. The server generates the actual HTML before sending it to you. So when you hit View Page Source on a Next.js page, you see the real content plus a lot of extra data.

That extra stuff you see — all those self.__next_f.push() scripts — that is the RSC Streaming Payload. RSC stands for React Server Components.

What it contains:

  • ✅ The actual rendered HTML content
  • ✅ Server-fetched data (articles, topics, lists)
  • ✅ Component props passed from server to client
  • ✅ SEO metadata
  • ⚠️ Sometimes things that shouldn't be there (more on this below)
<!-- What Next.js server sends -->
<h1>Trending Headlines</h1>

<script>
  self.__next_f.push([1, '{"articles": [...], "token": "eyJhbG..."}'])
</script>
Enter fullscreen mode Exit fullscreen mode

This is called Server Side Rendering (SSR). The server does the heavy lifting and sends you a fully built page.


Side by Side Comparison

Feature React (CRA) Next.js (App Router)
What server sends Empty HTML shell Full rendered HTML
Page Source content Just div#root Full content + streamed data
When content appears After JS runs Before JS runs
SEO friendly ❌ No ✅ Yes
First load performance Slower Faster
View Source useful? Not really Yes

View Page Source vs Inspect Element

This trips up a lot of developers. They look different because they are different.

🔍 View Page Source

Shows exactly what the server sent to your browser. A snapshot of the raw HTML before JavaScript touched anything.

  • React apps → you see an empty div
  • Next.js apps → you see pre-rendered HTML plus RSC payload
  • Useful for: checking SEO tags, verifying server rendering, finding security issues

🛠️ Inspect Element (DevTools)

Shows the live DOM — the actual state of the page after JavaScript has run, modified, and updated everything.

  • Always shows the current real UI
  • Updates in real time as JS runs
  • This is what the user actually sees

Simple rule: View Source = what the server sent. Inspect Element = what the browser is showing right now.


How the Browser Renders a Page

Once HTML arrives, a very specific sequence kicks off. Understanding this separates a developer who writes code from one who actually knows why things work.

Step 1 — HTML Parsing → DOM

The browser reads HTML top to bottom and builds a tree structure called the DOM (Document Object Model).

<body>
  <h1>Hello</h1>
  <p>World</p>
</body>
Enter fullscreen mode Exit fullscreen mode

Becomes this internally:

Document
  └── html
        └── body
              ├── h1 → "Hello"
              └── p  → "World"
Enter fullscreen mode Exit fullscreen mode

Step 2 — CSS Parsing → CSSOM

The browser reads all CSS and builds the CSSOM (CSS Object Model) — its internal map of all styling rules.

Step 3 — Render Tree

The browser merges DOM + CSSOM into a Render Tree.

Important rule: elements with display: none are not in the render tree at all. visibility: hidden elements still show up — they just have no color.

Step 4 — Layout (Reflow)

The browser calculates the exact position and size of every element. Width, height, coordinates, margins, padding. This is called Layout or Reflow.

It is expensive. If you trigger it repeatedly in JavaScript (like reading offsetHeight inside a loop), your page gets slow.

// ❌ Bad — triggers layout on every iteration
for (let i = 0; i < 1000; i++) {
  element.style.width = "100px"
  element.offsetHeight // forces layout recalculation
}

// ✅ Better — batch reads and writes separately
const height = element.offsetHeight // read once
element.style.width = "100px"       // then write
Enter fullscreen mode Exit fullscreen mode

Step 5 — Paint

The browser fills in the pixels. Colors, backgrounds, shadows, borders, text.

Step 6 — Compositing (GPU Magic)

Modern browsers split the page into layers and hand them to the GPU. transform and opacity only affect this compositing step.

That is why CSS animations using transform are so much smoother than animations using width or height — they skip Layout and Paint entirely.

/* ❌ Triggers layout + paint on every frame */
.box { transition: width 0.3s; }

/* ✅ Only hits compositing — buttery smooth */
.box { transition: transform 0.3s; }
Enter fullscreen mode Exit fullscreen mode

🚀 Pro tip: Always use transform and opacity for animations. They skip the expensive layout and paint phases.


HTTP vs HTTPS — The Real Difference

Before I understood this properly, I thought HTTP and HTTPS were basically the same thing with a padlock icon. That is completely wrong.

HTTP — Plain Text on the Internet

HTTP (HyperText Transfer Protocol) is the language browsers and servers use to communicate. The problem is everything travels in plain text. Zero encryption.

Imagine you're at a cafe on public WiFi. You go to an HTTP website and type your password. Anyone on that same network running a packet sniffer can literally read your password as it travels through the air:

POST /login HTTP/1.1
Host: somesite.com

username=john&password=secret123
Enter fullscreen mode Exit fullscreen mode

🔴 That password is visible to anyone intercepting your traffic. This is not a theoretical risk — it is trivially easy to do on public networks.

HTTPS — Encrypted Communication

HTTPS adds a security layer called TLS (Transport Layer Security) on top of HTTP. That same password POST, when encrypted with TLS, looks like this to anyone intercepting it:

xK93!#@mBqP2▓△◇☆■♠... [scrambled nonsense]
Enter fullscreen mode Exit fullscreen mode

Only the browser and the server hold the keys to decrypt it.


What TLS Actually Does (In Plain Terms)

TLS solves three real-world problems at once:

🔐 1. Encryption

Nobody can read your data in transit. Not your ISP, not hackers on the same WiFi, not your router. The data is scrambled before it leaves your device.

🏷️ 2. Authentication

You can verify you're actually talking to the real website and not a fake one designed to steal your data. This is what the padlock icon confirms.

🔏 3. Integrity

The data that arrives is exactly what was sent. Nobody modified it in the middle. Without this, a middleman could quietly change the content of pages you visit.


The TLS Handshake — Step by Step

Every HTTPS connection starts with a TLS handshake. This happens automatically, in milliseconds, before you see any page content.

Browser                          Server
   |                               |
   |------- ClientHello ---------->|  "Here's what encryption I support"
   |                               |
   |<------ ServerHello -----------|  "Let's use this method"
   |<------ Certificate -----------|  "Here's proof of who I am"
   |<------ Public Key ------------|
   |                               |
   |------- Key Exchange -------->|  Browser generates secret, encrypts it
   |                               |  Both sides now have the same key
   |                               |
   |====== Encrypted Channel ======|
   |                               |
   |------- GET /page ----------->|  Actual request, now encrypted
   |<------ 200 OK + HTML --------|  Actual response, now encrypted
Enter fullscreen mode Exit fullscreen mode

Step by step:

  1. Client Hello — Browser says: here are the encryption methods I support, and here's a random number.
  2. Server Hello — Server picks a method, sends its SSL certificate.
  3. Certificate Check — Browser checks: is this cert from a trusted authority? Is the domain right? Not expired? If anything is wrong, you see the "Not Secure" warning.
  4. Key Exchange — Browser encrypts a secret value using the server's public key and sends it. Both sides derive the same encryption key from this. The actual key never travels over the network — that's the clever part.
  5. Secure Channel — Everything from here on is encrypted.

🔒 For users: When you see the padlock, it means (1) you're on the real site, (2) your data is encrypted in transit, (3) nobody can tamper with what you receive.


The Full Journey: URL to Page

Let us walk through everything that happens when you type a URL and press Enter.

Phase 1 — URL Parsing

https://example.com/headlines?id=10
  │         │           │        │
  │         │           │        └── Query params
  │         │           └────────── Path (which page)
  │         └────────────────────── Domain name
  └──────────────────────────────── Protocol → port 443
Enter fullscreen mode Exit fullscreen mode

Phase 2 — DNS Lookup

The browser needs an IP address, not a domain name. Computers talk in numbers.

example.com
    │
    ├── Browser cache? → yes → use it (skip the rest)
    ├── OS cache?
    ├── Router cache?
    ├── ISP DNS server?
    ├── Root DNS server → "where is .com?"
    ├── .com TLD server → "where is example.com?"
    └── Authoritative DNS → IP: 93.184.216.34  ✅
Enter fullscreen mode Exit fullscreen mode

Phase 3 — TCP 3-Way Handshake

Browser → Server:  SYN      (I want to connect)
Server  → Browser: SYN+ACK  (OK, I'm ready)
Browser → Server:  ACK      (Great, let's go)

Connection established. ✅
Enter fullscreen mode Exit fullscreen mode

Phase 4 — TLS Handshake (if HTTPS)

As described above. Adds 1-2 round trips.

Phase 5 — HTTP Request

GET /headlines HTTP/1.1
Host: example.com
User-Agent: Chrome/145.0.0.0
Accept: text/html
Cookie: session=abc123
Enter fullscreen mode Exit fullscreen mode

Phase 6 — Server Processing

The server reads the route, runs logic, fetches data, generates HTML, and streams it back.

Phase 7 — Browser Rendering

HTML arrives → DOM → CSSOM → Render Tree → Layout → Paint → Composite → you see the page.


The Security Issue I Found

When I looked at my page source, I noticed sensitive data sitting right there in the HTML. Here is what I found and why it matters.

What Was Exposed

  • A JWT authentication token (the thing that keeps users logged in)
  • A Google OAuth token
  • Full user details including email and profile info

Why Is This a Problem?

The JWT token is essentially a digital key to that user's account. If someone grabs it from the page source, they can make API requests pretending to be that user. Tokens have expiry times, but that window can be wide open.

The Right Approach

Sensitive tokens should live in HTTP-only cookies. HTTP-only cookies cannot be accessed by JavaScript at all and are never exposed in page source. The server reads them automatically on each request.

// ❌ Wrong — token passed as prop, now it's in page source
<ClientComponent token={jwt_token_here} />

// ✅ Right — use HTTP-only cookie, set on the server
res.setHeader('Set-Cookie', [
  `auth_token=${token}; HttpOnly; Secure; SameSite=Strict; Path=/`
])
Enter fullscreen mode Exit fullscreen mode

⚠️ Lesson: Just because Next.js makes it easy to pass data from server to client doesn't mean all data should flow that way. Sensitive auth tokens belong in HTTP-only cookies, not in the RSC payload.


What This Means for Users (The Non-Dev Perspective)

Most users have no idea this is happening. But as developers, understanding this helps us build things that actually protect them.

What the user sees What's actually happening
Typing a URL and pressing Enter DNS lookup + TCP + TLS + HTTP request, all in under 200ms
The padlock icon TLS certificate verified, encrypted channel open
Page appearing immediately SSR pre-built the HTML, browser just painted it
Page loading slowly, then content appearing CSR — browser downloaded JS, ran it, then fetched data
"Not Secure" warning No TLS, data is unencrypted
Logging in safely Password encrypted by TLS before leaving your device
Laggy scrolling JavaScript causing layout reflow — expensive recalculations

Key Takeaways

React vs Next.js HTML

  • React (CRA) sends an empty shell. JS builds everything in the browser.
  • Next.js sends real pre-rendered HTML. The server did the work first.
  • View Source = what the server sent. Inspect Element = what the user sees.

Browser Rendering

  • HTML → DOM → CSS → CSSOM → Render Tree → Layout → Paint → Composite
  • display: none elements are not in the render tree
  • Use transform and opacity for animations — they skip Layout and Paint
  • Avoid reading layout properties inside loops (layout thrashing)

HTTP vs HTTPS and TLS

  • HTTP = plain text. Anyone can read it.
  • HTTPS = HTTP encrypted with TLS.
  • TLS provides: encryption (unreadable), authentication (real site), integrity (untampered)
  • The padlock means all three are active

Security

  • Never expose auth tokens in RSC payload or as client component props
  • Use HTTP-only cookies for sensitive session data
  • Regularly check your page source — you might be surprised what's in there

That one moment of confusion looking at a Next.js page source taught me more about how the web actually works than months of just writing components. Sometimes the best learning comes from pausing and asking — why does this look like this?


Tags: javascript nextjs react webdev security

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.