Web Dev The Right Way

This is the place where the low barrier of entry is artificially raised by so-called architects who love complexity for the sake of job security and where people with resumes longer than the time they spent in the sun call themselves seniors once they turn 25. And yes, guilty, for the past 15 years I’ve been one of these guys.

One could argue that I am an expert at getting paid to waste time in SCRUM meetings or to rearrange Material components on admin pages. But this also gives me the credentials to tell you that all this frontend complexity is smoke and mirrors, and that all you need to know about web development can be squeezed in a 10 minute video. Let me show you.

Trust me, this is actually really easy. After all, the most difficult problem we managed to solve as a community is to center elements on the screen.

Quick disclaimer. This is not going to be an article where I’m sharing abstract thoughts and random lessons learned from the past 15 years. This is a pragmatic review of all my frontend knowledge distilled in a short article.

How to start Frontend Development

It all starts with a backend service running somewhere on a server. This could be either a monolith meaning a single application that runs your entire logic, or a collection of smaller independent microservices, each tackling parts of your business logic. This setup can be hosted on premise, or in the cloud, but these details don’t really matter right now.

Just know that these backend services can be written in various programming languages, and their main feature is that they listen to incoming requests over the HTTP protocol.

The browser sends HTTP requests to various URLs which contain a verb describing the desired action, a list of headers providing information about the request and the client, and an optional request body containing additional data.

On the server, URLs are mapped to handler functions. If the incoming request is matched by one of the handlers, the input is validated, the business logic is performed, and the data is updated in your storage solution.

Then, a response is sent back to the client which contains a three digit status code indicating the result of the request, a list of headers and the actual response body.

Since the client is a browser, the response body will most likely be HTML describing the layout of a page, JSON when working with REST APIs, JavaScript, CSS or static assets like images or videos.

The browser will parse the HTML line by line in a synchronous manner and the markup will be used to construct an in-memory tree representation of the document structure called the DOM.

This DOM is then used to display all the UI in the browser.

Of course, the HTML can also contain CSS rules and JS script either directly embedded or as links to external files.

<head>
  <link href="styles.css" rel="stylesheet" />
  <script src="index.js"></script>

  <script>
    alert("Welcome to the page!");
  </script>
  <style>
    h1 { font-size: 1em; }
  </style>
</head>

When the browser finds such files, subsequent HTTP requests will be sent to the server to retrieve the data. Caching mechanisms can be used to avoid requesting information that’s already available in the browser from previous interactions.

So at the end of a request-response cycle the user will see the requested page in his browser. At a minimum, he can interact with this page via links to trigger other GET requests and navigate to other pages or via forms to submit information to the server. Every interaction will cause a full page refresh, and this architecture is called a Multi Page Application.

But there is a problem. While convenient and easy to implement, these types of applications have one big limitation - the user experience. This is where all the JavaScript libraries and frameworks come into play, and this is what we’ll focus on for the rest of the article.

This is a good time to mention our today’s sponsor - Scrimba. I know that all the information shared in this article might feel overwhelming, and Scrimba’s new Frontend Career Path created in partnership with Mozilla MDN will help you both master the basics and learn the latest best practices for modern web dev.

Scrimba offers a unique interactive way to learn by doing and have fun while going through their courses. A big chunk of the learning material is free, and you can get full access to the entire courses library with a pro subscription. Check out this link for an extra 20% off your purchase.

The easiest way to improve the UX is to avoid refreshing the whole page on every user interaction. Most of the time just part of the page needs to be updated so there is no need to interrupt the entire user flow. Think of a button that allows you to remove an entry from a movie list. Once the button is clicked, it is much nicer if we simply remove the element from the DOM, instead of doing a full page refresh. We can do this with JavaScript.

JS is what allows us to implement dynamic, interactive features in the browser. It follows closely the EcmaScript specification which is updated on a yearly basis. Note that the browsers have to add support for all the new features defined by the specification but this is usually a fairly slow process. So you can use a transpiler like Babel to write modern JavaScript which is then converted in code that all browsers can understand. Or better yet, you can use TypeScript to support both the newest EcmaScript features and strict types in your codebase.

When a specific action is captured, the JS script can perform an asynchronous call to the server, and when the response is received only the relevant DOM elements will be updated.

Working with the DOM can be somewhat verbose, and libraries like jQuery simplify the dev experience in such scenarios.

However, there is one major issue. We are performing all the async communication and DOM updates in a programmatic manner, and this can turn into a maintenance nightmare especially in large codebases.

$('#btn').on('click', () => {
  $('#message').text("Awesome!");
  $.get('/details', (resp) => {
    $('#details').text(resp.body);
  });
});

One pattern emerges. Once you start prioritizing the user experience, more of the logic initially implemented on the backend will be moved on the client, closer to the end user. This takes off some of the load from your servers, and makes the final app feel snappier. But there is one major caveat regarding initial load times. We’ll get back to this later.

SPAs

So more logic is implemented in the browser and our JavaScript codebase grows at a rapid pace. In this context people started to look for better ways to organize the code, and this is how the Single Page Application was born.

The idea is pretty simple. Since we are moving more of the logic on the browser, why not let the browser build and manage the entire application?

So when the browser requests a page, the server will return an app shell, not the actual HTML representing the desired page layout. Then this app shell loads JS scripts which are in charge of building the UI from scratch directly on the client. Subsequent requests will be made to REST or GraphQL endpoints to retrieve user data or perform various actions in an asynchronous manner. The server communication is done in the background, and all UI updates are performed through JavaScript directly in the browser without refreshing the page.

