PRPL is a pattern used to build scalable, fast modern web apps with great user experience.
PRPL is an acronym for:
PRPL architecture was conceived by the Google Chrome team seeking to make the web faster.
This PRPL is an individual optimization trick developed over the years that aims to facilitate a faster web experience. This came with the advent of Service Workers, Background sync, Cache API, Priority hints, and pre-fetching.
Specifically, PRPL works for phones with a low network when the phone is offline or in data-saver mode.
In this post, we will look at each unit in the PRPL.
This tells the browser to fetch a resource beforehand and store it in the browser. So when we need a resource, the resource is retrieved from the browser quickly without fetching the resource over the network. This is done with the use of the rel="preload"
.
Preload is a declarative fetch request that tells the browser to request a resource as soon as possible. Preload critical resources by adding a
<link>
tag withrel="preload"
to the head of your HTML document – Houssein Djirdeh web.dev.
To preload a resource, we use the link tag and add the rel='preload'
attribute to it.
<link rel="preload" as="style" href="style.css">
This will preload the resource style.css
as a Stylesheet. The as='style'
tells the browser that the resource to pre-load is a stylesheet and should be loaded as such.
Resources which can be preloaded include:
<link rel="preload" href="/your/webpage/link">
<link rel="preload" href="/your/js/file/link">
<link rel="preload" href="/your/css/file/link">
<link rel="preload" href="/your/audio/file/link">
<link rel="preload" href="/your/audio/file/link">
<link rel="preload" href="/your/video/file/link">
<link rel="preload" href="/your/image/file/link">
<link rel="preload" href="/your/document/file/link">
<link rel="preload" href="/your/webfont/file/link">
Pre-loading and pre-fetching are basically the same.
Pre-fetching involves a process whereby the browser fetches the resources of a <link>
tag and stores it in its local cache. When the user eventually requests the page via the <link>
tag, the browser serves the user the cached page. This speeds up both the loading and rendering of the webpage.
To pre-fetch a resource, we use the rel="prefetch"
attribute.
<link rel="prefetch">
This will pre-fetch whatever resource is there, just like pre-loading.
This is a rule that states that the initial route of a web app should be rendered as quickly as possible, and that the initial route should not be lazy-loaded.
We call it the Initial Contentful Paint. No matter what the app does, it must produce a First Paint on the browser quickly. In order to optimize for a First Contentful Paint, we must eliminate render-blocking resources, optimize CSS delivery, and use SSR.
This is mostly applicable to JS frameworks because they render their payload in the browser/client-side, so if the app has a heavy payload, you would see it will have to load the JS/CSS assets before rendering its content.
So SSR on your web app will help render the web app in the server and produce the first paint before the rest of the JS/CSS arrives.
For example, in React, we can build a server-side rendered app using the ReactDOMServer
.
Let’s see an example:
import React from "react" import ReactDOM from "react-dom" import App from "App" ReactDOM.hydrate(<App />, window.root)
We are using ReactDOM.hydrate
instead of render because we want the React DOM renderer to know that we are rehydrating the app after a server-side render. This means that the React DOM renderer would expect a render from a server. It would display this first. Then, the app component would then be by React from the browser.
Next, our Express server would be this:
import fs from "fs" import React from "react" import App from "./App" import ReactDOMServer from "react-dom/server" ... app.get("/", (req, res, next) => { // This would render the <App /> and return it as string. const app = ReactDOMServer.renderToString(<App />) fs.readFile("./build/index.html", (err, data) => { // We read the index.html file, replace the `div#root` with the rendered App component and send it to the browser. return res.send(data.replace("<div id='root'></div>", "<div id='root'>" + app + "</div>")) }) }) ...
With the above code, our React app is now SSR-enabled. It would render the First Paint before the whole load would be rendered.
CSS delivery involves inlining the important CSS to the HTML, so it is delivered quickly.
All these will help your web app render the initial route quickly and become interactive with the users before the rest of the payload arrives. It improves performance and user experience.
This is a technique whereby assets are cached in the browser so that when the requests for the asset are made, they are served from the cache without interrupting user experience. This is very helpful when the phone is offline.
So instead of the user seeing a network failure, the cached assets are served. When the phone comes online, the assets are then refreshed from the network.
Pre-caching is done using service workers, and is mostly used in PWAs.
Pre-caching in service workers have different strategies:
There is a library called Workbox from Google. It aims to provide tools for maintaining cache in service workers, and also provides the caching strategies we just saw above to choose from.
Let’s see an example of how to use Workbox for pre-caching.
Using Workbox, we set the route and the caching strategy we want to use. Workbox will listen for the route request and determine how the request will be cached and responded to.
import { registerRoute } from 'workbox-routing'; import { NetworkFirst } from 'workbox-strategies'; registerRoute( // We are caching the style resources. ({request}) => request.destination === 'style', // We are setting the style cahcing to use the // StaleWhileRevalidate strategy. // Use cache but update in the background. new StaleWhileRevalidate() );
The above is a simple example of how to cache our style resources in Workbox to use the StaleWhileRevalidate
strategy. This will cache all our style files for faster fetching on reload. They are quietly updated from the background when there is network.
registerRoute( ({request}) => request.destination === 'script', new NetworkFirst() );
Here, all our JavaScript files are cached using the NetworkFirst
strategy. They are fetched from the cache when fetch over network fails.
This is defers the loading of routes and assets in a web app to some other time.
The most important concepts of application performance are response time and resources consumption. It is inevitable that they are going to happen. A problem can arise from anywhere, and it is important to find and address problems before they happen.
Lazy loading helps reduce the risk many web app performance problems to a minimum. Lazy loading checks the concepts we listed above:
Lazy loading can be done natively in Chrome without the help of external libraries. In other words, lazy loading will be supported natively by the browser.
It is as simple as adding the loading
attribute to resources we want to be lazy loaded. For example, we have an image:
<img src="./big-image.jpg" />
To natively lazy load the above image, we just add the loading
attribute with value "lazy"
:
<img src="./big-image.jpg" loading="lazy" />
The loading
attribute is the key. It tells the browser that this resource is not to be eagerly loaded unless told otherwise. This attribute can be included in image
, iframe
, video
, and audio
tags to lazy load them.
The loading
attribute has different values we can choose from:
Lazy loading can also be done programmatically using the Intersection Observer API. Libraries in Angular, React, and Vue use Intersection Observer to lazy load components and resources.
In this post, we covered what PRPL is. It is a pattern or collections techniques set by modern web standards that dictates how to optimize our web app for efficiency. We went on to look at its different techniques, from pre-loading and pre-fetching to Lazy loading.
I’ll say the standards have been set. We just have to follow them to make blazingly-fast modern web apps.
The future is here.
If you have any questions regarding this or anything I should add, correct, or remove, feel free to comment, email, or DM me.
As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowHandle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
Design React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.