Removing JavaScript from This Website: The Case for a Quieter Web

June 21, 2026 · 8 min read

A few months ago, this site was exactly what you would expect from a modern developer portfolio: React, Next.js, Tailwind, a JavaScript build pipeline, and enough framework machinery to render a handful of pages, blog posts, images, and a contact form.

It worked. It was not broken. That was the problem.

Modern web stacks are very good at making unnecessary complexity feel normal. You start with a static page, add a framework because deployment is convenient, add a component model because it is familiar, add a bundler because the framework needs it, add hydration because the framework assumes it, and suddenly a mostly-read-only website is shipping a runtime to the browser just to display text.

So I took the site in the opposite direction: no client-side JavaScript. Plain HTML. Plain CSS. A small Rust Worker on Cloudflare for routing, blog rendering, the contact form, robots.txt, RSS, and the sitemap.

This post is about what changed, what got better, and why I think the default for most websites should be: ship as little JavaScript as possible, and justify every byte that remains.


The Starting Point: A Website Wearing an App Costume

There is a difference between an application and a website.

An application needs state, interaction, optimistic updates, client-side transitions, collaboration, offline queues, and sometimes a large JavaScript runtime. A website like this one mostly needs to do four things:

  • show information;
  • link between pages;
  • render articles;
  • let someone send me a message.

That is not an app. That is a document with a form.

My earlier move from Vercel to Cloudflare was already about cutting platform complexity and escaping the convenience tax of default choices. I wrote about that in Why You Should Just Use Cloudflare, and later about the broader architecture in The Full-Stack Edge. But after the hosting migration, one question kept bothering me:

If the site is mostly static, why am I still shipping an application framework to render it?

That question led naturally back to the philosophy I explored in The Philosophy of Minimalist Software: minimalism is not about doing less work. It is about removing everything that does not earn its place.

Client-side JavaScript had stopped earning its place here.


What We Removed

The rewrite removed the parts that existed mainly because the previous stack expected them to exist:

  • React for page composition;
  • Next.js routing and build output;
  • Tailwind as a generated styling pipeline;
  • OpenNext as a compatibility bridge;
  • client-side hydration;
  • browser-side JavaScript for navigation or presentation.

The new site is intentionally boring.

The Worker receives a request, matches a route, renders HTML, and returns it. Blog posts live as Markdown files in content/blog. Build-time Rust code parses front matter, renders Markdown, generates RSS and sitemap data, and embeds the resulting post metadata into the Worker. Static assets live in public/ and are served through Cloudflare Workers Assets.

The browser receives HTML and CSS. That is it.

No hydration step. No route bundle. No framework runtime. No use client. No script tag quietly turning a document back into an application.


What We Kept

This is the important part: removing JavaScript did not mean removing capability.

The site still has:

  • a homepage;
  • a blog index;
  • individual article pages;
  • syntax-highlighted code blocks;
  • RSS;
  • sitemap.xml;
  • robots.txt;
  • responsive styling;
  • dark mode through prefers-color-scheme;
  • a contact form.

The contact form is the only dynamic user-facing feature, and even that does not require client-side JavaScript. HTML forms existed before frontend frameworks, and they still work. The form posts to the Worker, the Worker validates the input, checks a honeypot field, and sends the message through Resend.

That is enough.

A lot of frontend JavaScript exists because we forget what the platform already gives us. Links navigate. Forms submit. CSS handles layout, media queries, hover states, focus states, responsive images, and color schemes. HTML gives us semantics, accessibility, and progressive behavior by default.

When you lean on the browser instead of fighting it, the stack gets smaller very quickly.


The Performance Argument Is Real, but It Is Not the Whole Story

The obvious reason to remove JavaScript is performance.

Less JavaScript means less to download, parse, compile, and execute. That matters on slow networks, old phones, low-power laptops, battery-constrained devices, and every browser tab already fighting twenty other tabs for memory.

But the bigger gain is not only speed. It is predictability.

A static HTML document fails less dramatically than a hydrated application. If CSS loads late, the content is still there. If an image fails, the text remains. If a user disables JavaScript, nothing important breaks. If a crawler, feed reader, text browser, accessibility tool, or constrained device visits the page, it gets the same core document as everyone else.

That is what the web is good at.

We have spent years rebuilding document behavior in JavaScript and then celebrating when we claw back the performance we lost. Server components, partial hydration, islands, resumability, streaming, and edge rendering are all useful tools. But for many sites, the strongest optimization is simpler:

do not send the runtime in the first place.


The Developer Experience Got Better

This surprised me less than it might have a few years ago.

The old stack had excellent DX in the narrow sense: hot reload, components, utility classes, mature conventions. But it also came with a constant background hum of dependencies, adapters, generated output, framework-specific rules, and deployment assumptions.

The new stack is smaller and easier to reason about.

There is no question about whether a component runs on the server or the client. No boundary between server components and client components. No hydration mismatch. No framework plugin translating one platform into another. No JavaScript dependency tree to audit for a site that does not need browser-side behavior.

The mental model is now:

  1. request comes in;
  2. route is matched;
  3. HTML is returned.

That sounds primitive until you remember that most websites are primitive in exactly this way. The complexity was not solving a hard problem. It was decorating a simple one.

This is similar to what I wrote in Ditching OpenCode for Pi: the best tool is often not the one with the most features, but the one whose surface area matches the problem. A portfolio does not need an application platform. It needs to be readable, fast, durable, and easy to change.


Where JavaScript Still Belongs

This is not an anti-JavaScript argument.

JavaScript is the language of the browser. If you are building a spreadsheet, a drawing tool, a map, a video editor, a dashboard with live filters, a local-first collaborative app, or a rich authenticated product, you probably need JavaScript. Sometimes you need a lot of it.

The problem is not JavaScript. The problem is default JavaScript.

We have normalized shipping client-side code before proving that the page needs client-side code. We reach for frameworks before HTML. We install animation libraries before trying CSS. We build client-side routers for sites where normal links would be faster, more robust, and more accessible.

The better rule is boring but useful:

Start with HTML. Add CSS. Add server-side logic when needed. Add JavaScript only when the user experience genuinely requires it.

That order matters.

It forces every interactive feature to justify itself. It makes JavaScript a deliberate enhancement rather than a tax every visitor pays upfront.


A Smaller Web Is a More Respectful Web

Every kilobyte is a choice someone else has to live with.

Your users pay for your JavaScript with time, battery, bandwidth, CPU, memory, and sometimes accessibility. On a high-end laptop over fiber, waste is easy to ignore. On an older phone, a metered connection, or a crowded train, it becomes obvious.

This is why I keep coming back to minimalism. Not as an aesthetic, but as a form of respect.

A website should not require a powerful device to read text. A blog should not need a hydration strategy. A contact form should not depend on a client bundle. A portfolio should not make the browser execute an app just to show a CV.

The modern web gives us extraordinary power. The professional move is not to use all of it by default. The professional move is to choose the smallest tool that does the job well.


Conclusion: JavaScript as an Exception, Not a Reflex

Removing client-side JavaScript from this site made it simpler, faster, more durable, and easier to understand.

More importantly, it made the site honest. It is no longer pretending to be an app. It is a website, served as documents, styled with CSS, enhanced on the server only where necessary.

That will not be the right architecture for every project. But it should be the baseline question for far more of them:

Does this need JavaScript, or have we just forgotten how much the web can do without it?

For this site, the answer was clear. The JavaScript could go. The website got better when it left.

If you are building a marketing page, portfolio, documentation site, blog, landing page, or mostly static product surface, try the same exercise. Remove the script tag in your head before you add one to the page. You might be surprised by how much remains.