Markus Oberlehner

The Best of Both Worlds: Progressive Enhanced Hybrid Applications


In the ever-evolving landscape of web development, we have seen a shift from traditional Multi-Page Applications (MPAs) to modern Single Page Applications (SPAs), each with advantages and drawbacks. To deal with those drawbacks, frameworks embracing the principles of, what I call, Progressive Enhanced Hybrid Applications (PEHAs) emerge as a new approach that addresses the individual challenges of MPA and SPA architectures. Combining the best SPAs and MPAs allows us to build applications with a better user experience, improved performance, and solid accessibility (a11y).

In this article, we will discuss the benefits of PEHAs and explore how we can implement them using popular web app frameworks like SvelteKit and Remix (React). So let’s dive into the world of PEHAs and discover how they can revolutionize how we build web applications, creating a more resilient and user-friendly experience.

Disclaimer: Kent C. Dodds calls the same type of applications Progressively Enhanced Single Page Apps (PESPA), but I believe the term “hybrid” is more fitting because the client-side, SPAish logic is only about progressive enhancement, but the server-side, MPAish routing is what matters most.

The evolution of web applications

Over the years, web applications have evolved from traditional Multi-Page Applications (MPAs) to modern Single Page Applications (SPAs). Each approach has strengths and weaknesses, which we will briefly discuss in this section.

Multi Page Applications (MPAs):

Single Page Applications (SPAs):

In summary, MPAs offer progressive enhancement by default and can be very resilient but often suffer from degraded performance because of full-page reloads and long server response times. On the other hand, SPAs provide fast initial responses and an app-like user experience but can be fragile and have waterfall loading issues. As developers, we constantly search for ways to overcome these challenges and create better web applications for our users. Progressive Enhanced Hybrid Applications (PEHAs) aim to combine the best of both MPAs and SPAs while avoiding all of the downsides of those two techniques.

Introducing Progressive Enhanced Hybrid Applications (PEHAs)

Frameworks following the principles of PEHAs combine the best features of both MPAs and SPAs while minimizing their drawbacks. The PEHA architecture aims to make it straightforward to build fast, resilient, and accessible web applications for users. Let’s take a closer look at the benefits of PEHAs and how they address the challenges of MPAs and SPAs.

Progressive Enhanced Hybrid Applications (PEHAs):

By merging the advantages of both MPAs and SPAs, PEHAs offer a more robust solution for building web applications. They provide a fast, resilient, and accessible user experience, ensuring web applications function optimally across different devices and browsers.

Next, we will explore how PEHAs can improve performance, resilience, and accessibility in web applications and discuss some popular frameworks we can use to build PEHAs, such as SvelteKit (Svelte) and Remix (React).

Improved Performance with PEHAs

One of the main benefits of PEHAs is their ability to deliver improved performance compared to traditional MPAs and SPAs. So let’s compare the performance of MPAs, SPAs, and PEHAs and discuss how PEHAs can help developers create faster web applications.

Performance Comparison

Building PEHAs with SvelteKit and Remix

We can leverage two popular web app frameworks, SvelteKit and Remix, to build PEHAs today. These frameworks allow streaming less critical data to the client, ensuring a faster and more responsive user experience.

SvelteKit Example:

// src/routes/+page.server.js
export function load() {
  return {
    profile: profileService.getBy({ userId }),
    streamed: {
      interactions: interactionService.listBy({ userId }),
      posts: postService.listBy({ userId }),
    },
  };
}

This example shows how we can use the load function to fetch data for a specific route. The function returns an object containing two properties: profile and streamed. The crucial aspect is that SvelteKit does not await promises in the nested properties of the streamed object on the server side. Instead, the results of these promises are streamed to the client as soon as they resolve. This approach allows the application to render the primary content (profile) immediately without waiting for the secondary content (interactions and posts). Therefore, users can start interacting with the page’s most important content immediately.

Remix Example:

// app/routes/index.tsx
export const loader = async () => {
  return defer({
    interactions: interactionService.listBy({ userId }),
    posts: postService.listBy({ userId }),
    profile: await profileService.getBy({ userId }),
  });
};

