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>
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>
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>
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>
Becomes this internally:
Document
└── html
└── body
├── h1 → "Hello"
└── p → "World"
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
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; }
🚀 Pro tip: Always use
transformandopacityfor 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
🔴 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]
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
Step by step:
- Client Hello — Browser says: here are the encryption methods I support, and here's a random number.
- Server Hello — Server picks a method, sends its SSL certificate.
- 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.
- 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.
- 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
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 ✅
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. ✅
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
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=/`
])
⚠️ 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: noneelements are not in the render tree - Use
transformandopacityfor 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.