What happens if you don't use a web framework?



It's worth asking whether the new tools that we use are right for the job, if only to gain an appreciation for the things we gained along the way. Today, I'd like to report on the tradeoffs I've experience building this site in pure JS/HTML/CSS. I am already writing a blog like it's 2004, so this stack fits the theme perfectly.

Most frontend devs would probably presume that this choice is completely fine for a few static pages, but that it would become too tedious to implement if you truly wanted a modern web experience. You were right a couple of years ago, and now I think you'd be surprised, but I'm getting ahead of myself. Before we continue I think it's worth summarizing 30 years of web development in a few paragraphs.

The web was made to view documents that could link to each other. JavaScript (JS) was subsequently created to make web pages interactive. JS was intended to be a simple scripting language used for things like forms and pop-ups. It turns out that interaction is much more valuable than people initially thought, and the role of js grew with the popularity of the web. JS is to this day the only language that can interact directly with the bowser.

JS was also used to fix issues with the web's lack of interaction in it's initial design. Every time you navigate to a new web page, the browser has to do a costly document object model (DOM) reload. It is slow, because the whole page has to be laid out and rendered. People eventually figured out they could use JS to swap only some of the HTML asynchronously and thus avoiding or hiding DOM reloads, a pattern known as AJAX. The exchange of information between the server and the client can now be completely hidden from the user beyond the initial page load.

This led to the development of single page applications (SPA). Assuming that a website will always be contained in a single page allows to develop frameworks around this pattern. Instead of an HTML document with sprinkled JS, we interact with JS applications that manage their own UI. All of the modern web frameworks (React, Vue, Angular, ..) work under this model.

There are plenty of reasons to adopt web frameworks: code reuse, maintainability, less effort to ship a working product. Those arguments, however, matter only to developers, not users. JS frameworks became the de facto way to build web applications largely because SPAs offer a superior user experience.

Making a SPA prototype is surprisingly easy: whenever the user clicks on a new page, fetch the URL and replace the HTML bit by bit.

async function loadPage(url) {
    try {
        const response = await fetch(url);
        if (!response.ok) throw new Error('Page not found');

        const html = await response.text();
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');

        document.title = doc.title;

        const currentHeader = document.querySelector('header');
        const newHeader = doc.querySelector('header');
        if (currentHeader && newHeader) {
            currentHeader.replaceWith(newHeader);
        }

        const currentMain = document.querySelector('main');
        const newMain = doc.querySelector('main');
        if (currentMain && newMain) {
            currentMain.replaceWith(newMain);
        }

        const currentFooter = document.querySelector('footer');
        const newFooter = doc.querySelector('footer');
        if (currentFooter && newFooter) {
            currentFooter.replaceWith(newFooter);
        }
        window.scrollTo(0, 0);
}

Now, you only need to call this function on some specific user events. I am skipping some details here, but you get the idea.

document.body.addEventListener('click', handleLinkClick);

function handleLinkClick(e) {
    const link = e.target.closest('a'); 
    const url = link.href;
    loadPage(url);
}

A similar pattern can be used to replicate JS hydration, which simply means binding JS (which takes time to setup) to initially static HTML, hopefully without the user noticing. This code snippet will not work for every single situation, but that's not what we are aiming for. It’s far easier to code this behavior for a specific case than for all possible applications, which is what frameworks have to do. The router for this site works exactly like this, and takes only about 200 lines.

Still, reproducing what React offers for free seems like unnecessary work, and in some ways it is. It would require a significant amount of resources and expertise for a human to hand-code and maintain this site. However, the future is now, it's 2025 and LLMs can write code, and especially JS/HTML/CSS, at incredible speed and efficiency. The whole website took me under 10 hours to make. I'm convinced that as I get better at vibecoding this time can be reduced to under an hour (content not included). It has a Python backend for the chatbot, custom Markdown to HTML conversion for posts, nice text search thanks to SQLite, and a very, very small bundle size.

Not relying on a JS-heavy framework like React also makes this site easy to crawl, and fast to deploy. I do not think security is an argument against this design decision either. I don't use NPM, which is one of the most common sources of attacks. User inputs are sanitized, the chat is rate limited in multiple ways, and I rely on Firebase for anonymous authentication. If you want to try to break it please go ahead, but you have to tell me how you did it afterwards.

Frameworks remain the better choice for the vast majority of websites. Being able to reuse components, integrate with other libraries, and to maintain productivity as people come and go matters far more than total payload. In my specific case, pure JS works out as I am the sole maintainer of this site, and not a proficient web dev to begin with. Would I have been faster with a framework? Maybe, I'll have to experiment more to be sure, but it was great learning experience regardless. If you also have a self-rolled personal website, please link it to the bot, I'd love to have a look.