Achieve a SPA-like architecture in multi-page applications by combining partials, service workers, and streams.
Updated
Network reliability
Single page application (SPA) es un patrón arquitectónico en el que el browser ejecuta código JavaScript para actualizar la página existente cuando el Username visita una sección diferente del sitio, en lugar de cargar una página nueva completa.
Esto significa que la aplicación Web no realiza una recarga de página real. los History API se utiliza en su lugar para navegar de un lado a otro a través del historial del usuario y manipular el contents de la pila del historial.
The use of this type of architecture can provide a shell application UX that's fast, reliable, and generally consumes less data when browsing.
En las aplicaciones de varias páginas (MPA), cada vez que un usuario navega a una nueva Url, el navegador genera progresivamente HTML específico para esa página. Esto significa una actualización de página completa cada vez que visita una nueva página.
While both are equally valid models to use, you may want to bring some of the benefits of the SPA UX application shell to your existing MPA site. In this article we will discuss how you can achieve a SPA-like architecture in multi-page applications by combining partials, service workers, and streams.
Production case
DEV es una comunidad donde los desarrolladores de software escriben artículos, participan en debates y crean sus perfiles profesionales.
Its architecture is a multi-page application based on traditional backend templates through Ruby on Rails. His team was interested in some of the benefits of an application shell model, but did not want to undertake a major architectural change or move away from its original technology stack.
This is how your solution works:
- First, they create partials of your home page for the header and footer (
shell_top.html
andshell_bottom.html
) y publicarlos como fragmentos de HTML independientes con un punto final. Estos activos se agregan a la cache en el trabajador del servicio.install
event (lo que comúnmente se denomina almacenamiento en caché). - When the service worker intercepts a navigation request, he creates a response transmitted combinando el encabezado y pie de página almacenados en caché con el contenido de la página principal que acaba de llegar desde el server. El cuerpo es la única parte real de la página que requiere obtener datos de la red.
The key element of this solution is the use of streams, which enables incremental creations and updates de fuentes de datos. La API Streams también proporciona una interfaz para leer o escribir fragmentos de datos asincrónicos, de los cuales solo un subconjunto puede estar disponible en la memoria en un momento dado. De esta manera, el encabezado de la página se puede representar tan pronto como se selecciona de la caché, mientras que el resto del contenido se obtiene de la red. Como resultado, la experiencia de navegación es tan rápida que los usuarios no perciben una actualización de página real, solo se actualiza el nuevo contenido (el cuerpo).
The user experience resultante es similar al patrón de experiencia de usuario de shell de aplicaciones de SPA, implementado en un sitio de MPA.
The previous section contains a quick summary of the DEV solution. For a more detailed explanation, see your blog post about this theme.
Implement an application shell UX architecture in MPA with Workbox
In this section, we will cover an overview of the different parts involved in implementing an application shell UX architecture in MPA. For a more detailed post on how to implement this on a real site, see Beyond SPAs: Alternative Architectures for Your PWA.
The server
Partial
The first step is to adopt a partial HTML-based site structure. These are just modular pieces of your pages that can be reused on your site and can also be delivered as separate HTML snippets.
the partial head it can contain all the logic needed to design and render the page header. the partial navigation bar can contain the logic of the navigation bar, the partial footer the code that needs to run there, and so on.
The first time the user visits the site, your server generates a response by assembling the different parts of the page:
app.get(routes.get('index'), async (req, beef) => {
beef.write(headPartial + navbarPartial);
const tag = req.query.tag || DEFAULT_TAG;
const data = await requestData(…);
beef.write(templates.index(tag, data.items));
beef.write(footPartial);
beef.end();
});
Using the beef
(response) of the object writing methodand referencing locally stored partial templates, the answer can be transmitted immediately, without being blocked by any external data source. The browser takes this initial HTML and displays a meaningful interface and loading message right away.
The next part of the page uses API data, which involves a network request. The web app can't process anything else until it receives a response and processes it, but at least the users aren't staring at a blank screen while waiting.
The service worker
The first time a user visits a site, the page header will render faster, without having to wait for the page body. The browser still needs to go to the net to find the rest of the page.
After the first page is loaded, the service worker logs in, allowing him to get the partials for the different static parts of the page (header, navbar, footer, etc.) from the cache.
Static active precaching
The first step is to cache the partial HTML templates, so they are available immediately. With Preloaded work box you can store these files in the install
service worker event and keep them updated when changes are made to the web application.
Depending on the build process, Workbox has different solutions to generate a service worker and indicate the list of files to preload, including web package and drink accessories, a generic nodejs module and a command line interface.
For a partial configuration like the one described above, the resulting service worker file should contain something similar to the following:
workbox.precaching.precacheAndRoute([
{
url: 'partials/about.html',
revision: '518747aad9d7e',
},
{
url: 'partials/foot.html',
revision: '69bf746a9ecc6',
},
]);
Transmission
Then add the service worker logic so that the cached partial HTML can be sent back to the web application immediately. This is a crucial part of being fast and reliable. Using the Streams API within our service worker makes it possible.
Workbox flows abstracts the details of how the transmission works. The package allows you to pass to the library a combination of streaming sources, both cache and runtime data that can come from the network. Workbox takes care of coordinating the individual sources and bringing them together into a single streaming response.
First, configure the strategies in Workbox to handle the different sources that will make up the broadcast response.
const cacheStrategy = workbox.strategies.cacheFirst({
cacheName: workbox.core.cacheNames.precache,
});const apiStrategy = workbox.strategies.staleWhileRevalidate({
cacheName: API_CACHE_NAME,
plugins: [
new workbox.expiration.Plugin({maxEntries: 50}),
],
});
Next, tell Workbox how to use the strategies to build a full stream response, passing a number of sources as functions to run right away:
workbox.streams.strategy([
() => cacheStrategy.makeRequest({request: '/head.html'}),
() => cacheStrategy.makeRequest({request: '/navbar.html'}),
async ({event, url}) => {
const tag = url.searchParams.get('tag') || DEFAULT_TAG;
const listResponse = await apiStrategy.makeRequest(…);
const data = await listResponse.json();
return templates.index(tag, data.items);
},
() => cacheStrategy.makeRequest({request: '/foot.html'}),
]);
- The first two sources are cached partial templates read directly from the service worker cache, so they will always be immediately available.
- The following source function fetches data from the network and processes the response in the HTML expected by the web application.
- Finally, a cached copy of the footer and closing HTML tags are passed to complete the response.
Workbox takes the result from each source and transmits it to the web application, in sequence, only lagging if the next function in the array has not yet completed. As a result, the user immediately sees the page being painted. The experience is so fast that when browsing the header stays in position without the user noticing the update of the entire page. This is very similar to the UX provided by the application shell SPA model.