In this Remix example, we use the loader function to fetch data for a specific route. The function returns a defer object with three properties: interactions, posts, and profile. The critical aspect here is that Remix streams promises, not awaited on the server side (interactions and posts), to the client. Using the defer function, again, we ensure that we can render the primary content (profile) immediately while streaming the secondary content (interactions and posts) to the client with the same result noted for the SvelteKit example.

Using nested properties in SvelteKit, load() or defer() in Remix, enables us to create applications that only load the most critical content server-side, then send the initial HTML to the client while streaming secondary data to the client as it comes in. Now that we’ve seen how loading data works in PEHAs let’s continue exploring how SvelteKit and Remix also help us build more resilient applications.

Improved Resilience with PEHAs

Resilience refers to a web application’s ability to function effectively and recover from errors. PEHAs provide better resilience than SPAs by ensuring that even when errors occur, the application continues working and delivering a smooth user experience. In this section, we will examine how PEHAs handle errors better than SPAs and discuss the benefits of using PEHAs for error handling.

Error Handling in SPAs vs. PEHAs

PEHA Error Handling Examples

Let’s consider an example where a user tries to navigate to the checkout page of an e-commerce application:

By handling errors gracefully, PEHAs offer a more resilient and user-friendly experience than SPAs. Users can continue interacting with the application even when errors occur, ensuring a smoother and more enjoyable experience.

Last but not least, let’s explore how PEHAs can help improve the accessibility of web applications, making them more inclusive and user-friendly for everyone.

Improved Accessibility with PEHAs

Accessibility is a crucial aspect of web development, ensuring that web applications are usable by as many people as possible, including those with disabilities or using assistive technologies. PEHAs can help us create more accessible applications by encouraging the use of accessible code by default and following best practices.

Why a11y matters

We must design web applications to be usable by everyone, whether they’re using a mouse, a keyboard, or assistive technologies. By creating accessible applications, developers can reach a broader audience and provide a better user experience for all users.

Accessible and inaccessible code

Let’s consider an example of a checkout form in an e-commerce application:

// app/routes/checkout.tsx 🚫
// ...
<div>
  <label>
    Name
    <input value={form.name} onChange={onUpdateField} />
  </label>
  {/* ... */}
  <div onClick={handleCheckout}>Checkout</div>
</div>
// ...

In this SPA example, we use a div element with an onClick event handler for the checkout button. Unfortunately, this approach is not accessible, as screen readers and other assistive technologies may not recognize the div as a clickable element. Yet nothing prevents us from writing this inaccessible code because using JavaScript hides the fact that this code only works when using a mouse or a touch device to navigate the page.

Now let’s consider the following code that resembles the default way of how to build a form with Remix:

// app/routes/checkout.tsx ✅
export default function Checkout() {
  return (
    <Form method="post">
      <label>
        Name
        <input name="name" />
      </label>
      {/* ... */}
      <button type="submit">Checkout</button>
    </Form>
  );
}

In this Remix example, we must use a proper button element attribute for the checkout button. Otherwise, we can’t submit the form at all. This approach is accessible, as screen readers and other assistive technologies can recognize the button as a clickable element. The default way of how to build forms with Remix guides us the way for how to create accessible web applications.

The future of PEHAs

As frameworks following Progressive Enhanced Hybrid Application principles gain traction, I hope they cause a renaissance of the long-forgotten art of progressive enhancement. More and more web development frameworks and libraries will hopefully adopt PEHA principles, making it easier for developers to create fast, resilient, and accessible applications.

Wrapping it up

PEHAs offer a promising solution to the challenges faced by traditional MPAs and modern SPAs. By combining the best of both worlds, PEHAs enable developers to create web applications that are fast, resilient, and accessible for the benefit of all of our users.

I encourage you to explore the world of PEHAs and consider adopting this approach in your web development projects. Doing so will contribute to a more inclusive, user-friendly, and high-performing web for everyone.