Quick side note, while most client server communication is performed over HTTP, there are use cases such as messaging or real time apps where a more flexible channel is needed. WebSockets provide a bi-directional communication channel where servers can directly push information to the client. Compare this with the standard HTTP protocol where the client has to explicitly request data from the server.

Back to the Single Page Apps Architecture, this is what made web dev infamous. For the past 10 years a wide range of frameworks were released, all tackling the same basic problem: UI has to be built based on templates and user data, and then it has to be kept in sync when the underlying data changes due to user or server actions. This is the “big problem” frontend frameworks are solving.

Frontend Frameworks review

For simplicity let’s classify the SPA frameworks in two big categories.

  1. First you have your big established solutions like Angular, React and Vue.
  2. Then, you have a second generation of more recent frameworks which focus on efficiency. Think of Svelte, Solid or Qwik.

As you’ll see in a second, when you really think about it, there is little difference between all of these.

It is worth mentioning that you also have solutions like Alpine, Petite Vue or HTMX which combine some SPA concepts with the Multi page architecture. As you’ll see in a second, while SPAs were very popular for a while, we were forced to get back to Multi Page or Hybrid Architectures in recent years.

But first, let’s review the anatomy of a Single Page App.

Just like URLs are mapped to handlers on the server, in an SPA paths are mapped to UI elements on the client. Whenever a path is accessed, the associated layout is built using JavaScript and then rendered in the DOM.

Note that regardless of complexity, the UI is just a tree structure containing components. These are small independent units which can interact with the rest of the app via properties and events, they maintain their own internal reactive state, and they define UI elements in some sort of templating solution which is then translated by the framework in real DOM elements.

If too much of the internal state has to be shared between components, this data can be extracted in a central place called a state manager.

The really interesting thing here is the reactive nature of the component. Whenever its internal state or the associated properties are changed by some sort of event, the related UI will be automatically updated. In the most basic example, when the count value is increased, the new value is automatically updated in the DOM. Compare this to the vanilla JS based programmatic approach where you have to fetch the actual DOM element and update its value programmatically.

Keep in mind that at the end of the day this is what all components do, regardless of the framework you are using. They keep track of user data called state, and they perform automatic UI updates when this data changes.

Yes, React might use a Virtual DOM and dirty checking to detect changes and update the UI, while Solid might use signals to track changes and will update the real DOM directly. However, these are just implementation details which rarely affect the end product you are building. What really matters at the end of the day is your preferred User Experience and the community support around the framework.

Take design for instance. Most modern web apps have quite a lot of UI components in common. Modals, dropdowns, buttons and form controls are your usual reusable building blocks, and component libraries can help you avoid reinventing the wheel here. You can still build your own components and style them with CSS or a preprocessor like SASS, or, if you can have the best of both worlds with a Headless UI solution.

It is also worth mentioning that while frameworks are the go-to solution in frontend development, there are efforts to standardize the creation of custom HTML elements via a suite of different technologies collectively called Web Components. They offer similar features such as reactivity and templating but the developer experience is not really matching the modern expectations. However, if you are forced to work with Web Components, you should give libraries like Lit a try.

Build Tools

Before getting to look at some of the pitfalls of SPAs and how we can solve them, we should take one small detour and discuss build tools.

One of the consequences of moving so much of the logic on the frontend is that projects have larger, more complex codebases. This code has to be structured in files and modules, for development purposes. For a long while JavaScript did not have a proper module system, so bundlers like WebPack or Vite came into the scene.

Tools like Prettier for code formatting and static code analyzers like ES lint can also help with maintaining a clean code base.

SPA Architecture

Ok, let’s get back to the SPA architecture for a second. As you might recall, when the browser requests a page for the first time, it receives back from the server an app shell and the scripts needed to build the UI on the client. This is called Client Side Rendering. While the UI is built, your users will see a blank page on the screen. The larger the app, the longer users have to wait until they can see something meaningful on the page.

This is not the best experience, and, as an alternative we can execute the JavaScript code on the backend first, generate the UI as static HTML and then send it to the browser. This is called Server Side Rendering.

Now, your clients will see a static HTML page with no event handlers attached to it. Then, this HTML has to be hydrated. In other words it has to be associated with the JavaScript code in charge of managing events or handling internal data. When the hydration process is complete your users will be able to interact with the app as expected.

So with Server Side Rendering your customers will not see a blank page while the app is rendered, but when the server is also involved in rendering a new set of complexities are introduced in the dev flow. The hydration process can be optimized, client-side routing can be mixed with server-side file based routing, multiple data fetching strategies can be employed and so on.

Meta-Frameworks

This is where meta-frameworks come into play (Next.js, Astro, SvelteKit). These are advanced tools which build on lower-level frameworks and provide an additional set of features to allow you to build performant web applications. This is why nowadays it is actually recommended to start with a meta-framework directly and build SSR apps which then transform themselves into Single Page Apps once they are loaded on the screen.

Before wrapping things up please note that we focused on JavaScript in this article since this is the status quo for building frontends. However, Web Assembly based solutions were developed in recent years, and these allow you to build your UIs in languages like Rust or C#.

I’m sure I missed quite a lot of tools or ideas you might consider important. If that’s the case I’d love to read your thoughts in the comments.

Please check some of my other videos, and, until next time, thank you for reading.