Master HTML: The Ultimate Guide for Beginners

The Bloat is a Choice: A Return to Semantic Sanity

Next.js is a cancer. There, I said it. It is a bloated, over-engineered, glorified wrapper for people who are too terrified to look at a raw html file. We have spent thirty years refining the most resilient, accessible, and lightweight document format in human history, only for a generation of “full-stack” developers to decide that what the world really needs is 2.5 megabytes of JavaScript to render a single paragraph of text.

The modern web is a graveyard of intent. We used to build documents; now we build “experiences” that take six seconds to hydrate on a mobile device. We’ve traded the elegance of the Document Object Model (DOM) for a Virtual DOM that eats RAM like a starving dog. You aren’t “optimizing” anything with your server-side rendering hacks; you are just trying to fix the problems you created by using a framework that shouldn’t exist in the first place. Every time you npm install a new dependency to handle a hover state, a piece of the original web dies.

H2: Next.js is a High-Level Language for People Who Hate Computers

The industry has been sold a lie. The lie is that html is “just a markup language” and therefore beneath the dignity of a “real engineer.” So, we hide it. We bury it under layers of JSX, TSX, and CSS-in-JS. We treat the browser like a dumb terminal for a remote execution engine.

When you use a framework like Next.js or React, you aren’t writing for the web. You are writing for a transpiler. You are creating a brittle abstraction that breaks the moment the user’s connection flinches. I remember when we wrote code that worked in Netscape 2.0 and IE3. It didn’t need a “build step.” It didn’t need a “hydration phase.” It just worked because the browser knew how to parse html. Today, if your JavaScript bundle fails to load, your site is a white screen. That isn’t progress; it’s professional malpractice.

H2: The Curl Audit: 4 Megabytes of Nothing

Let’s look at the reality of a “modern” tech blog. I won’t name names, but this is a popular site built with a “bleeding edge” stack. Let’s see what happens when we ask for the headers and the initial payload.

