This is a modern motherfucking website.

And it’s still fucking perfect.

Seriously, what the fuck happened?

The original motherfucking website told you in 2013 that your site was an over‐engineered piece of shit. You nodded, laughed, shared it, and then went right back to work and made it so much fucking worse.

Now you need Node, a package manager, 1,100 dependencies, a bundler, a transpiler, a meta‐framework, a hydration strategy, and a CI pipeline to put a paragraph on a screen. Your “Hello World” has a lockfile longer than this page. Your blog has a loading spinner. A blog. It’s text, you maniac.

And your excuse is always the same: “But we need modern features. SEO. Social cards. Performance. Responsive images.”

Cool. Here’s the thing, shithead: all of that is just HTML. It’s tags. You type them. With your fingers. Into a file.

Semantic elements: accessibility is important, dumbass

You wrote <div class="nav-wrapper-container-inner"> and then installed an accessibility library to explain to screen readers that it’s a nav. There is an element for that. It’s called <nav>. It shipped in Internet Explorer 9.

<header>…</header>
<nav>…</nav>
<main>
  <article>
    <h1>A heading. One of them.</h1>
    <section>…</section>
    <aside>…</aside>
    <time datetime="2026-09-24">today</time>
  </article>
</main>
<footer>…</footer>

That’s landmarks, a document outline, keyboard navigation, and it makes reader mode work. Screen readers understand it. Search engines understand it. No ugly motherfucking JavaScript in sight.

You also don’t need a component library for an accordion. Behold, <details>:

A normal fucking disclosure widget

No npm install, no useEffect. It works with a keyboard, it works with a screen reader, it’s searchable with find‐in‐page, and it’ll still work in twenty years without having to reconstruct an entire build environment.

Same goes for <dialog>, popover, <input type="date">, and native form validation. Hell, you can even turn off CSS and this webpage still looks reasonable (seriously, try it; in Firefox, View → Page Style → No Style). There are three major libraries that already worked out the kinks in all of this, they’re called Gecko, Blink, and WebKit.

JSON-LD: it’s fucking JSON

You installed an “SEO plugin” with its own settings page and a premium tier to generate this:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "This is a modern motherfucking website.",
  "datePublished": "2026-09-24",
  "author": { "@type": "Person", "name": "Robin Reel" }
}
</script>

That’s it. That’s structured data. You tell the search engine what the page is using: the one data format every developer on Earth already knows. Pick a type from schema.org (Article, Product, Recipe, Event, FAQPage), fill in the blanks, paste it in the <head>. It doesn’t execute. It doesn’t screw with rendering. It doesn’t need a fucking SDK.

This page has one. Go look.

Open Graph: it’s seven meta tags

You know that nice card that shows up when someone pastes your link in a chat? You do not need a “social sharing integration” for that. You need this:

<meta property="og:type"  content="article">
<meta property="og:title" content="This is a modern motherfucking website.">
<meta property="og:description" content="Zero of the bullshit.">
<meta property="og:url"   content="https://modernmotherfuckingwebsite.dreamstation.systems/">
<meta property="og:image" content="https://modernmotherfuckingwebsite.dreamstation.systems/www.webp">
<meta property="og:image:alt" content="Robert Cailliau’s W3 logo">
<meta name="twitter:card" content="summary_large_image">

Done. Slack, iMessage, Discord, LinkedIn, Mastodon, Bluesky, and X will all unfurl it beautifully.

And here’s the part that should really sting: because these tags are in the actual HTML, crawlers see them instantly. Your client‐rendered SPA injects them with JavaScript after the crawler has already fucked off, which is why you then had to bolt on server‐side rendering to get back what just fucking writing HTML already gave you.

Resource hints: performance tuning in one line

You hired a consultant and adopted a “performance framework”. Meanwhile the browser has been begging you to just tell it what’s coming:

<!-- Open the connection early to hide the wait on TCP, TLS, and the user’s shitty DNS server -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin>

<!-- Low risk, high reward DNS prefetch for the "maybe" origins -->
<link rel="dns-prefetch" href="https://analytics.example.com">

<!-- Explicitly fetch the bullshit the parser won’t find on its own -->
<link rel="preload" href="/fonts/body.woff2" as="font" type="font/woff2" crossorigin>

<!-- Tell the browser what’s your real content and what’s junk they don’t need to see immediately -->
<img src="hero.avif" fetchpriority="high" width="1200" height="600" alt="…">
<img src="footer-junk.avif" loading="lazy" decoding="async" width="400" height="300" alt="…">

Want instant page navigations like a “real app”? You don’t need a client‐side router. You need speculation rules, which is (say it with me) a script tag with some JSON in it:

<script type="speculationrules">
{ "prerender": [{ "where": { "href_matches": "/*" }, "eagerness": "moderate" }] }
</script>

Browsers that support it prerender the next page on hover. Browsers that don’t support it ignore it. Nobody gets a broken back button. Add @view-transition { navigation: auto; } to your CSS and your plain old multi‐page site cross‐fades between pages like the SPA you spent nine months building.

A word of warning, because you will overdo it: if you preload everything, you’ve prioritized nothing. Hint your actual fucking content and not your print view PDF behind three user clicks.

Modern image formats: the browser picks, you chill

Your hero image is a 4 MB PNG. I know it is. Don’t lie to me.

AVIF and WebP are routinely half the size of a JPEG at the same quality. “But users’ browsers might not support them!” Bullshit, AVIF shipped in Chrome six years ago and WebP did OVER A DECADE AGO. And you don’t need WordPress’s “image optimization service” or a magic framework <Image> component to serve them, because HTML has had format selection built into a tag for years:

<picture>
  <source type="image/avif"
          srcset="hero-800.avif 800w, hero-1600.avif 1600w"
          sizes="(max-width: 40rem) 100vw, 40rem">
  <source type="image/webp"
          srcset="hero-800.webp 800w, hero-1600.webp 1600w"
          sizes="(max-width: 40rem) 100vw, 40rem">
  <img src="hero-800.jpg" width="1600" height="800"
       alt="Something worth the bytes">
</picture>

You don’t need to sniff the user agent for that. The browser takes the first format it understands, at the size that fits the screen. The width and height attributes reserve the space so your layout doesn’t jump around like a caffeinated toddler, which is your Cumulative Layout Shift score fixed with two attributes.

“But I need a build step to make the AVIFs!” No, you need to run one command, one time, when you add the image:

avifenc hero.png hero.avif
cwebp -q 80 hero.png -o hero.webp

That’s not a build system. That’s writing a fucking file that you then serve.

Look how much fucking smaller AVIF is than JPEG

Notice how those lazy‐loaded, so people who don’t want to download three extra shitty images don’t have to until they need to. When brotli compressed, the main content is a lean as fuck 12 KB that you could load in three seconds on dial‐up, and if you don’t open this <details>, you’re spared half a megabyte. Hell, I don’t even make you load the WCAG badge at the bottom if you get bored of this crappy page and don’t scroll all the way down to it.

HTTP/3: stop making your packets wait

We talked about the content, now let’s talk about moving it.

HTTP/2 was a big fucking deal: one connection, lots of requests multiplexed over it at once, no more opening six sockets per webpage like an animal. But it all still rides on the TCP from 1974, and TCP has one rule it will die on: bytes arrive in order. So when a single packet gets lost, every stream on that connection stops and waits for the retransmit, including the CSS, the font, and the HTML that has already arrived and is stuck in the kernel’s buffer. That’s head‐of‐line blocking. HTTP/2 tried to pave over it, but only by shoving it down a layer.

HTTP/3 runs on QUIC, which runs on UDP and handles reliability per stream. Lose a packet carrying your image and your image waits. Your stylesheet doesn’t give a shit. No, you won’t notice on your 10GbE office line, but on Wi‐Fi across the house or spotty mobile data (where your users actually fucking are) it handles loss far better than TCP.

And it’s faster before you’ve even sent your payload:

  • HTTP/1.1 and HTTP/2 pay for a TCP handshake and then a TLS handshake: two round trips before your first byte of data (three on old TLS). QUIC folds TLS 1.3 into its own handshake: one round trip. Been to the server before? It can be zero, with the request riding along in the very first packet. (0‐RTT data can be replayed, so it’s for safe requests like GET. Don’t let anyone mint an API key with it, genius.)
  • A TCP connection is welded to your four‐tuple. Hop from cellular to Wi‐Fi, or get a different IP from DHCP, and every connection dies. QUIC identifies connections by an ID, not your address and port, so it just keeps fucking going.

So what framework do you need for this? None, obviously. It’s server config:

# nginx 1.25 or newer
server {
    listen 443 quic reuseport;   # HTTP/3
    listen 443 ssl;              # fallback for old clients and bad firewalls
    http2 on;

    # Tell browsers HTTP/3 is available
    add_header Alt-Svc 'h3=":443"; ma=86400';
    …
}

On corporate networks that block UDP 443 because of nonsensical threat models, browsers automagically fall back to HTTP/2 over TCP. Same deal as speculation rules: nothing breaks, and the people who can get the good thing get the good thing. Just don’t expect it to save you from yourself: no transport protocol fixes chatty JavaScript, and it sure as hell won’t fix your 4 MB PNG.

