HOW TO CHOOSE THE RIGHT FRONT-END ARCHITECTURE?
- 26 Jun 2023
Choosing the right frontend framework can indeed be challenging, especially in a rapidly evolving landscape.
The purpose of this article is to assist you in selecting the appropriate architecture by providing an overview of the evolution of frontend frameworks.
To fully grasp the current challenges that frontend frameworks address, it is crucial to explore the different frontend architectures that exist and gain an understanding of their respective advantages and drawbacks.
By examining these architectures, you can develop a solid foundation for evaluating and selecting the most suitable framework for your project.
- First, let's delve into various frontend architectures to gain a comprehensive understanding of the evolution of frontend frameworks.
- Next, we'll shift our attention to the rendering techniques employed by Server Side Rendering (SSR) frameworks.
- Finally, we'll explore different hydration techniques used to enhance interactivity in web applications.
Multi-Page Apps (MPA)
The Multi-Page App (MPA) was the initial architecture employed when browser capabilities were limited.
In this architecture, the majority of dynamic processes occur on the backend.
Here's how navigation works within an MPA application:
- The client navigates through the website (e.g., clicking a link), and the browser provides visual feedback (e.g., displaying a spinner in the tab).
- Routing takes place on the backend.
- Data is fetched from the database.
- The backend generates the HTML, which is then sent back to the client.
- The browser renders the HTML.
Here's another example that involves a redirect after form submission (a well-known pattern used to prevent form resubmission when the page is refreshed):
- The client submits a form, resulting in UI feedback.
- Routing occurs, and the backend manages the data.
- The backend instructs the browser to redirect the user.
- The browser automatically redirects the user to the new page, providing UI feedback once again.
- The backend then generates the HTML for the new page.
- Finally, the browser renders the HTML.
What are the advantages of such an architecture?
- It is straightforward to comprehend since all logic resides on the backend, eliminating additional complexity.
- UI feedback is handled by the browser.
And what are the disadvantages?
- It involves full-page refreshes, requiring the reloading of frontend assets every time.
- Some tasks can be challenging to accomplish, such as managing focus or scroll position after form submission.
- Certain tasks are impractical, for instance, experiencing a full-page refresh every time an item is added to the favorites list.
- Some tasks are simply impossible, like animated page transitions.
- Managing state can be tricky, involving the utilization of cookies and other techniques.
- UI feedback is limited and not closely associated with the element the user interacted with.
Due to these limitations, alternative architectures were introduced.
Progressively Enhanced Multi-Page Apps (PEMPA)
Progressive Enhancement is the concept that our web applications should be functional and accessible to all web browsers, and then utilize additional browser capabilities to enhance the user experience (e.g., using JS and AJAX for improved UX).
Here's what happens when the user loads the website for the first time:
- The client loads the website, arriving from a Google search, for example.
- Similar to MPA, the backend generates the HTML and sends it back to the client.
- The client navigates within the website.
- Data is fetched from the backend using AJAX.
- On the backend side, the view component is no longer present, and data is usually sent back to the frontend in JSON format.
- The frontend receives the data and renders the new UI.
So, what is the advantage?
- Significantly improved user experience (that's the primary goal).
And what about the disadvantages?
- More aspects to manage manually (e.g., form resubmission, error handling, spinners, etc.).
- Increased amount of code on both the frontend and backend, which can potentially lead to more bugs.
- Code duplication between the backend and frontend (the same UI needs to be available on both sides due to Progressive Enhancement).
- Reliance on imperative code (with or without jQuery) that can be difficult to follow.
- If a change is made on one side of the network (backend/frontend), the other side needs to be updated as well.
While the UX is enhanced, it comes at a clear cost, which is why new architectures have been introduced.
Single Page Applications (SPA)
To address the issue of code duplication while maintaining an enhanced user experience, the solution is to separate and remove the UI code from the backend. This is the core idea behind Single Page Applications (SPA).
Here's what happens when the user initially loads the website:
- The client loads the website.
- Similar to MPA and PEMPA, the backend generates the HTML and sends it back to the client. However, at this point, the HTML is mostly empty.
- The backend sends the JSON data.
After the initial load, navigation follows a similar pattern as in the PEMPA scenario:
The advantages of SPA are:
- Continued improvement in user experience.
- Enhanced developer experience.
- Elimination of code duplication.
- Declarative code thanks to JSX or similar approaches.
- Utilization of modern tooling.
However, there are some drawbacks:
- SEO issues.
- Larger bundle sizes.
- Longer initial load times and potential waterfall issues (waiting for components to load before fetching data).
- Reduced runtime performance on lower-powered devices.
- Complex state management.
Now, the problem of SEO (and sometimes performance) needs to be addressed, which leads us to the next architecture.
Server-Side Rendered Single Page Applications (SSRSPA)
To address the SEO problem, rendering the application on the server during the initial load is required.
In this case, the application loads similarly to the PEMPA scenario:
This is similar to the rendering that occurs on the backend, where the component tree is transformed into HTML.
In this case, the generated virtual DOM is used to add event listeners to the existing DOM elements.
The navigation can follow either the same pattern as the MPA scenario or the same pattern as the PEMPA/SPA scenario, depending on the rendering technique employed (explained in the next section).
The advantages of this architecture are:
- Improved user experience.
- Enhanced developer experience, including elimination of duplication, declarative code, and improved tooling.
- No more SEO issues (although this may not be considered an advantage by some).
- Potentially improved performance, depending on the rendering technique used.
However, there are some disadvantages:
- State management remains challenging.
The following techniques are all Server Side Rendering techniques (SSR) that involve processing components on the backend to generate HTML in the HTTP response and address SEO issues. They differ from Client Side Rendering (CSR) used by SPA architecture, which renders content on the client side only.
Dynamic Rendering (DR)
In this technique, the page is dynamically generated on the server. It was primarily used to solve the SEO problems of SPA architecture by rendering frontend framework components on the server and sending the generated output to the client.
However, in terms of performance, it is not necessarily superior to Client Side Rendering (CSR) used by SPA architecture (without utilizing server-side caching).
The time taken to render the UI is similar, whether it occurs on the server for DR or on the frontend for CSR.
In fact, it may even be worse due to the hydration phase (explained in the next section).
Dynamic Rendering is available in frameworks such as Next.js (React), Gatsby (React), Nuxt (Vue), and others.
As previously mentioned, compared to CSR (SPA), the advantages of Dynamic Rendering are:
- No SEO issues.
However, there are some disadvantages:
- The page is generated on the backend, requiring Node.js and consuming more server resources than CSR.
- Caching is necessary to improve performance.
- Cache invalidation based on backend changes can be challenging.
- The page needs to be hydrated on the client side for interactivity.
Static Site Generation (SSG)
In the Static Site Generation (SSG) technique, pages are generated ahead of time during the build phase.
The pages are then stored as static HTML files and served as static content.
This technique is also supported in frameworks like Next.js (React), Gatsby (React), Nuxt (Vue), and others.
SSG offers the following advantages:
- No SEO problems.
- Minimal server load.
- Fast page loading.
However, there are some drawbacks:
- Pages are generated during the build phase and remain fully static until the next build.
- The build process can be time-consuming.
- All pages need to be generated during the build.
- The page still requires hydration on the client side to become interactive.
Incremental Static Regeneration (ISR)
Incremental Static Regeneration (ISR) is a rendering technique that combines the benefits of SSR and SSG.
Similar to SSG, pages are generated ahead of time during the build phase.
However, there are two options for how to handle page updates:
1. Time-based revalidation: You can specify a time interval during which the page will be cached.
- Initially, the static page is served for all requests.
- When the specified time expires, the framework generates a new version of the page in the background.
- Subsequent requests will then use the updated page.
2. On-demand revalidation: Your back-office system can inform the framework whenever there are changes, triggering the regeneration of specific pages. This allows for dynamic updates without relying solely on a predetermined time interval.
Next.js (React) supports both time-based and on-demand revalidation, while Nuxt (Vue) currently only supports time-based revalidation.
The advantages of ISR include:
- SEO compatibility.
- Reduced server load compared to SSR.
- Fast page loading.
- Ability to have dynamic content.
However, there are some considerations:
- Time-based revalidation may result in potential cache issues, as older content may be served until the cache expires.
- Implementing revalidation logic between the front-office and back-office is necessary for on-demand revalidation.
- Page hydration is still required for interactivity.
From Jamstack.org :
Jamstack is an architectural approach that decouples the web experience layer from data and business logic, improving flexibility, scalability, performance, and maintainability.
The core principles of Jamstack include:
- Decouple Building and Hosting
- Decouple Frontend and Backend
- Use APIs rather than databases (expose data through APIs, use external tools...etc.)
- Generate pre-baked markup, enhanced with JS
In practice, a Jamstack setup involves the following steps:
- Generating pre-rendered static markup using a Static Site Generator.
- Hosting the static assets on a CDN (Content Delivery Network).
- Integrating with backend APIs for dynamic content.
- Storing the entire project in a version control system like Git.
- Utilizing automated builds to streamline the deployment process.
The advantages of using Jamstack include:
- SEO compatibility, allowing for better search engine visibility.
- Reduced server load due to serving static assets from a CDN.
- Fast page loading times for improved user experience.
- The ability to incorporate dynamic content through backend APIs.
However, there are some considerations:
- Content management and updates should be handled through version control systems like Git.
- For content that live in database, custom logic is required to generate data inside Git (and also maybe to trigger a new build).
- Page hydration is still necessary for interactivity on the client side.
We can summarize the different rendering technique in the following table:
The purpose of hydration is to enable interactivity on the page by attaching event listeners to the server-generated DOM structure.
The different techniques serve the purpose of enhancing performance, aiming to make the page interactive for the user as quickly as possible upon receiving the response from the server.
In fact, if there are no interactions on certain pages, there is no need to perform hydration on them.
This technique is available in the following frameworks: Remix, Sveltekit and SolidStart.
When using classical hydration, the component tree structure is generated on the server to create the HTML.
However, on the client side, the tree structure is generated again so that the library can compare the existing HTML and bind events to the appropriate elements.
The first video of this link shows what happen in the browser during classical hydration.
This is essentially what occurs when using Next.js with the old Pages Router.
Progressive hydration (or Lazy Hydration)
The purpose of progressive hydration is to utilize dynamic components to defer the loading of non-urgent components and prioritize the hydration of urgent components.
This technique requires manual implementation by the developer, who decides which components should be lazy loaded and how they should load.
The second and third videos of this link demonstrate the differences between classical hydration and progressive hydration.
The technical requirements for progressive hydration implementation are as follows:
- Allows usage of SSR for all components.
- Supports splitting of code into individual components or chunks.
- Supports client side hydration of these chunks in a developer defined sequence.
- Does not block user input on chunks that are already hydrated.
- Allows usage of some sort of loading indicator for chunks with deferred hydration.
The idea behind selective hydration is to return an incomplete response from the server.
By streaming the response, we can receive some response from the backend even if some queries have not finished yet.
When we receive the query response, we can send it back to the browser, which will replace the incomplete UI and then hydrate the new page chunk, as shown in the videos in this page .
Partial Hydration (Islands Architecture)
Partial hydration is kind of an updated version of Static Routes.
The purpose of islands is to hydrate at the component level, rather than the entire page.
This technique can be used when only certain components need to be interactive.
Anything outside these islands/components will never change and will not be interactive.
This technique is used by Astro, Marko, Fresh, and 11ty.
And it is also available in Next.js or Gatsby using React Server Components.
This concept is used by Qwik.
The idea behind a resumable application is to serialize the state of the application on the server and then resume from that point on the client.
Even event handlers get serialized, like in this example:
<button on:click="q-671f8656.js#s_D04jAYuCnhM[0 1]">click me</button>
All of these techniques yield different performance results and may have limitations. However, it is not necessary to compare them extensively.
Major frameworks consider these techniques and continuously evolve to offer new performance improvements.
In most cases, if your application is slow, it is likely not due to the hydration technique being used.
Therefore, hydration will probably not be an issue if you follow the framework documentation, but it's still valuable to be aware of these techniques.
Do we need a frontend framework ?
The decision to use a frontend framework depends on the specific requirements of the project.
Here are the pros in favor of using a frontend framework:
- Maintainability and scalability
- Fast and delightful user experience
- Standardized front-end approach
- Quick prototyping and development
On the other hand, there are some cons to consider:
- Complexity for simple projects
- Difficulty integrating with backend tools
- Opinionated nature of frameworks
- Requires training and familiarity
In summary, it is recommended to use a frontend framework when you have a complex frontend with a significant number of user interactions.
Headless or not ?
Headless architecture is not always the solution.
If you only need some interactions on certain pages, you can use frontend frameworks for those specific components without using them for the entire page.
Here are some considerations for different frameworks:
- React can be used to render specific components, but it is not SEO-friendly (only for parts rendered with React).
- Vue.js can be used in Progressive Enhancement mode, as it is based on the HTML served by the server and is SEO-friendly.
- Angular is not well-suited for this scenario.
- Some frameworks, like Alpine.js, are designed specifically for this use case.
SEO or not ?
If SEO is not a requirement, client-side rendering (CSR) frameworks are usually sufficient.
Examples of scenarios where SEO is not critical include:
- Backend offices or admin panels
- Apps that require authentication
- Apps without indexable content (e.g.: Excalidraw )
Choosing the Rendering Technique
- If the content is in the source code → JamStack can be used.
- If you are building a website with infrequent changes → Static Site Generation (SSG) can be used.
- If you can implement a content update logic → Incremental Static Regeneration (ISR) with on-demand revalidation can be used.
- If you want to utilize time-based caching → ISR with time-based revalidation can be used.
- Otherwise, server-side rendering (SSR) is recommended.
To summarize, the decision on using a frontend framework, headless architecture, SEO considerations, and rendering technique should be based on the specific requirements of the project.
The schema below provides a visual representation that can assist in choosing the appropriate architecture with the corresponding rendering technique:
Please note that this schema serves as a helpful guideline, but there may be certain edge cases where it may not fully apply. It's important to consider the unique characteristics and needs of your project when making these decisions.