NOW LET US – AI RAG SaaS Studio TP.HCM
NOW LET US
Digital Product Studio
Back to news
DEV-TOOLS...6 min read

Modern Frontend Complexity: essential or accidental?

Share
NOW LET US Article – Modern Frontend Complexity: essential or accidental?

An exploration of how web development evolved from simple static files to complex build pipelines, and whether we can return to a simpler, server-driven architecture.

Modern Frontend Complexity: essential or accidental?

It was simple back then

What are the roots of this Complexity? How have we arrived here?

Once upon a time, at the dawn of the web, browsers and websites were simple. There were no apps really, but mostly static pages - collections of .html files sprinkled with some CSS for better look. These websites were text-based for the most part, linking to other similar documents available on the World Wide Web. Everything was plain and simple; static documents, referring to each other.

Then slowly, step by step, more and more interactivity was added; first came forms and inputs, not long afterwards - JavaScript programming language (both in 1995).

At this stage, Complexity was still low. Web systems developed then consisted mostly of:

  • .html documents and templates
  • .css file or files
  • some .js scripts
  • HTTP servers to make these static files available and handle state altering requests from forms
  • databases to store system's state

Crucially, the UI source code of these first websites and apps was mostly the same as the output files interpreted and executed in the browser - runtime target. Even with the use of PHP and templating languages/systems (like Mustache), it looked very similar to the target HTML files, displayed by the browser:

<h1>{{page.title}}</h1>
<div>
<p>{{name.label}}: {{user.name}}</p>
<p>{{email.label}}: {{user.email}}</p>
<p>{{language.label}}: {{user.language}}</p>
</div>
<a href="/sign-out">{{sign-out}}</a>

A templating engine - just a library available in the server runtime/environment - turns this into a specific HTML page:

<h1>User Account</h1>
<div>
<p>Name: Igor</p>
<p>Email: [email protected]</p>
<p>Language: EN</p>
</div>
<a href="/sign-out">Sign Out</a>

A little more complicated than static collections of .html documents, but still fairly straightforward. What has happened next?

Then came AJAX - weird acronym for Asynchronous JavaScript and XML. It brought a completely new possibility to update HTML document content in the background, asynchronously - without reloading the whole page. From this point onwards, more and more of websites functionality started to be delegated to increasingly complex JavaScript - especially for partial updates, triggered mostly by more sophisticated user interactions, to avoid full page reloads. Not long after that, the concept of Single Page Application (SPA) and first frameworks arrived:

Backbone.js, Knockout.js and AngularJS (2010). In this model, the source code we work on is very remote from what finally lands in the browser environment. More elaborate abstractions came here as well - complexifying needed tooling as a result.

That is how, more or less, we ended up with today's Complexity - where most apps are built with React, Vue, Angular or Svelte, requiring a whole toolchain to build and develop, such as Vite or Webpack. How they work is inherently different from what browsers were designed to do.

Source Code vs Browser Runtime

As the gap between source code format and browser runtime has been growing - because of these newly discovered and adapted abstractions - more tools and of increasing complexity became essential to develop, build and deploy web applications.

Let's take a typical modern SPA - written in React, using TypeScript and Vite for development & building. To make it digestible and understandable by the browser:

  • TypeScript must be transpiled/compiled into JavaScript - it is its superset and browsers do not know anything about it - In our case, we also need to transform TSX to JSX - TSX is just a typed variety of JSX
  • Take JSX files and turn them into JavaScript - browsers have no clue what to do about .jsx files
  • It is an SPA with only one index.html HTML file and potentially tens, hundreds or even thousands of small .js files - for performance reasons, they should be packaged into a single .js bundle (or a few ones). Since at least React as a dependency has to be available, it must be added to the resulting bundle as well
  • With the latter, there are mostly two additional optimizations: tree shaking and minification. Tree shaking takes dependencies and leaves in the resulting bundle only actually used source code, not all of it; minification on the other hand, removes unnecessary whitespaces, shortens variable names and so on to make the final .js file as small as possible - while still keeping its functionality intact

On top of that, there might be additional steps:

  • Post CSS processing - transforming CSS in various ways: adding vendor prefixes, allowing the use of not yet widely supported CSS features or CSS modules/scopes
  • Polyfills and transpilers - as new proposals, specifications and versions of JavaScript (ECMAScript) are developed, it takes time to have them widely supported. Polyfills and transpilers close this gap - they make it possible to use new and not yet widely supported features of JavaScript by transforming our source code to a version that works in older runtimes (browsers) as well

As we can clearly see - that really is a lot! And of course, it would be highly impractical to write scripts performing all of these transformations; that is why we have build tools like Webpack, Turbopack and Vite. They of course introduce yet another dependency; something new to learn and master. But, we have gone so far away from what browsers are actually operating on at runtime, that they rather are necessary. One could make a very good case that it developed in this way purely for historical reasons, because of the browser limitations in the past (there were no native modules for a long time for example).

The current ecosystem complexity is rivaling Tower of Babel. I would then ask:

Can we start from scratch and figure out a much simpler approach, given how browsers have evolved in recent years?

What is essential

For most web apps, what today's users treat as given:

  • instant load times
  • native-like, smooth transitions between pages
  • high degree of interactivity; most user actions should feel fast
  • real-time validation and hints; especially for complex forms and processes

What programmers want:

  • great developer experience - ability to quickly see and validate UI changes
  • possibility of creating, sharing and reusing configurable UI components
  • testability - how do we know whether it works?
  • easy to introduce translations & internationalization

A simpler alternative

Here is an idea:

  • UI mostly server-driven, server-side rendered with the use of HTMX
  • Single Page Application - routing is provided by HTMX, out of the box
  • HTML Web Components - for reusable and framework agnostic components. It is an approach where Web Components mostly provide behavior, not the structure - we will see how it works and what the benefits are below
  • Mustache for server-side rendered HTML templates - implementations are available in pretty much all major programming languages
  • TailwindCSS to make styling easier
  • Simple scripts to bundle it all together and prepare a package for deployment

A fully working example is available in this repo. Let's go through the most important and interesting parts.

Server

In the example, I have written a server in Java, using Spring Boot framework; but, it could have been written in any other programming language and/or framework suited for web development. I call it a server, because in this approach, there is no frontend/backend distinction really; there is just an app, with views rendered mostly by the server, sprinkled with client-side JS here and there.

From various endpoints, rendered HTML pages or fragments are returned as:

@GetMapping("/devices")
String devices(Model model, Locale locale,
@RequestParam(required = false) String search) {
translations.enrich(model, locale, Map.of("devices-page.title", "title"),
"devices-page.title",
"devices-page.search-i
© 2026 Now Let Us. All rights reserved.

Source: Hacker News

Advertisement
Ad slot ready: 5887729102

More in this category

EXPLORE TOPICS

Discover All Categories

Deep dive into the specific technology sectors that matter most to you.