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):
- MPAs have been the standard for web applications since the early days of the Internet.
- They rely on server-side rendering, where each page request triggers a full roundtrip to the server and a complete re-render of the HTML.
- Advantages of MPAs include out of the box progressive enhancement and resilience, meaning they can work well even on older browsers and devices.
- However, MPAs have some drawbacks, such as full-page reloads and longer server response times, leading to a less-than-optimal user experience.
Single Page Applications (SPAs):
- SPAs emerged as a response to the limitations of MPAs, offering a more app-like user experience.
- They load a single HTML file and dynamically update content as users interact with the application, eliminating the need for full-page reloads.
- SPAs can provide faster initial response times and improved user experience compared to MPAs.
- However, SPAs come with challenges, such as waterfall loading (which can be exacerbated due to large JavaScript bundles) and fragile applications that may break on older browsers or devices.
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):
- PEHAs enable only slightly slower initial response times than SPAs while making it possible to show actual content immediately instead of a useless loading spinner, ensuring a quick and smooth loading experience.
- They have properties similar to MPAs regarding progressive enhancement and resilience, ensuring as many people as possible can use them regardless of the capabilities of their devices and browsers.
- By combining these strengths, PEHAs provide an app-like user experience without the fragility and other issues commonly associated with SPAs.
- In addition, PEHAs can improve accessibility by encouraging developers to write accessible code by default, following best practices for web development.
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
- MPAs can take a long time to load because they must fetch all the data from APIs and the database before sending the HTML to the client. This results in longer server response times and a slower user experience.
- SPAs have a speedy initial response time. Still, we can only render an app shell with a loading spinner or skeleton loading animation until the application fetches and processes all necessary data.
- PEHAs strike a balance between MPAs and SPAs by providing a slightly slower initial response time than SPAs while making it possible to show actual content immediately instead of a loading spinner.
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
- SPAs rely on JavaScript, leading to fragile applications that break on older browsers or devices. When an error occurs in an SPA, the entire application may become unusable, leading to a poor user experience.
- PEHAs handle errors more gracefully by relying on server-side rendering and progressive enhancement. This approach ensures that the application can continue to function even in the event of an error, and users can still interact with the primary content.
PEHA Error Handling Examples
Let’s consider an example where a user tries to navigate to the checkout page of an e-commerce application:
- In an SPA, if an error occurs when loading the checkout page, the user might be stuck with a blank screen or an endless loading spinner, rendering the application unusable.
- In a PEHA, if an error occurs when loading the checkout page, the application can still render the primary content and allow the user to interact. The secondary content may not load, but the user can still complete their purchase.
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.