You don’t need a build system to write tags and configuration files

Let’s review what you thought required a toolchain:

  • Accessibility landmarks: element names.
  • Structured data: a script tag with JSON in it.
  • Social cards: meta tags.
  • Performance hints: link tags and two attributes.
  • A modern transport protocol: nginx and LiteSpeed support it out of the box, and Caddy turns it on by default.
  • Next‐gen responsive images: a picture tag.
  • Dark mode: one media query. This page has it. Go flip your OS setting, I’ll wait.

Every one of these is a platform feature. It’s in the browser. It was already downloaded, by the user, before they ever heard of you. The framework doesn’t give you these things. At best it gives you a slower way to type them, and at worst it hides them behind a plugin ecosystem so you forget they were free.

This website has no dependencies, no cookie banner, no hydration errors, and a microscopic attack surface. The deploy process is “copy the file to the served directory”.

“Yeah, but…”

“But I have 500 pages and a shared header.”

Fine. That’s a real problem, and server‐side includes or a simple static site generator are proportionate answers. Notice how that is not the same thing as shipping a 2 MB runtime to every visitor and making their $50 phone assemble your header for you. Generate HTML, serve HTML.

“But I’m building an actual application.”

Then use an actual application framework, you beautiful bastard. If you’re building a spreadsheet, a video editor, or a design tool, go nuts. But be honest: you’re building a marketing site, a blog, a docs page, or a restaurant menu. It’s a document. The web was invented for documents. It’s extremely fucking good at them.

Addendum : “But I’m building a shop or forum.”

Okay, I’m worried the previous section let some of you assholes off the hook a little too easily. “Well, my site has user accounts, so it’s basically Google Sheets.” Wrong!

The W3 has a lot of shops, forums, online banking portals, et cetera that aren’t workable as static sites but definitely aren’t fucking Applications. And yes, server‐side rendering (a 2020s‐ism for what we’ve always done with CGI, PHP, etc.) is a valid tool in this case, and is in fact what you should be doing. I’m still fucking insistent that static sites should be static sites, but one of the points I’m trying to get across here is that you shouldn’t be foisting off bullshit work on the client. Do your logic on the server, send HTML to the client, communicate with forms, cookies, and query strings.

Your server knows who’s logged in. It’s sitting next to the database. It has a fast CPU, a warm cache, and is plugged into the wall. Your user’s phone has none of that. When you ship a client‐side app, you’re telling every single visitor to rebuild the same page from scratch on the worst hardware in the whole chain, and paying for it with their battery, their data plan, and their patience. You are being an asshole.

Another word of warning, because you will overdo it again: Don’t take this as a license to round‐trip to the server for everything. Your website is for end users, and the point is to put each job where it works best for the user. For things that are purely presentational and ephemeral, and the platform doesn’t already do, (check first: <details>, <dialog>, popover, and :has() cover a stupid amount of this), JavaScript away. Don’t use it to draw fucking text, but this isn’t a “measured, reasonable use of JavaScript is evil” page.

Also: “server‐side rendering” in framework‐land sometimes means rendering on the server, then making the client render the entire thing again and attach event handlers. Don’t do that. That’s called hydration, and it’s the worst of both worlds: both your server and the user pay.

“But developer experience!”

Your developer experience is waiting 40 seconds for a Docker environment to boot so you can change a word. Mine is pressing save and hitting refresh. Also, nobody visiting your site cares about your developer experience.

“But it looks plain.”

So write some CSS. Modern CSS has nesting, variables, grid, container queries, :has(), clamp(), and color functions built in. You don’t need a preprocessor for those anymore. You don’t need JavaScript for them either. This website’s presentation is pretty fucking austere as a nod to the original motherfucking website, but feel free to go crazy as long as it doesn’t impact accessibility. My point is that every byte should be there because you chose it, not because it fell out of the thousand assholes that wrote everything in your node_modules.

“But Bob only knows React!”
  • renderToStaticMarkup (and similar things in other frameworks) can turn a component into an editable, templateable or statically‐servable plain HTML string just fine.
  • If you work on computers, you have to learn some new shit every month anyway. Learn the foundational shit too.
  • It’s 2026, your AI pro max 1000x subscription agent can write plain HTML just as well as it can React.

This is a website. Look at it.

View source. Seriously, right‐click, do it. Everything I just described is sitting right there in plain text, readable top to bottom by a human being, the way the web was meant to be. No source maps required, because the source is the map.

Start with HTML. Add what you need. Stop when you’re done. That’s the whole fucking methodology.