$ curl -v -I https://[REDACTED-MODERN-TECH-BLOG].com
> GET / HTTP/2
> Host: [REDACTED]
> User-Agent: curl/7.68.0
> Accept: */*
> 
< HTTP/2 200 
< content-type: text/html; charset=utf-8
< x-powered-by: Next.js
< cache-control: s-maxage=31536000, stale-while-revalidate
< content-encoding: gzip
< vary: Accept-Encoding
< x-nextjs-cache: HIT
< date: Tue, 22 May 2024 14:20:01 GMT
< server: Vercel
< transfer-encoding: chunked

Look at that x-powered-by: Next.js header. It’s a badge of shame. But let’s look at the actual payload size. When I run a full curl and pipe it to a file:

$ curl -s https://[REDACTED-MODERN-TECH-BLOG].com | wc -c
2,458,922

2.4 megabytes. For a page that contains approximately 1,200 words of text. That is a ratio of roughly 2,000 bytes of overhead for every single byte of actual content. Where is that space going? It’s going to a massive JSON object at the bottom of the page—the __NEXT_DATA__ script—which contains the entire state of the application, duplicated, because the developers don’t trust the html they just sent to the client. It’s going to inline CSS that redefines the meaning of “padding” forty-five times. It’s a failure of engineering.

H2: The Archeology of the Tag: From Wilbur to the Living Standard

We have forgotten the lineage of our craft. To understand why modern html is failing, you have to understand where it came from.

HTML 3.2 (Wilbur) – 1997

This was the era of the “Browser Wars.” RFC 1866 had given us the basics, but HTML 3.2 was where things got wild. We had <table> for layout, because CSS was a fever dream. We had <font> tags. We had <u>. It was messy, but it was honest. You knew exactly what the browser was going to do. The Trident engine (IE) and the Gecko engine (Netscape/Firefox) were fighting for dominance, but the core of the document was still a document.

HTML 4.01 – 1999

The “Strict” era. This was the peak of semantic intent before the industry lost its mind. HTML 4.01 tried to separate structure from presentation. It gave us the id and class attributes as we know them. It tried to kill the <center> tag (rightfully so). It was the version that powered the web for a decade. If you couldn’t build a site in HTML 4.01 Strict, you weren’t a developer; you were a hobbyist.

HTML5 (The Living Standard)

Then came the WHATWG and the “Living Standard.” HTML5 gave us <article>, <section>, <nav>, and <header>. These weren’t just “new divs.” They were a gift to accessibility and SEO. They told the browser—and the screen reader—what the content meant.

And what did the modern developer do with this gift? They ignored it. They went back to using <div> for everything, but this time they wrapped it in a React component called <StyledContainer> that compiles down to a <div> with a randomized class name like css-18z7x3. We have regressed past 1997. At least in 1997, I could read the source code.

H2: Your Div Soup is a Crime Against Logic

Let’s perform a post-mortem on a typical “modern” navigation component. I see this every day in code reviews. It makes my teeth ache.

The Modern Horror:

<div class="nav-wrapper">
  <div class="nav-container">
    <div class="nav-item-link" onclick="window.location.href='/home'">
      <span class="nav-text">Home</span>
    </div>
    <div class="nav-item-link" onclick="window.location.href='/about'">
      <span class="nav-text">About</span>
    </div>
  </div>
</div>

This is an abomination. You’ve used a div with an onclick handler. You’ve broken the middle-click (open in new tab). You’ve broken the “copy link address” function. You’ve made it invisible to screen readers unless you manually add role="button" and tabindex="0", which you won’t do because you’re “moving fast and breaking things.”

The Semantic Sanity (The HTML Way):

<nav>
  <ul>
    <li><a href="/home">Home</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>

The second example is smaller. It’s faster. It’s natively accessible. It works without JavaScript. It uses the html tags for their intended purpose. But “engineers” today think this is too simple. They think they need a “Link Component” that tracks analytics, pre-fetches the JSON for the next page, and triggers a transition animation that nobody asked for.

H2: The Byte-Size Difference: A Technical Reality Check

Let’s talk about the “Cost of Convenience.” Developers claim that using div elements for everything is faster for development. It isn’t. It’s just lazier. And the user pays the price in bytes and battery life.

Element Type Code Bytes (Minified) Accessibility Keyboard Support
Semantic <button>Save</button> 21 Native Native
Div Soup <div class="btn" onclick="save()">Save</div> 46 None None
React/Tailwind <div class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 focus:outline-none focus:ring-2" role="button" tabindex="0">Save</div> 148 Manual Manual

Look at that table. The React/Tailwind version is 7 times larger than the semantic version. And that doesn’t even include the 50KB of CSS required to make those utility classes work, or the 100KB of JavaScript required to make the div behave like a button. You are literally charging your users for your inability to learn the html specification.

H2: The Ghost of Tim Berners-Lee is Weeping Over Your Bundle Size

The original vision of the web was a decentralized web of linked documents. It was built on the principle of “Graceful Degradation.” If a browser didn’t support a feature, it would skip it and show the content anyway.

Modern web development has replaced this with “Total Failure.” If your main.js bundle gets a 404 because of a CDN hiccup, your user gets nothing. You have built a single point of failure into a system that was designed to be indestructible.

I’ve seen “landing pages” that require 12MB of assets to display a headline and an email signup form. I’ve seen memory heaps in Chrome climb to 1GB just to display a dashboard with three charts. This is because you aren’t using html; you’re using the DOM as a canvas for a bloated runtime. You are treating the user’s CPU like a free resource for your garbage collection.

H2: The Deprecated Graveyard: A Eulogy for the <font> Tag

I almost miss the <font> tag. At least back then, we knew we were being naughty. We knew that putting color="#FF0000" inside a tag was a hack. Today, we do the same thing with Tailwind classes—<p class="text-red-500">—and we call it “Utility-First CSS.” It’s the same damn thing! We’ve just renamed it and added a build step.

But let’s talk about the tags that actually died. Does anyone remember <dir>? It was for directory lists. It was deprecated in HTML 4.01 because <ul> did the same thing. What about <applet>? It was the precursor to the modern “SPA” (Single Page Application) nightmare. We realized that embedding a heavy, proprietary binary (Java) into a document was a bad idea. Yet, here we are in 2024, embedding a heavy, proprietary-feeling JavaScript framework into every page. We didn’t learn a thing.

And then there’s <center>. It was simple. It was effective. We killed it in the name of “separation of concerns.” But now, we have developers who can’t center a div without looking up a Flexbox cheat sheet or importing a “Layout System.” We traded simplicity for a theoretical purity that we immediately abandoned the moment we started writing CSS inside our JavaScript files.

H2: The Obscure Global Attributes You’re Ignoring

If you actually took the time to read the html Living Standard, you’d find that the browser can do almost everything you’re currently doing with JavaScript.

Take contenteditable. You want to build a rich text editor? You don’t need a 300KB library. You need a container with the contenteditable attribute. The browser engine—whether it’s Blink, WebKit, or Gecko—already has a full-featured editing engine built-in.

What about accesskey? It allows you to define keyboard shortcuts natively. But no, you’d rather import react-hotkeys and add 15KB to your bundle.

What about the details and summary tags? They provide a native accordion widget. No JavaScript required. It’s accessible by default. It works with the keyboard. But I still see developers building “Accordion Components” with useState(false) and a bunch of complex logic to toggle a CSS class. Why? Because they don’t know the platform. They are “React Developers,” not “Web Developers.”

H2: The Accessibility Tax: Why Your ‘Custom Component’ is Broken

When you use a div to build a checkbox, you are a thief. You are stealing the user’s ability to use the web. A real <input type="checkbox"> comes with a decade of accessibility refinement. It works with screen readers. It works with high-contrast modes. It works with voice control software.

When you build a “Custom Checkbox” using a div and some CSS, you have to manually re-implement every single one of those features using ARIA attributes. And you will fail. You will forget aria-checked. You will forget to handle the Space key. You will forget that some users don’t use a mouse.

The “Accessibility Tax” is the extra work you have to do because you refused to use the correct html tag. And the tragedy is that most developers just don’t pay the tax. They ship broken, inaccessible garbage and call it a “premium UI.”

H2: The Memory Heap and the Death of the Document

Let’s talk about the hardware. When I started, we were targeting machines with 8MB of RAM. If your code was inefficient, the computer crashed. Today, we assume the user has a 16-core M3 Max with 64GB of memory.

But the web isn’t just for people in San Francisco. It’s for someone on a five-year-old Android phone in a rural area with a spotty 3G connection. When you ship a 5MB html payload (after hydration), you are excluding those people. You are saying that their time and their data plans don’t matter.

Every node you add to the DOM has a memory cost. Every event listener you attach has a cost. When you use a framework that re-renders the entire tree because a user typed one character into a text box, you are burning CPU cycles for no reason. The browser’s parser is highly optimized. It can stream html and render it as it arrives. But your JavaScript-heavy site can’t do that. It has to wait for the whole bundle to download, then it has to parse the JS, then it has to execute the JS, then it has to fetch the data, and then it can finally show the user the text they came for.

H2: A Call for Semantic Sanity

It is time to stop the madness. It is time to return to a “Markup-First” mentality.

  1. Start with the Document: Write your content in pure, semantic html. Use <header>, <main>, <footer>, <article>, and <section>. If it doesn’t look right, use CSS. If it doesn’t have the right behavior, use a tiny bit of JavaScript.
  2. Kill the Frameworks: You don’t need React for a marketing site. You don’t need Next.js for a blog. You need a static site generator at most, and even then, you should be questioning every byte of JavaScript that makes it into the final build.
  3. Respect the User: Every byte you send is a liability. Every script you include is a security risk and a performance bottleneck.
  4. Learn the Spec: Stop reading “Top 10 React Hooks” articles and start reading the MDN documentation for html elements. Learn what dl, dt, and dd are for. Learn how to use <figure> and <figcaption>.

The web was designed to be a resilient, open, and accessible platform. We have turned it into a bloated, fragile mess of competing abstractions. The “Bloat” isn’t an accident; it’s a choice. It’s a choice made by developers who value their own “developer experience” over the actual experience of the people using their software.

I’ve been building for this platform since 1994. I’ve seen technologies come and go. I’ve seen Flash rise and fall. I’ve seen the rise of jQuery and the death of Silverlight. And I am telling you: the current path is unsustainable. We are building on sand.

Go back to the basics. Open a text editor. Write an index.html file. No build step. No node_modules. Just tags and content. Feel the speed. Observe the simplicity. That is the web we were supposed to have. That is the web we need to fight for.

The ghost of Tim Berners-Lee isn’t just weeping; he’s probably wondering why he bothered in the first place. Stop building “apps” and start building documents again. Your users—and your memory heap—will thank you.

H2: The Final Insult: The Head Element

I can’t finish this without mentioning the <head> element. In a sane world, the head contains a title, some metadata, and a link to a stylesheet. In the modern world, the head is a 500-line dumping ground for tracking scripts, tag managers, polyfills for browsers that died a decade ago, and inline CSS that looks like a cat walked across the keyboard.

I’ve seen <head> sections that are larger than the entire <body>. We are loading Facebook’s tracking pixel, Google’s analytics, Hotjar’s heatmaps, and four different “cookie consent” scripts before we even show the user a single pixel of content. This is the ultimate betrayal of the html philosophy. We have turned the document into a surveillance device that happens to display text as a side effect.

If your <head> is more than 20 lines long, you’ve failed. If you have more than two <script> tags, you’re being lazy. If you’re using a “Tag Manager” to inject more bloat without telling the developers, you’re part of the problem.

The web is a document. Treat it like one. Use html as it was intended. Anything else is just expensive noise. Now, if you’ll excuse me, I have some hand-coded tables to optimize for a 14.4k modem. At least back then, we knew how to fit a whole world into 64 kilobytes. You can’t even fit a favicon into that today. Pathetic.

Leave a Comment