<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Kasper Andreassen</title>
    <description>The latest articles on Forem by Kasper Andreassen (@kaspera).</description>
    <link>https://forem.com/kaspera</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F613525%2F3b140e55-dbcb-494a-a19d-ad65e0b20920.jpg</url>
      <title>Forem: Kasper Andreassen</title>
      <link>https://forem.com/kaspera</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kaspera"/>
    <language>en</language>
    <item>
      <title>Meet the new Core Web Vital: Interaction to Next Paint (INP) 🎨</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Fri, 11 Aug 2023 09:05:43 +0000</pubDate>
      <link>https://forem.com/enterspeed/meet-the-new-core-web-vital-interaction-to-next-paint-inp-dbc</link>
      <guid>https://forem.com/enterspeed/meet-the-new-core-web-vital-interaction-to-next-paint-inp-dbc</guid>
      <description>&lt;p&gt;In March 2024, Interaction to Next Paint (INP) will replace one of the current Core Web Vitals, First Input Delay (FID).&lt;/p&gt;

&lt;p&gt;This is a big deal since Core Web Vitals affect both user experience and your organic rankings on Google (Core Web Vitals became a part of Google's ranking algorithm in 2021).&lt;/p&gt;

&lt;p&gt;Before we dive into what Interaction to Next Paint (ICP) is, let's do a quick recap by answering the following questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are Core Web Vitals?&lt;/li&gt;
&lt;li&gt;Why are they important?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;💡 If you're already familiar with Core Web Vitals, you can skip to the "Meet the new kid on the block: INP"-section.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is core Web Vitals?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7fvizcxe6cfubhy6h01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7fvizcxe6cfubhy6h01.png" alt="Core Web Vitals"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Image source: &lt;a href="https://web.dev/vitals/" rel="noopener noreferrer"&gt;https://web.dev/vitals/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Core Web Vitals represent a trio of essential user-centric metrics introduced by Google for evaluating and enhancing website performance. These three are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Largest Contentful Paint (LCP):&lt;/strong&gt; This metric measures the time the largest visible element takes to appear, influencing the perceived speed of a website's loading process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First Input Delay (FID):&lt;/strong&gt; FID measures the responsiveness of a webpage by quantifying the delay between a user's initial interaction (e.g., clicking a button) and the browser's ability to respond. It indicates how smoothly users can interact with a site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cumulative Layout Shift (CLS):&lt;/strong&gt; CLS evaluates visual stability by calculating unexpected layout shifts during page load. These shifts can lead to accidental clicks or confusing user experiences. CLS aims for a smooth loading process without abrupt visual changes.&lt;/p&gt;

&lt;p&gt;These metrics collectively offer insights into the user experience journey on a website, covering loading efficiency, interactivity, and visual consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why are Core Web Vitals important?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyupuk671cskzi4j2eozy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyupuk671cskzi4j2eozy.jpg" alt="User being frustrated by a poor user experience"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A poor user experience can both frustrate and infuriate users.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are multiple reasons why it's essential to consider Core Web Vitals when developing a website, for instance:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Experience (UX):&lt;/strong&gt; Core Web Vitals directly address the factors that influence how users perceive and engage with websites. Websites can create a smoother and more satisfying user journey by focusing on loading speed, interactivity, and visual stability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Engine Optimisation (SEO):&lt;/strong&gt; Google has integrated Core Web Vitals into its ranking algorithm, making them a critical element for search engine optimisation. Websites that excel in these metrics are more likely to rank higher in search results, leading to increased visibility and organic traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conversion Rate Optimisation (CRO):&lt;/strong&gt; Core Web Vitals are crucial in CRO strategies. Improved user experience, stemming from better loading times and interactivity, directly contributes to higher conversion rates. Users are more likely to complete desired actions when they have a seamless and engaging website experience.&lt;/p&gt;

&lt;p&gt;Now let's look at the new Core Web Vital that will replace First Input Delay (FID): The new metric, Interaction to Next Paint (INP).&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the new kid on the block: INP
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskeqhc2mud3sucm28xyu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskeqhc2mud3sucm28xyu.jpg" alt="The new kid on the block: INP"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can think of Interaction to Next Paint (INP) as First Input Delay (FID) on steroids 💉&lt;/p&gt;

&lt;p&gt;Where FID only measures the first interaction, INP measures all page interactions. That means you get the latency of all the interactions the user has made with a page.&lt;/p&gt;

&lt;p&gt;However, you are not getting the value (metric) of each interaction, but instead, a single value which all / nearly all interactions were below (longest interaction observed, ignoring outliers). INP gets calculated when the user leaves the page.&lt;/p&gt;

&lt;p&gt;So, where FID tells you something about the first impression, INP tells you something about the overall experience. &lt;/p&gt;

&lt;p&gt;A low INP will indicate that the page consistently responded fast to all (or the vast majority) of user interactions.&lt;/p&gt;

&lt;p&gt;But why switch out FID with INP? According to Chrome usage data, users spend roughly 90% of the time on a page after it loads. Therefore, responsiveness throughout the page lifecycle is essential in determining the overall user experience. The INP metric precisely evaluates this aspect.&lt;/p&gt;

&lt;p&gt;The goal is to provide good responsiveness to the user, which comes when the page responds quickly to the user's interaction. This response will be some sort of visual feedback. Visual feedback tells the user if an item has been added to the shopping cart, a login form is being authenticated, etc.&lt;/p&gt;

&lt;p&gt;INP is not about showing the "end result" as quickly as possible but to cue the user that something is happening.&lt;/p&gt;

&lt;p&gt;An interaction might be highly complex and take a long time. INP doesn't care how long a network fetch takes but rather how quickly you display visual feedback to the user. This could be with a loading spinner. INP only wants to know how long the next paint is blocked.&lt;/p&gt;

&lt;p&gt;Why? Because poor responsiveness can cause multiple unintended responses (e.g., clicking a button too many times), which results in a poor user experience.&lt;/p&gt;

&lt;p&gt;We've talked a lot about interactions, so let's look at exactly what an interaction is.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is an interaction?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjekw5vgc7pl38qipuafk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjekw5vgc7pl38qipuafk.png" alt="Interaction illustration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Image source: &lt;a href="https://web.dev/inp/" rel="noopener noreferrer"&gt;https://web.dev/inp/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The interactivity is often happening through JavaScript. However, native browser controls like checkboxes, radio buttons, and CSS-powered controls also provide interactivity.&lt;/p&gt;

&lt;p&gt;INP doesn't measure all interaction types, but only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mouse clicking&lt;/li&gt;
&lt;li&gt;Tapping on a touchscreen&lt;/li&gt;
&lt;li&gt;Keystrokes (pressing a key on a keyboard)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means that INP does not measure hovering and scrolling unless the user scrolls with their keyboard (space, page up/down, etc.) since this is a keystroke.&lt;/p&gt;

&lt;p&gt;It's also important to note that an interaction may consist of multiple parts. For instance, when you hit a key, you trigger the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event" rel="noopener noreferrer"&gt;keydown&lt;/a&gt; and the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/keyup_event" rel="noopener noreferrer"&gt;keyup&lt;/a&gt; events. In this case, the event with the longest duration within the interaction is chosen for the interaction's latency.&lt;/p&gt;

&lt;p&gt;How many interactions there are on a website depends on the type of website/page. If a page is made up of mostly text and images, there may be few or no interactive elements. If, on the other hand, the page includes a text editor or a game, there could be hundreds or even more interactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a good INP?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsekmuy6n9n7hheufdldu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsekmuy6n9n7hheufdldu.png" alt="Interaction to Next Paint (INP) rating"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Image source: &lt;a href="https://web.dev/inp/" rel="noopener noreferrer"&gt;https://web.dev/inp/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It can be challenging to rate responsiveness as "good" or "poor" since the capability of a user's device can vary a lot (e.g., older vs newer devices).&lt;/p&gt;

&lt;p&gt;However, a good rule of thumb is to measure the 75th percentile of page loads (segmented across mobile and desktop devices). Then a &lt;strong&gt;good&lt;/strong&gt; INP would be below or at &lt;strong&gt;200 ms&lt;/strong&gt;. If the INP is between &lt;strong&gt;200 ms&lt;/strong&gt; and &lt;strong&gt;500 ms&lt;/strong&gt;, your page's responsiveness needs improvement. Everything above &lt;strong&gt;500 ms&lt;/strong&gt; means that your page has poor responsiveness.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you measure INP?
&lt;/h3&gt;

&lt;p&gt;The best way to measure INP is by using "field data", which refers to real-world performance data collected from users interacting with your website.&lt;/p&gt;

&lt;p&gt;One way to get these field data is to use the Chrome User Experience Report (CrUX) – if your website qualifies for inclusion. You can find the CrUX in &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;Pagespeed Insights&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another way to get field data is by using a Real User Monitoring tool (RUM) like &lt;a href="https://www.speedcurve.com/" rel="noopener noreferrer"&gt;SpeedCurve&lt;/a&gt;. If you're using a framework like Next.js (v10+), Nuxt.js (v2+), or Gatsby (v2+), you can also use &lt;a href="https://vercel.com/docs/concepts/speed-insights" rel="noopener noreferrer"&gt;Vercels Speed Insights&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also test INP "in the lab" (synthetic testing), but it's recommended to have field data so that you can reproduce and identify slow interactions.&lt;/p&gt;

&lt;p&gt;Read more about how to &lt;a href="https://web.dev/diagnose-slow-interactions-in-the-lab/" rel="noopener noreferrer"&gt;diagnose slow interactions in the lab&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;💡 Tip: If you don't have any field data, the Total Blocking Time (TBT), which assesses page responsiveness during load, correlates very well with INP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can INP always be measured?&lt;/strong&gt;&lt;br&gt;
No, there are times when INP isn't measured, for instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user never "interacted" (clicked, tapped, or pressed a key) - with the page.
A bot accessed the page (e.g., a crawler or a headless browser).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to optimize INP?
&lt;/h3&gt;

&lt;p&gt;Before we can optimise INP, we need to know one crucial thing. Can you guess &lt;em&gt;what&lt;/em&gt; it is?&lt;/p&gt;

&lt;p&gt;We need to know what to optimise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkexlitha46vil6tu22oa.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkexlitha46vil6tu22oa.gif" alt="Rolling eyes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, I know it seems obvious. But unlike other web vitals, such as Largest Contentful Paint (LCP), finding which interactions are slow can be difficult.&lt;/p&gt;

&lt;p&gt;Ideally, you already have loads of field data showing which interactions need to be optimised.&lt;/p&gt;

&lt;p&gt;Unfortunately, in the real world, this isn't always the case.&lt;/p&gt;

&lt;p&gt;Maybe you don't have any Real User Monitoring (RUM) implemented on your site. Perhaps you don't have the required traffic amount to be present in the CrUX reports. Or maybe it's an entirely new site that you want to ensure performs well before shipping it.  &lt;/p&gt;

&lt;p&gt;So, let's see how we can find and diagnose these slow interactions "in the lab".&lt;/p&gt;

&lt;h4&gt;
  
  
  Diagnosing INP in the lab
&lt;/h4&gt;

&lt;p&gt;To diagnose INP in the lab, you will need a Chrome browser and the &lt;a href="https://web.dev/diagnose-slow-interactions-in-the-lab/" rel="noopener noreferrer"&gt;Web Vitals Chrome Extension&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once installed, locate the extension and open it. Click on the gear icon (settings) in the bottom right corner. Enable Console logging and click save.&lt;/p&gt;

&lt;p&gt;Now all interactions will be logged into your console (You can open the console using the keyword shortcut &lt;em&gt;Ctrl + Shift + J&lt;/em&gt; on Windows and &lt;em&gt;Cmd + Opt + J&lt;/em&gt; on Mac), including other web vitals.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3igybak7oxq28wmoubs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3igybak7oxq28wmoubs.png" alt="Interactions logged to the console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, it's time to put in the legwork. You need to go through your site interactions to test if any interactions need improvement.&lt;/p&gt;

&lt;p&gt;If you have a larger site, you can write down common user flows and test them out. For instance, if you have an e-commerce site, the flow and interactions could look something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on a product.&lt;/li&gt;
&lt;li&gt;Select a variant (e.g., size, colour).&lt;/li&gt;
&lt;li&gt;Select quantity.&lt;/li&gt;
&lt;li&gt;Click "Add to cart".&lt;/li&gt;
&lt;li&gt;Click the cart icon in the header.&lt;/li&gt;
&lt;li&gt;Click the "Go to checkout"-button.&lt;/li&gt;
&lt;li&gt;Apply a discount code.&lt;/li&gt;
&lt;li&gt;Fill out the shipping address.&lt;/li&gt;
&lt;li&gt;Choose a shipping option.&lt;/li&gt;
&lt;li&gt;Go to payment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember, the more interactions you can test, the better. For instance, you may find that the discount field is performing slowly without showing any loading indicator. Or maybe the Postal code field, which automatically fills out the City field, is the sinner.&lt;/p&gt;

&lt;p&gt;Once you have been through all interactions (or the most common ones), you should have a list of interactions which need improvement. If you don't have any, give yourself a pad on the back and take a break.&lt;/p&gt;

&lt;p&gt;If you're not that lucky, we will look into how we optimise the interactions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Optimise interactions
&lt;/h4&gt;

&lt;p&gt;Now you should have identified one or more slow interactions you can reproduce.&lt;/p&gt;

&lt;p&gt;An interaction can be split into three phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The input delay:&lt;/strong&gt; Starts when the user initiates an interaction and ends when the event callbacks begin to run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The processing time:&lt;/strong&gt; The time it takes for event callbacks to run to completion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The presentation delay:&lt;/strong&gt; The time it takes for the browser to present the next frame.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add these three together, and you get the total interaction latency.&lt;/p&gt;

&lt;h5&gt;
  
  
  Reducing Input delay
&lt;/h5&gt;

&lt;p&gt;Input delay, the time from user interaction initiation to event callback execution on a website, plays a crucial role in determining the responsiveness of web interactions. These are some of the ways we can reduce input delay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prioritise Brief Interactions:&lt;/strong&gt; Shortening the duration of interactions is key to meeting the Interaction to Next Paint (INP) metric. While eliminating input delay entirely is challenging, focusing on reducing main thread workload during user interactions can significantly enhance responsiveness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimise Timer Usage and Animation:&lt;/strong&gt; Be careful about employing JavaScript timers like &lt;a href="https://developer.mozilla.org/docs/Web/API/setTimeout" rel="noopener noreferrer"&gt;setTimeout&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/docs/Web/API/setInterval" rel="noopener noreferrer"&gt;setInterval&lt;/a&gt;. Instead, try to use CSS animations over JavaScript animations, as they minimise the accumulation of animation frames that can cause delays.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manage Long Tasks:&lt;/strong&gt; Long tasks on the main thread contribute to extended input delays. Optimise task workload and consider breaking down lengthy tasks into smaller segments using yielding to improve overall responsiveness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Address Interaction Overlap:&lt;/strong&gt; Interaction overlap, where subsequent actions interrupt the rendering of the initial interaction, can worsen input delay. Use techniques like &lt;a href="https://web.dev/debounce-your-input-handlers/" rel="noopener noreferrer"&gt;debouncing&lt;/a&gt; for user inputs and the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort" rel="noopener noreferrer"&gt;AbortController&lt;/a&gt; to manage resource-intensive interactions.&lt;/p&gt;

&lt;h5&gt;
  
  
  Optimising Event Callbacks
&lt;/h5&gt;

&lt;p&gt;After tackling input delay, the focus shifts to ensuring these callbacks respond swiftly to user interactions. Here are some of the ways we can optimise these callbacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frequent Main Thread Yielding:&lt;/strong&gt; Prioritise minimising work within event callbacks. Break tasks into smaller units using techniques like setTimeout to prevent long tasks from obstructing the main thread. This approach enhances overall responsiveness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Efficient Rendering with Yielding:&lt;/strong&gt; Yield immediately after an event callback that updates the UI to expedite rendering. Advanced techniques involve structuring code to prioritise rendering-critical tasks, deferring non-critical ones for subsequent tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preventing Layout Thrashing:&lt;/strong&gt; Avoid synchronous layout issues (layout thrashing) by refraining from updating and immediately reading styles within the same task. Be cautious of properties causing this problem in JavaScript.&lt;/p&gt;

&lt;h5&gt;
  
  
  Minimising Presentation Delay
&lt;/h5&gt;

&lt;p&gt;Now it's time for the presentation, where we need to minimise the delay as much as possible. The presentation delay occurs between event callbacks' completion and the browser painting the next frame to reflect interaction changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reducing DOM Size:&lt;/strong&gt; Smaller DOMs lead to quicker rendering, while larger DOMs increase workload. Efforts like flattening the DOM or strategically adding elements help, though large DOMs may remain challenging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lazy Rendering with content-visibility:&lt;/strong&gt; Utilise CSS's content-visibility to render elements as they approach the viewport. It cuts rendering time, benefiting the Interaction to Next Paint (INP) metric.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smart HTML Rendering via JavaScript:&lt;/strong&gt; JavaScript-rendered HTML impacts performance, involving parsing, styling, and rendering. Balancing initial HTML with JavaScript content updates avoids delayed frame presentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledgement
&lt;/h2&gt;

&lt;p&gt;These great articles have been used as the basis of this article. Some explanations have been quoted directly to avoid confusing the reader.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interaction to Next Paint (INP): &lt;a href="https://web.dev/inp/" rel="noopener noreferrer"&gt;https://web.dev/inp/
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Optimise Interactions to Next Paint: &lt;a href="https://web.dev/optimize-inp/" rel="noopener noreferrer"&gt;https://web.dev/optimize-inp/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Diagnose slow interactions in the lab: &lt;a href="https://web.dev/diagnose-slow-interactions-in-the-lab/" rel="noopener noreferrer"&gt;https://web.dev/diagnose-slow-interactions-in-the-lab/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Optimise input delay: &lt;a href="https://web.dev/optimize-input-delay/" rel="noopener noreferrer"&gt;https://web.dev/optimize-input-delay/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need to dive into the subject of INP even more, I recommend starting here: &lt;a href="https://web.dev/how-to-optimize-inp/" rel="noopener noreferrer"&gt;https://web.dev/how-to-optimize-inp/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Speeding up B2B pricing on your website</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Wed, 10 May 2023 08:22:01 +0000</pubDate>
      <link>https://forem.com/enterspeed/speeding-up-b2b-pricing-on-your-website-112i</link>
      <guid>https://forem.com/enterspeed/speeding-up-b2b-pricing-on-your-website-112i</guid>
      <description>&lt;p&gt;It's all 'bout the money. It's all 'bout the dum dum da da dum dum. If you were rocking it in the late 90s, you might remember this &lt;a href="https://www.youtube.com/watch?v=YcXMhwF4EtQ"&gt;Meja lyric&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, more than 20 years later, not much has changed. Well, maybe the dum dum da da dum dum, but it is, in fact, still all 'bout the money.&lt;/p&gt;

&lt;p&gt;When it comes to business, getting the price exactly right is an art in itself, which is why pricing is also one of the four Ps in the legendary &lt;a href="https://www.investopedia.com/terms/f/four-ps.asp"&gt;"Marketing mix"-model&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Perfecting the price and finding the sweet spot in &lt;a href="https://en.wikipedia.org/wiki/Supply_and_demand"&gt;supply and demand&lt;/a&gt; is both a complex and continuous task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vzzlycfn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fwl6uy4ek8xn9mk6oupi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vzzlycfn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fwl6uy4ek8xn9mk6oupi.png" alt="Supply and demand curve" width="750" height="750"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The price P of a product is determined by a balance between production at each price (supply S) and the desires of those with purchasing power at each price (demand D). The diagram shows a positive shift in demand from D1 to D2, resulting in an increase in price (P) and quantity sold (Q) of the product. (Source: &lt;a href="https://en.wikipedia.org/wiki/Supply_and_demand"&gt;Wikipedia&lt;/a&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The complexity of this task doesn't decrease when it comes to B2B prices. Unlike regular B2C pricing, where the price is mostly the same – and visible – to all customers, B2B pricing often has custom prices (individual prices for different customer segments), and the price itself is often hidden behind a login.&lt;/p&gt;

&lt;p&gt;There can be various reasons for this. The price may be tailored to the customers' use case and needs. They may have negotiated a better deal. Or the price/discount may have been set by how much money they're spending (e.g., a large company buying 1,000 bags of coffee a month will get a better deal than a company buying 50 bags).&lt;/p&gt;

&lt;p&gt;If we take a traditional B2B eCommerce/catalogue site, a common way to handle custom prices would be to have predefined customer segments/groups to which each customer can belong. E.g.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Segment X&lt;/li&gt;
&lt;li&gt;Segment Y&lt;/li&gt;
&lt;li&gt;Segment Z&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a relatively simple and easy way to manage it. The rules can be as complex as one wishes, e.g., different discounts on a category/product level, etc. The point is that the price is tied to the customer segment/group and not to the individual customer.&lt;/p&gt;

&lt;p&gt;These rules are handled either by the eCommerce system or, especially for larger sites, a PIM system (Product Information Management).&lt;/p&gt;

&lt;p&gt;A PIM system can help to greatly optimise the way a company manages and organises its product data. They come in all shapes and sizes but can quickly become both large and expensive.&lt;/p&gt;

&lt;p&gt;However, what we're interested in isn't the cost nor the size. It's the speed. We want websites to be blazingly fast, no matter how complex the products are.&lt;/p&gt;

&lt;p&gt;Now you're probably thinking: &lt;em&gt;"Naturally, big expensive enterprise solutions offer fast responses with an easy API, right?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--olXHuOpv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wyebejn8nfxlpkguayci.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--olXHuOpv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wyebejn8nfxlpkguayci.gif" alt="Oh, my sweet summer child" width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yeah, about that. There's a reason why, for instance, the banking industry still runs on COBOL (a 60-year-old programming language). Big enterprise solutions aren't famous for running bleeding edge solutions… or cutting edge solutions… or "whatever is 500 meters away from the edge"-solutions.&lt;/p&gt;

&lt;p&gt;Please note that we're not saying this is true for all systems. Not at all. But we are saying that there are systems out there that could improve their performance and the APIs they offer.&lt;/p&gt;

&lt;p&gt;Luckily there are solutions for this * &lt;em&gt;clears throat&lt;/em&gt; *.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uxG5Em-t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h9kzl018yanggu58082j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uxG5Em-t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h9kzl018yanggu58082j.png" alt="Mrs Robinson, you're trying to seduce me" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, yes. Once again, we're tooting our own horn (but &lt;em&gt;what&lt;/em&gt; a horn).&lt;/p&gt;

&lt;p&gt;If you know anything about Enterspeed, you know the deal, so we'll make it quick.&lt;/p&gt;

&lt;p&gt;We help you decouple your systems, be it CMS, eCommerce, or PIM, by storing your data in our blazing-fast network. Moreover, you can easily transform and combine data from multiple sources using our low-code schema designer to model your content.   &lt;/p&gt;

&lt;p&gt;But, Enterspeed also shines (sorry, this is the last toot – promise!) by essentially caching complex data like PIM data. You can read more about how we handle &lt;a href="https://www.enterspeed.com/blog/automatic-cache-invalidation"&gt;Automatic Cache invalidation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, the question is, how do we handle showing customer-specific prices without exposing them to other customers? Let's look at one way to do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling custom prices with Enterspeed
&lt;/h2&gt;

&lt;p&gt;To keep this article short, let's say we've already &lt;a href="https://www.enterspeed.com/features/ingest"&gt;ingested&lt;/a&gt; our data into Enterspeed and &lt;a href="https://www.enterspeed.com/features/transform"&gt;modelled the data&lt;/a&gt;, which now leaves us with X number of product views available to fetch via our &lt;a href="https://www.enterspeed.com/features/delivery"&gt;Delivery API&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hamburger Earmuffs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Frink inventions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The tastiest way to keep your ears warm!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"inStock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"regular"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"sale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"customer-x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"customer-y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"customer-z"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="w"&gt; 
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the response we get when we fetch the URL "/products/hamburger-earmuffs". In this example, we have stored all the custom prices in the pricing object.&lt;/p&gt;

&lt;p&gt;Now we want to show the correct price to the correct customer – and only show the price if they're authenticated.&lt;/p&gt;

&lt;p&gt;Because we have all the information stored in a single view, we can't fetch it client-side since that would reveal all the prices in the GET request. So instead, we'll have to fetch it server-side.&lt;/p&gt;

&lt;p&gt;If we use a framework like Next.js, that's really easy to do by using the &lt;a href="https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props"&gt;getServerSideProps&lt;/a&gt; – and even easier if you're using their new &lt;a href="https://beta.nextjs.org/docs/data-fetching/fundamentals"&gt;app directory&lt;/a&gt; in Next 13, which uses server components.&lt;/p&gt;

&lt;p&gt;So, let's build a function that does that. Then, let's break down what it should do.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It should fetch the data from the Enterspeed Delivery API&lt;/li&gt;
&lt;li&gt;It should only return a price if a user is authenticated.&lt;/li&gt;
&lt;li&gt;It should check to see if the user belongs to one of the groups on the price object.&lt;/li&gt;
&lt;li&gt;If the user doesn't belong to any of the groups, it should return the regular price.&lt;/li&gt;
&lt;li&gt;If there's a sale price present, it should check to see if it's lower than the customer's price since we don't want to cheat them out of any discounts we're running.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We start by creating a helper function to handle fetching the data from Enterspeed. You can &lt;a href="https://github.com/enterspeedhq/enterspeed-demos/blob/master/next-middleware/lib/enterspeed.js"&gt;check it out here&lt;/a&gt; in one of our demo repos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://delivery.enterspeed.com/v2?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Api-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ENTERSPEED_PRODUCTION_ENVIRONMENT_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getByHandle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`handle=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getByUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`url=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we create the function that handles the actual product logic. Let's call it getProduct.&lt;/p&gt;

&lt;p&gt;An auth provider will handle the authentication, including the user's segment/group. In this example, we assume we have access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the user is authenticated or not.&lt;/li&gt;
&lt;li&gt;The user segment (if any) the user belongs to.&lt;/li&gt;
&lt;li&gt;The URL of the product they want to view.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having access to this, we can build a function that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getByUrl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/lib/enterspeed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getProduct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userSegment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getByUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Product not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login to see price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userSegment&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasOwnProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userSegment&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customerPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userSegment&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;salePrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;salePrice&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;salePrice&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;customerPrice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;salePrice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;customerPrice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;getProduct&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down step-by-step what the function does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Takes three parameters: url (a string), isAuthenticated (a boolean), and userSegment (an optional string).&lt;/li&gt;
&lt;li&gt;Attempts to fetch a product object from the Enterspeed Delivery API using the getByUrl function.&lt;/li&gt;
&lt;li&gt;If the product object is falsy (e.g. null, undefined, etc.), throws an error with the message "Product not found".&lt;/li&gt;
&lt;li&gt;Extracts the price property from the product object and assigns it to a variable.&lt;/li&gt;
&lt;li&gt;If isAuthenticated is false, sets the price variable to the string "Login to see price".&lt;/li&gt;
&lt;li&gt;If userSegment is falsy or the price object doesn't have a property matching userSegment, sets the price variable to the default property of the price object.&lt;/li&gt;
&lt;li&gt;Retrieves the price of the product based on the userSegment&lt;/li&gt;
&lt;li&gt;Retrieves the sale price of the product, if it exists.&lt;/li&gt;
&lt;li&gt;If a sale price exists and it's less than the customer price, sets the price variable to the sale price. Otherwise, sets the price variable to the customer price.&lt;/li&gt;
&lt;li&gt;Returns a new object that contains all of the properties of the original product object, as well as the price property that was just calculated.&lt;/li&gt;
&lt;li&gt;If an error occurs during the execution of the function, logs the error to the console and returns an object with an error property containing the error message.&lt;/li&gt;
&lt;li&gt;Exports the getProduct function as the default export of the module.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. By syncing our product data, including our custom pricing, to Enterspeed, we have essentially cached it. And because we handle all the price logic server-side, only the correct price will be visible to the user.&lt;/p&gt;

&lt;p&gt;If you want to give Enterspeed a spin, you can sign up for our free plan, which includes 5,000 source entities and 500,000 delivery API requests per month. You can check out our &lt;a href="https://www.enterspeed.com/pricing"&gt;pricing right here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>performance</category>
    </item>
    <item>
      <title>Unleashing the full power of Middleware with Enterspeed ⚡</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Wed, 15 Feb 2023 09:00:00 +0000</pubDate>
      <link>https://forem.com/enterspeed/unleashing-the-full-power-of-middleware-with-enterspeed-4gm3</link>
      <guid>https://forem.com/enterspeed/unleashing-the-full-power-of-middleware-with-enterspeed-4gm3</guid>
      <description>&lt;p&gt;For many years, middleware has played a crucial role in web development. It enables applications to operate smoothly and effectively to meet user demands.&lt;/p&gt;

&lt;p&gt;It acts as a bridge between the UI (frontend) and the backend services, handling tasks such as authentication, routing, and data processing.&lt;/p&gt;

&lt;p&gt;However, building and managing middleware isn’t always easy, especially when it comes to scalability, maintainability, and performance.&lt;/p&gt;

&lt;p&gt;Luckily, frameworks like Next.js have moved the needle regarding innovating what is possible with middleware and how easy it is to use. For instance, they have made it really easy to work together with Edge functions, making it possible to personalise content based on the user’s geolocation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;💡 An edge function is a serverless function that runs at the edge. If you want to learn more about it, check out Netlify’s “&lt;a href="https://www.netlify.com/blog/edge-functions-explained/" rel="noopener noreferrer"&gt;Understanding Edge Functions: The Edge and Beyond&lt;/a&gt;”.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But as incredible as these innovations are, we’re still left with some challenges regarding middleware. Since it sits in the, you guessed it, middle, we risk that the middleware becomes a bottleneck, making the site slower and hurting the user experience.&lt;/p&gt;

&lt;p&gt;The risk isn’t that high for small and simple sites, but as the sites become larger and more complex, the risk of introducing bottlenecks increases.&lt;/p&gt;

&lt;p&gt;Fortunately, there are some great solutions out there which are designed to address these challenges. Can you guess the name of one of these solutions?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F645c2hz700td5ed4qcl1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F645c2hz700td5ed4qcl1.gif" alt="Everything is coming up, Milhouse!" width="498" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s right. &lt;a href="https://www.youtube.com/watch?v=M67E9mpwBpM" rel="noopener noreferrer"&gt;Everything is coming up, Milhouse&lt;/a&gt;! I mean, Enterspeed (sorry).&lt;/p&gt;

&lt;p&gt;Enterspeed lets you unleash the full power of your middleware. By acting as a middleware accelerator, it can optimise the performance of your application by reducing latency as well as server loads.&lt;/p&gt;

&lt;p&gt;But first, let’s look at what middleware exactly is and what it can do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is middleware?
&lt;/h2&gt;

&lt;p&gt;There are several types of middleware, each with its own use case. The type of middleware we will be focusing on in this article is Application middleware.&lt;/p&gt;

&lt;p&gt;Middleware is a layer of software that sits between the UI and backend services. It acts as a mediator, which allows the UI and the backend services to communicate and exchange data seamlessly.&lt;/p&gt;

&lt;p&gt;The concept of middleware isn’t a new thing – it has been around since &lt;a href="http://homepages.cs.ncl.ac.uk/brian.randell/NATO/nato1968.PDF" rel="noopener noreferrer"&gt;1968&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As the name might suggest, it handles specific tasks within the application, such as data fetching, authentication, request/response handling, etc.&lt;/p&gt;

&lt;p&gt;An example could be a user trying to access a page that requires authentication – e.g. an article behind a paywall.&lt;/p&gt;

&lt;p&gt;The user (client) sends an HTTP request to the server for the URL (&lt;em&gt;e.g. nnytimes.com/hero-robot-rescues-valuable-money&lt;/em&gt;). Before the article is returned, a piece of middleware kicks in.&lt;/p&gt;

&lt;p&gt;The middleware checks if the user is signed in. If not, it redirects the user to /sign-in, else it continues to the article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fat5htgl5g2bspf98r38y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fat5htgl5g2bspf98r38y.png" alt="Middleware diagram" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The authentication is often done using &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;JWT (JSON Web Token)&lt;/a&gt;, which can also include details like user role (e.g. subscriber, premium subscriber, super-duper-ultra-premium subscriber).&lt;/p&gt;

&lt;p&gt;Other use cases for middleware can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data fetching&lt;/li&gt;
&lt;li&gt;Analytics&lt;/li&gt;
&lt;li&gt;A/B testing&lt;/li&gt;
&lt;li&gt;Feature flags&lt;/li&gt;
&lt;li&gt;IP blocking&lt;/li&gt;
&lt;li&gt;Bot detection&lt;/li&gt;
&lt;li&gt;Personalisation with geolocation&lt;/li&gt;
&lt;li&gt;Translation based on geolocation&lt;/li&gt;
&lt;li&gt;Power Parity Pricing strategies&lt;/li&gt;
&lt;li&gt;Use-agent based rendering&lt;/li&gt;
&lt;li&gt;Cookie handling&lt;/li&gt;
&lt;li&gt;Redirects&lt;/li&gt;
&lt;li&gt;Removing query params&lt;/li&gt;
&lt;li&gt;Maintenance mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, there are plenty of use cases to choose from.&lt;/p&gt;

&lt;p&gt;Next, we’ll look into how we can use Enterspeed as a middleware accelerator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accelerating your middleware with Enterspeed
&lt;/h2&gt;

&lt;p&gt;When looking at slow-performing websites, we often see bottlenecks occurring during data fetching.&lt;/p&gt;

&lt;p&gt;The reasons for that can be many things, for instance:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Server placement: The data source is located far away from the client.&lt;/li&gt;
&lt;li&gt;Server resources: The server may not have the resources to handle the amount of traffic.&lt;/li&gt;
&lt;li&gt;Poor load balancing: The server may not have a good way of distributing traffic correctly.&lt;/li&gt;
&lt;li&gt;Query complexity: The queries in the database may be complex, resulting in longer execution times.&lt;/li&gt;
&lt;li&gt;Data fragmentation: The data may be stored in multiple locations (e.g. PIM, Commerce solutions, CMS, etc.).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The list could go on. It all depends on how your setup and architecture look.&lt;/p&gt;

&lt;p&gt;So how can Enterspeed help avoid these kinds of bottlenecks? We touched upon this in our last article, “&lt;a href="https://www.enterspeed.com/blog/all-you-probably-need-to-know-about-caching-on-the-web" rel="noopener noreferrer"&gt;All you (probably) need to know about caching on the web&lt;/a&gt;”, but to sum it up:&lt;/p&gt;

&lt;p&gt;Enterspeed helps you offload your data, which you can combine with multiple data sources (as well as transform) in a Redis cache (in-memory storage). This essentially decouples your server, as well as makes your dynamic content static.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuha4j871qs6uomvwzk6i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuha4j871qs6uomvwzk6i.png" alt="Enterspeed illustration" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So let’s see how an Enterspeed setup could solve the bottlenecks we looked at above.&lt;/p&gt;

&lt;p&gt;For server placement, Enterspeed provides multi-region support globally. Additionally, our entire solution is built on Azure cloud services, which automatically handle scaling and traffic distribution. &lt;/p&gt;

&lt;p&gt;All query complexity and data fragmentation are removed since we combine the user’s data into static views, which automatically regenerate as soon as the data updates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftc0vf09096t2hwd3t96i.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftc0vf09096t2hwd3t96i.gif" alt="Wow, wow, wow... Wow" width="498" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That concludes today’s sales pitch. Now let’s actually try to build the middleware that fetches from the Enterspeed API. In this example, we will be using Next.js and Netlify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up middleware in Next.js with Netlify
&lt;/h2&gt;

&lt;p&gt;To utilise the full power of our middleware, we will use Netlify’s Next.js Runtime, which allows us to rewrite and transform content at the edge.&lt;/p&gt;

&lt;p&gt;Another one of its insanely powerful features is its ability to transform page props on the fly, which we will use in the examples below.&lt;/p&gt;

&lt;p&gt;To get started, make sure you have the latest version of the Netlify CLI (and a Netlify account). The &lt;a href="https://www.npmjs.com/package/netlify-cli" rel="noopener noreferrer"&gt;Netlify CLI&lt;/a&gt; allows us to use edge logic for custom headers and redirects, environment variables, and Netlify Functions on our local machine.&lt;/p&gt;

&lt;p&gt;Next, &lt;a href="https://nextjs.org/docs/getting-started#automatic-setup" rel="noopener noreferrer"&gt;spin up a new Next.js project&lt;/a&gt; and install the &lt;a href="https://www.npmjs.com/package/@netlify/next" rel="noopener noreferrer"&gt;@netlify/next&lt;/a&gt; package, which allows us to use Netlify’s edge functions in our middleware.&lt;/p&gt;

&lt;p&gt;Create a middleware.js (or .ts) file and place it in your root/src directory (same level as your pages folder). The file will run on every request, including static files, pages, assets, etc.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;💡 The example below is based on the brilliant article “&lt;a href="https://www.netlify.com/blog/rewrite-html-transform-page-props-in-nextjs/" rel="noopener noreferrer"&gt;Rewrite HTML and transform page props in Next.js with Next.js Advanced Middleware&lt;/a&gt;” by &lt;a href="https://www.netlify.com/blog/authors/salma-alam-naylor/" rel="noopener noreferrer"&gt;Salma Alam-Naylor&lt;/a&gt;. Check it out for a more in-depth tutorial of how the Next.js Advanced Middleware on Netlify works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6oop38o2e1mixibcg20b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6oop38o2e1mixibcg20b.png" alt="Middleware example" width="800" height="841"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Export an async function named middleware and pass in the request as nextRequest to the function.&lt;/p&gt;

&lt;p&gt;Import { NextResponse} from “next/server” and { MiddlewareRequest } from “@netlify/next”.&lt;/p&gt;

&lt;p&gt;Next, declare the following constants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;const pathname = nextRequest.nextUrl.pathname;&lt;/strong&gt; &lt;em&gt;(We get the pathname from the request)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;const middlewareRequest = new MiddlewareRequest(nextRequest);&lt;/strong&gt; &lt;em&gt;(We initialise a new Netlify MiddlewareRequest, passing in the nextRequest)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;const response = await middlewareRequest.next();&lt;/strong&gt; (&lt;em&gt;We get the next response in the HTTP chain by awaiting middlewareRequest.next())&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, since the file runs on every request, we check to see if the pathname matches some of our unwanted resources. If it does, we simply move on to the next response in the HTTP chain by returning response.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;💡 This can also be done using &lt;a href="https://nextjs.org/docs/advanced-features/middleware#matcher" rel="noopener noreferrer"&gt;matcher &lt;/a&gt;in the config object. However, there seemed to be some problems with this and the Netlify middleware in this case.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Afterwards, we get the actual content from Enterspeed using our &lt;a href="https://docs.enterspeed.com/api#tag/Delivery" rel="noopener noreferrer"&gt;Delivery API&lt;/a&gt;. We have made a helper function called getByUrl, which simply fetches from the API using the URL as the query parameter.&lt;/p&gt;

&lt;p&gt;The data we’re getting back from Enterspeed is based on the &lt;a href="https://docs.enterspeed.com/transform/designing-a-schema" rel="noopener noreferrer"&gt;schema we have designed&lt;/a&gt;. The data also includes a status (200, 301, 404, etc.) and a redirect property, which can include the path to where the content has been moved.&lt;/p&gt;

&lt;p&gt;If we receive status 301, we redirect to the URL provided in the redirect property. If we receive status 404, we throw a 404 response.&lt;/p&gt;

&lt;p&gt;However, if all goes well, we pass the data from Enterspeed using response.SetPageProp, which makes the data available as props in our pages, and afterwards return the response.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;💡 To keep the example simple, we simply look at these three status codes. For real-world applications, you should handle all possible exceptions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, we can create a page using &lt;a href="https://nextjs.org/docs/routing/dynamic-routes" rel="noopener noreferrer"&gt;dynamic routes&lt;/a&gt; to handle the content we get. For this simple example, we have created a simple article that returns a few properties. The properties we get are:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgabbfu1zip25uvc6ib2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgabbfu1zip25uvc6ib2a.png" alt="My free article" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you’re probably thinking, what’s with the “premium” boolean? Glad you asked!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmfu95x19kglpr29l7pj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmfu95x19kglpr29l7pj.gif" alt="Glad you asked!" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Say we have paywalled content, which requires a subscription to read. We then need to check to see if the user has an account before they can access it. This can be done using JWT (JSON Web Token).&lt;/p&gt;

&lt;p&gt;By implementing authentication in the middleware, we’re able to authenticate users before they hit any endpoints – we can even do it right at the edge. You can check out Vercels &lt;a href="https://vercel.com/templates/next.js/jwt-authentication" rel="noopener noreferrer"&gt;JWT Authentication template right here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once authentication has been added, we can make a simple function checking if they’re signed in and if the premium property is true.&lt;/p&gt;

&lt;p&gt;If they’re not signed in and the premium property is true, the user will be redirected to the login page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yoiswqkbkess53l6p96.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yoiswqkbkess53l6p96.png" alt="Middleware " width="800" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;💡 As previously mentioned, we can store data like the user role in the token, which can be implemented in the logic above.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Interesting, but what if we still want the user to be able to access a page but not view all of its information? For instance, a B2B website that only shows prices to its current customers.&lt;/p&gt;

&lt;p&gt;The data could look something like this, where we want to hide the price.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4tt6ryt75vmqfyrfa98.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4tt6ryt75vmqfyrfa98.png" alt="My product" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we simply change the price property before setting the page props. We can either delete it or change its value, like in the example below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90azhy17ohxgxy1t3jvz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90azhy17ohxgxy1t3jvz.png" alt="Middleware " width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ah, you spotted it, didn’t you? Once again, you sit with a puzzled look on your face thinking, “What the dickens is the ppp-property?”&lt;/p&gt;

&lt;p&gt;…And just like a hungry salmon swimming in the crystal-clear river, you took my bait 🎣&lt;/p&gt;

&lt;p&gt;Poor naming convention aside, let’s look at how we can transform prices based on geolocation.&lt;/p&gt;

&lt;p&gt;But why change prices based on geolocation? The answer is Purchasing Power Parity (PPP) strategy.&lt;/p&gt;

&lt;p&gt;Purchasing Power Parity is a pricing strategy where companies set product prices based on local market conditions and production costs in different countries, using PPP exchange rates to ensure price consistency and stability.&lt;/p&gt;

&lt;p&gt;The data could look something like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm62hzjaxy5wd8keh5odc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm62hzjaxy5wd8keh5odc.png" alt="My PPP product" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we have different prices for “DK” (Denmark), “US” (United States), and “UK” (United Kingdom).&lt;/p&gt;

&lt;p&gt;In our middleware, we have access to the visitor’s geodata via the nextRequest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykc0zd9gi1k26q8ubxv7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykc0zd9gi1k26q8ubxv7.png" alt="Middleware " width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can then change our price, depending on the visitor’s country, with some simple logic. First, we check to see if the ppp-property is true, and then we change it to the price for the specific country.&lt;/p&gt;

&lt;p&gt;If we don’t have a price for the user’s country, we use “US” as a fallback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55bjrso3khx75sraoqs3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55bjrso3khx75sraoqs3.png" alt="Middleware " width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How cool is that!&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;You may be sitting in your chair, fingers stretched, screaming, &lt;a href="https://www.youtube.com/watch?v=Sg14jNbBb-8" rel="noopener noreferrer"&gt;“UNLIMITED POWER!”&lt;/a&gt; to the thoughts of all the possibilities of middleware.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fezmg8vw42sijn9y9kqc6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fezmg8vw42sijn9y9kqc6.jpg" alt="" width="700" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s great! The purpose of this article was to show you how you can utilise middleware for your application in a performance-friendly way using Enterspeed.&lt;/p&gt;

&lt;p&gt;You can view other use cases for middleware in &lt;a href="https://vercel.com/templates?type=edge-middleware" rel="noopener noreferrer"&gt;Vercels template library&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The question is then, should you use middleware? To quote every software developer on the planet:&lt;/p&gt;

&lt;p&gt;_“…It depends”.&lt;br&gt;
_&lt;br&gt;
There are a thousand ways to skin a cat. What makes sense for your setup may not make sense for others.&lt;/p&gt;

&lt;p&gt;Simply fetching data and manipulating props can also be done using &lt;a href="https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props" rel="noopener noreferrer"&gt;getServerSideProps&lt;/a&gt;, the same goes for checking &lt;a href="https://nextjs.org/docs/authentication" rel="noopener noreferrer"&gt;authentication&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, what middleware can do, in this case, edge middleware, is to personalise content with very low latency since it’s running on the edge.&lt;/p&gt;

&lt;p&gt;Moreover, because the middleware runs before the cache, it can manipulate statically generated content.&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>All you (probably) need to know about caching on the web 🗃</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Fri, 06 Jan 2023 10:39:59 +0000</pubDate>
      <link>https://forem.com/enterspeed/all-you-probably-need-to-know-about-caching-on-the-web-4loa</link>
      <guid>https://forem.com/enterspeed/all-you-probably-need-to-know-about-caching-on-the-web-4loa</guid>
      <description>&lt;p&gt;Every developer has most likely bumped into the concept of caching at some point in their career.&lt;/p&gt;

&lt;p&gt;For some, it’s a vital instrument in their everyday work that helps their code to run as fast and cost-effective as possible.&lt;/p&gt;

&lt;p&gt;For others, they might only have worked with it that one time they activated “that cache plugin” on a client’s WordPress site.&lt;/p&gt;

&lt;p&gt;If you belong to the first group, you probably won’t need this article. Instead, you can enjoy a nice game of “Chrome dino”. Open a Chrome browser and type: &lt;code&gt;chrome://dino/&lt;/code&gt; (send me your high score afterward!).&lt;/p&gt;

&lt;p&gt;For all you people not so well-travelled in the world of cache, I know you’re dying to also play a nice game of “Chrome dino”, but patience you must have my young Padawan – your time will also come.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficnv5zsrpmr1ioznos75.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficnv5zsrpmr1ioznos75.jpg" alt="Yoda: Patience you must have my young Padawan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, let’s look at what cache is and how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is cache?
&lt;/h2&gt;

&lt;p&gt;Caching is the process of temporarily storing copies of data, so it can be accessed more quickly.&lt;/p&gt;

&lt;p&gt;It’s a concept that has been used in computer science for ages, going all the way back to 1965 when British computer scientist Maurice Wilkes introduced memory caching.&lt;/p&gt;

&lt;p&gt;But copying things doesn’t necessarily make them faster, Let’s look at three examples.&lt;/p&gt;

&lt;p&gt;In the first example, we’ll see how you can reduce the server’s workload by turning your dynamic content into static content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making dynamic content static
&lt;/h2&gt;

&lt;p&gt;Back in the early days of the web, all websites that existed were static – it was all pure, simple HTML.&lt;/p&gt;

&lt;p&gt;Then in the mid 90’s that all changed when multiple server-side languages were released (PHP, ASP, etc.), as well as one client-side scripting language called JavaScript&lt;/p&gt;

&lt;p&gt;Dynamic pages revolutionised the web and made websites more personalised and interactive. However, it also made the web a bit slower since the user now had to wait for a server to render the content.&lt;/p&gt;

&lt;p&gt;Having the server create the content every single time a user visited, even though nothing had changed, seemed a tad bit silly, so developers started implementing caching.&lt;/p&gt;

&lt;p&gt;The caching worked by saving a copy of the “server response”, turning the dynamic content into static content.&lt;/p&gt;

&lt;p&gt;That way they could generate content upfront and serve it both quicker and cheaper to the user – they just had to remember to invalidate it when the content changed.&lt;/p&gt;

&lt;p&gt;Let’s see what this could look like in the real world.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Soup of the day
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9soravzqtyftjuvdifb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9soravzqtyftjuvdifb.jpg" alt="Two delicious soups"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagine a restaurant serving “soup of the day”. Each day a new, different soup is available.&lt;/p&gt;

&lt;p&gt;Which soup is available that day, may depend on which ingredients are in surplus in the kitchen and what the chef feels like making.&lt;/p&gt;

&lt;p&gt;Since the restaurant doesn’t know what the soup of the day will be, they can only write “Soup of the day – ask the waiter” on their menu.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(It’s really difficult for me not to write a witty ‘ask the server’-pun here).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That’s fine for a small restaurant, or one where a waiter takes your order each time. Now imagine a busy street food restaurant serving hundreds of customers throughout the day.&lt;/p&gt;

&lt;p&gt;Getting asked, “What is today's soup?” a hundred times a day, every single day, would turn any man or woman into a &lt;a href="https://www.youtube.com/watch?v=RqlQYBcsq54" rel="noopener noreferrer"&gt;Soup Nazi&lt;/a&gt;, yelling “NO SOUP FOR YOU!” the 249th time this question was asked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7u84x6ij9q0vik9t7br.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7u84x6ij9q0vik9t7br.jpg" alt="No soup for you!!!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The restaurant could instead “cache” this request by writing “Soup of the day: Chicken soup” on a blackboard above the counter.&lt;/p&gt;

&lt;p&gt;The chef could then easily “invalidate” this cache once a day, when the soup changed, or when the soup is sold out.&lt;/p&gt;

&lt;p&gt;In the next example, we will see how we can get the soup… Sorry, I mean content, closer to the user by using caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving the content closer to the user
&lt;/h2&gt;

&lt;p&gt;The location of a server hosting a website can greatly impact the website’s speed.&lt;/p&gt;

&lt;p&gt;If the user is located far away from the server, their response will be slow compared to someone located close to the server.&lt;/p&gt;

&lt;p&gt;The closer the user is to the server, the quicker the response. This is also why there’s big money in &lt;a href="https://theweek.com/articles/493238/wall-streets-secret-advantage-highspeed-trading" rel="noopener noreferrer"&gt;renting out servers as close to the stock exchanges as possible&lt;/a&gt; – for high-speed trading bots, every millisecond counts.&lt;/p&gt;

&lt;p&gt;Luckily, we don’t need to have the servers quite as close to the users, as a trading bot needs to be to a “trading floor”, but we need it to be somewhat close to their geographic area.&lt;/p&gt;

&lt;p&gt;Setting up multiple independent servers to serve multiple geographic areas can be both inefficient, costly, hard to scale, and a nightmare to manage. Luckily there’s a better solution – a CDN.&lt;/p&gt;

&lt;p&gt;A CDN, Content Delivery Network, is a group of servers distributed geographically that work together to deliver your content fast to the user.&lt;/p&gt;

&lt;p&gt;It stores a copy of your data, including HTML pages, JavaScript files, stylesheets, images, and even videos.&lt;/p&gt;

&lt;p&gt;CDN providers have a ton of locations available. Cloudflare, for instance, has &lt;a href="https://www.cloudflare.com/network/" rel="noopener noreferrer"&gt;275 servers in more than 100 countries&lt;/a&gt; – imagine if you had to set that up yourself!&lt;/p&gt;

&lt;p&gt;Let’s try to translate a CDN into a real-world example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: The electrician and his van
&lt;/h3&gt;

&lt;p&gt;Say you are an electrical contractor and run a successful company with several employees under you. Since you’re an electrical contractor and not a baker, you need to go to your customers instead of your customers coming to you.&lt;/p&gt;

&lt;p&gt;The customers can be located far away from the workshop, where you store all your tools, cables, and other supplies.&lt;/p&gt;

&lt;p&gt;Having to go back and forth from the workshop to the customer, each time you need a tool or cable would be quite cumbersome.&lt;/p&gt;

&lt;p&gt;Instead, the electrician “caches” his most used tools and supplies in his van. That way the things he needs are close to him, and he can quickly grab what he needs.&lt;/p&gt;

&lt;p&gt;Each day when he gets back to the workshop, he can “revalidate his cache” by filling up the vans with new supplies, charging his electrical tools, and preparing the van for the next job.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlq0d7mkpn3oh28nvnkq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlq0d7mkpn3oh28nvnkq.jpg" alt="An electricians van"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Image source: &lt;a href="https://www.fieldpulse.com/blog/electrician-van-setup/" rel="noopener noreferrer"&gt;https://www.fieldpulse.com/blog/electrician-van-setup/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the last example, we will see how we can cache our data to faster storage solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving the data to faster storage
&lt;/h2&gt;

&lt;p&gt;All storage is not “built the same”. You'll know what I'm talking about if you have ever switched from using a hard drive to an SSD.&lt;/p&gt;

&lt;p&gt;Hard drives, which store the data on mechanical spinning disks are great at storing a large amount of data since they are incredibly cheap when it comes to “cost per GB”.&lt;/p&gt;

&lt;p&gt;However, having mechanical parts just isn’t as fast as something without any moving parts, like an SSD, which stores its data on flash memory. SSDs are much faster, but (as you may already have guessed) also more expensive.&lt;/p&gt;

&lt;p&gt;Over the years many web hosts have started offering plans with SSD hosting to make their services even faster.&lt;/p&gt;

&lt;p&gt;But do you know what’s even faster and way more expensive than SSD storage? RAM storage.&lt;/p&gt;

&lt;p&gt;That’s right, you can store your files in the RAM. Benchmark has shown read speeds that were &lt;a href="https://www.makeuseof.com/tag/ram-drives-faster-ssds-5-things-must-know/" rel="noopener noreferrer"&gt;6.3 times faster&lt;/a&gt; than the speed of an SSD – insane!&lt;/p&gt;

&lt;p&gt;But no sane person would store websites in memory (RAM), would they? Indeed, they would.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; is an in-memory data store that you can use as a database – or a cache in front of your regular database. The speed of Redis makes it ideal to cache database queries, complex computations, API calls, and session state.&lt;/p&gt;

&lt;p&gt;Now, I know what you are thinking. “I’m just a simple developer trying to make my way in the universe. I’m not going to set up and maintain a separate database merely for caching.”&lt;/p&gt;

&lt;p&gt;I get that, boy do I get that. As the late Phil Karlton once stated: “There are only two hard things in Computer Science: cache invalidation and naming things”.&lt;/p&gt;

&lt;p&gt;If only there was a way to reap the fruits of in-memory storage, without the hassle of setup, maintenance – and more importantly cache invalidation.&lt;/p&gt;

&lt;p&gt;You can see where this is going, can’t you?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7d7srekuwqbuco778gv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7d7srekuwqbuco778gv.gif" alt="Austin Powers: "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, this is indeed shameless self-promotion. Don’t worry, I’ll be sure to whip myself 3 times afterward and put on the &lt;a href="https://www.youtube.com/watch?v=VWU5M1q0WqU" rel="noopener noreferrer"&gt;cone of shame&lt;/a&gt; for the rest of the day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.enterspeed.com/" rel="noopener noreferrer"&gt;Enterspeed&lt;/a&gt; is a way to cache your data in a high-speed, in-memory storage, without the hassle of maintenance and cache invalidation.&lt;/p&gt;

&lt;p&gt;We offload your data, which you can combine with multiple data sources (as well as transform), in a Redis database. What this does is essentially decouple your server, as well as make your dynamic content static.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev2wo82vek7druy7jmkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev2wo82vek7druy7jmkw.png" alt="Illustration of how Enterspeed works"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to know more about how this all works, head over to &lt;a href="https://www.enterspeed.com/" rel="noopener noreferrer"&gt;Enterspeed.com&lt;/a&gt; to read more.&lt;/p&gt;

&lt;p&gt;Well, that’s enough self-promotion (for now). Let’s look at the real-world example for this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: RV with a car storage
&lt;/h3&gt;

&lt;p&gt;After thinking about it for many years, you’ve finally decided that you’re going to see Europe. You’ll take some months of work, rent an RV, and visit all the places you’ve dreamt about seeing.&lt;/p&gt;

&lt;p&gt;An RV is perfect since you don’t have to plan far ahead and can stay at each place for as long as you like. One problem though. While an RV is great for many things, it isn’t exactly an easy or fast vehicle to operate – especially not on those tiny European streets.&lt;/p&gt;

&lt;p&gt;You’ll need something else for those small sightseeing tours. Therefore, you decide to add a car to your travel plans.&lt;/p&gt;

&lt;p&gt;What solves this problem, is an RV with built-in car storage. That way you can use your RV to travel from location to location, and the car to go sightseeing within the location.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhs84gieu4tn5r34594rd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhs84gieu4tn5r34594rd.jpg" alt="An RV with built-in car garage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Image source: &lt;a href="https://www.concorde.eu/modelle/liner" rel="noopener noreferrer"&gt;https://www.concorde.eu/modelle/liner&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you’re not Scrooge McDuck, this could also simply be a bike, or a scooter placed on the back of the bus – but how cool is a freaking built-in car garage!&lt;/p&gt;

&lt;p&gt;Now, before moving on to the “how to” of the article, let’s look at some of the different types of web caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web caching types
&lt;/h2&gt;

&lt;p&gt;There are several types of caching. Some of the most used in web caching includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Client-side caching:&lt;/strong&gt; A cache that is stored on the user’s computer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser caching:&lt;/strong&gt; A cache that is stored on the user’s computer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side caching:&lt;/strong&gt; A cache that is stored on the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN caching:&lt;/strong&gt; A cache that is stored across multiple CDN servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reverse proxy caching:&lt;/strong&gt; A cache stored on a reverse proxy server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you’re properly thinking: “Wait, isn’t client-side caching and browser caching the same thing?”.&lt;/p&gt;

&lt;p&gt;Not exactly. They’re similar in the way they store their data (on the client), but they’re not the same thing. To add to the confusion, the way client-side caching can be implemented is by using the browser’s storage APIs.&lt;/p&gt;

&lt;p&gt;However, what differentiates them isn’t how they store data but rather what types of data they store.&lt;/p&gt;

&lt;p&gt;Client-side caching is used to store responses from the server, e.g., API requests, which then reduces the number of requests that need to be made to the server.&lt;/p&gt;

&lt;p&gt;Browse caching on the other hand stores static resources like images, videos, fonts, stylesheets, HTML files, JavaScript files, etc.&lt;/p&gt;

&lt;p&gt;Both client-side caching and browser caching are controlled by the user’s browser. The developer can control the cache headers (for instance when the cache should be invalidated), but it is ultimately up to the web browser to interpret and enforce them.&lt;/p&gt;

&lt;p&gt;This brings us to Server-side caching which stores the cache on… you guessed it – the server. Here the developer is in full control. There are several types of server-side caching, some of those are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Page caching:&lt;/strong&gt; Stores all the HTML of a page, so the server doesn’t have to generate it dynamically (as explained in the “Making dynamic content static”-example).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database caching:&lt;/strong&gt; Stores data that are frequently used in the database (As explained in the “Moving the data to faster storage”-example).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object caching:&lt;/strong&gt; Stores complex data structures or objects, which reduces the number of database queries needed (Also explained in the “Moving the data to faster storage”-example).
The next type of caching is CDN caching, which we explained in the “Moving the content closer to the user”-example, so we won’t dive too much into that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, we have reverse proxy caching. A reverse proxy cache, also known as a reverse HTTP cache, is also a type of server-side cache.&lt;/p&gt;

&lt;p&gt;It sits in front of the server(s) and acts as a buffer between the client and the server. When a request arrives, it’s forwarded to the server by the reverse proxy which then caches the response. Thus, future requests can be served from the cache instead of hitting the server.&lt;/p&gt;

&lt;p&gt;A reverse proxy cache is therefore a great way to improve not only the performance of a website but also its scalability – a reverse proxy cache is also able to work as a load balancer.&lt;/p&gt;

&lt;p&gt;Before moving on to some of the ways you can implement cache on your website, we need to look at two more types of cache – private and public cache.&lt;/p&gt;

&lt;p&gt;Private caching is a cache that can only be accessed by a specific user or user group. It can be stored both client-side and server-side.&lt;/p&gt;

&lt;p&gt;Some examples of things that can be stored in a private cache are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User preferences (Preferred language, dark mode, etc.)&lt;/li&gt;
&lt;li&gt;Personal data (Name, e-mail address, etc.)&lt;/li&gt;
&lt;li&gt;Private messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Public caching, on the other hand, is accessible to all users and is often stored on the server. Some examples of things that are stored in a public cache are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commonly accessed resources (Images, videos, product catalogues, etc.)&lt;/li&gt;
&lt;li&gt;Public content (articles, blog posts. etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that’s all set, it’s time to move on to how you can use cache on your website using some “easy wins”.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using cache on your website
&lt;/h2&gt;

&lt;p&gt;Designing and setting up a caching strategy can be difficult. There are several ways to tackle cache and it all depends on how your setup looks and what your needs are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing a caching plugin
&lt;/h3&gt;

&lt;p&gt;If you’re using a CMS like WordPress, one of the quickest ways to start utilising cache is by installing a cache plugin.&lt;/p&gt;

&lt;p&gt;One of the most beloved WordPress caching plugins is &lt;a href="https://wp-rocket.me/" rel="noopener noreferrer"&gt;WP Rocket&lt;/a&gt;. They’ve managed to take something complex and make it extremely easy, yet still powerful and configurable. They have also made it easy to integrate with a CDN in just a few clicks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up a CDN
&lt;/h3&gt;

&lt;p&gt;Implementing a CDN is an extremely easy way to implement caching on your website.&lt;/p&gt;

&lt;p&gt;If your site is built on the &lt;a href="https://jamstack.org/" rel="noopener noreferrer"&gt;Jamstack&lt;/a&gt; principles, you are probably already using it via a provider like Netlify, Vercel, Cloudflare Pages, etc.&lt;/p&gt;

&lt;p&gt;If not, then it’s as simple as setting up an account at a CDN provider and updating your nameservers. Cloudflare offers a generous free plan and is really simple to set up. You can read more about the &lt;a href="https://developers.cloudflare.com/fundamentals/get-started/setup/add-site/" rel="noopener noreferrer"&gt;setup here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing ISR (Incremental Static Regeneration)
&lt;/h3&gt;

&lt;p&gt;It’s no secret that I’m a big fan of &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; and its many fantastic features. One of the features I adore is their Incremental Static Regeneration.&lt;/p&gt;

&lt;p&gt;When rendering a page, the choice is usually between SSR (Server-side Rendering) or SSG (Static Site Generation). Due to the risk of poor SEO and slow initial load, CSR (Client-side Rendering) isn’t often used on “regular websites” (non-app websites).&lt;/p&gt;

&lt;p&gt;SSR is great since it makes your pages dynamic but can be slow since it must wait for the server each time.&lt;/p&gt;

&lt;p&gt;SSG is great since it’s super-fast but isn’t easy to update since you will have to do a deployment each time you want to change something.&lt;/p&gt;

&lt;p&gt;Well, ISR completely changed the game. Like a peanut butter and jelly sandwich, it took two great things and combined them, making something even better. It combined the power of SSR with the power of SSG.&lt;/p&gt;

&lt;p&gt;ISR caches each page individually. When a user visits the page, it will check if there is new content available. If there is, it will start regenerating the page and once it’s done, invalidate the cache and show the updated page for the next visitor.&lt;/p&gt;

&lt;p&gt;You can read more about &lt;a href="https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration" rel="noopener noreferrer"&gt;Next.js ISR here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caching API responses
&lt;/h3&gt;

&lt;p&gt;If you are fetching data client-side, e.g., showing your visitors data from the &lt;a href="https://swapi.dev/" rel="noopener noreferrer"&gt;Star Wars API&lt;/a&gt;, you should cache these responses.&lt;/p&gt;

&lt;p&gt;If the user has already made a request to see &lt;a href="https://swapi.dev/api/starships/" rel="noopener noreferrer"&gt;all the starships in Star Wars&lt;/a&gt;, there’s no reason to make an identical request if the user wants to see it again. Instead, the response should be cached.&lt;/p&gt;

&lt;p&gt;You can implement this yourself, for instance by using a state management solution, or something as simple as the &lt;a href="https://reactjs.org/docs/hooks-state.html" rel="noopener noreferrer"&gt;State hook&lt;/a&gt; in React.&lt;/p&gt;

&lt;p&gt;You can also use data fetching packages with built-in caching management, for instance, &lt;a href="https://tanstack.com/query/v4" rel="noopener noreferrer"&gt;TanStack Query&lt;/a&gt;, &lt;a href="https://swr.vercel.app/" rel="noopener noreferrer"&gt;SWR&lt;/a&gt;, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev8oaxvassl8fzqdrl70.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev8oaxvassl8fzqdrl70.jpg" alt="The Chrome Dino game"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this article about caching. You are now free to play all the “Chrome Dino” that you wish.&lt;/p&gt;

&lt;p&gt;The “Chrome Dino” game is shown in Chrome when you are offline. If you want to make your website/app work offline (making it a &lt;a href="https://web.dev/learn/pwa/" rel="noopener noreferrer"&gt;PWA&lt;/a&gt;), one of the ways you can store data is by using cache. You can read more about &lt;a href="https://web.dev/learn/pwa/offline-data/" rel="noopener noreferrer"&gt;“offline data” here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>The illusion of speed – why perceived performance matters</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Mon, 28 Nov 2022 07:50:59 +0000</pubDate>
      <link>https://forem.com/enterspeed/the-illusion-of-speed-why-perceived-performance-matters-f38</link>
      <guid>https://forem.com/enterspeed/the-illusion-of-speed-why-perceived-performance-matters-f38</guid>
      <description>&lt;p&gt;Time is relative.&lt;/p&gt;

&lt;p&gt;We have all heard this expression a million times before. Many years ago, some German dude went on and on about it, and even developed some theories about the same thing.&lt;/p&gt;

&lt;p&gt;But what does it actually mean? With the risk of going all “Neil deGrasse Tyson” and start answering questions that nobody has asked, we can circle back to the aforementioned German dude.&lt;/p&gt;

&lt;p&gt;The German dude, let’s call him Albert, had a secretary who got burdened with questions from reporters about the meaning of relativity. To help her out, Albert told her to simply answer these questions with the following example:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“When you sit with a nice girl for two hours you think it’s only a minute, but when you sit on a hot stove for a minute you think it’s two hours. That’s relativity.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Interesting. This means that how we perceive time depends on our frame of reference. Moreover, the time we see on a clock doesn’t necessarily reflect our “brain's time” (psychological time).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Cool stuff, nerd. But what the heck does this have to do with website performance?”.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcf2kyq0v8jutba847df0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcf2kyq0v8jutba847df0.jpeg" alt="Homer yelling "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hang on, hang on, I’ll get to it. Just like taking your partner on a date, we must first set the mood. So let me light some candles and throw on some Barry White before diving into it.&lt;/p&gt;

&lt;p&gt;When we talk about web performance, we often tend to focus solely on the metrics. &lt;em&gt;“What is your Lighthouse score? What about your LCP? And have you optimized your FID?”&lt;/em&gt; often gets thrown around.&lt;/p&gt;

&lt;p&gt;We are so enthralled in those goddamn Lighthouse scores, that we risk forgetting about the user.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Wait, what? But I do this FOR the user!”.&lt;/em&gt; Yes, and that’s great. Optimizing your web vitals is important to the user experience, but it doesn’t tell you whether the user perceives your website as fast or slow.&lt;/p&gt;

&lt;p&gt;You can have two websites with identical web vital metrics, meaning one loads just as fast as the other, where one is perceived as fast by the user and the other as slow.&lt;/p&gt;

&lt;p&gt;I know, it’s weird.&lt;/p&gt;

&lt;p&gt;We call this perceived performance. Where traditional performance optimization focuses on objective speed, perceived performance optimization focuses on subjective speed, and according to Mozilla, it’s even more important than the “actual performance”.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“The perception of how quickly (and smoothly) pages load and respond to user interaction is even more important that the actual time required to fetch the resources.” – &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Performance/Perceived_performance" rel="noopener noreferrer"&gt;MDN Web Docs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Just like any other subject in tech, the rabbit hole goes quite deep when we start looking into perceived performance. So deep in fact, that a 224 pages book called &lt;a href="https://www.amazon.com/Designing-Engineering-Time-Psychology-Perception/dp/0321509188" rel="noopener noreferrer"&gt;“Designing and Engineering Time: The Psychology of Time Perception in Software”&lt;/a&gt; has been written about it.&lt;/p&gt;

&lt;p&gt;Therefore, we’re not going to follow the white rabbit to the end of the hole in this article, but merely glance into it… we simply don’t have the time (&lt;a href="https://www.youtube.com/watch?v=6zXDo4dL7SU" rel="noopener noreferrer"&gt;bad dum tss!&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9l9p0ot62ow9n8zcv06v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9l9p0ot62ow9n8zcv06v.png" alt="Rabbit from Alice in Wonderland staring at clock"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What we want to do is to keep the user in a state of “flow”. But what is flow?&lt;/p&gt;

&lt;p&gt;Authors Rina A. Doherty and Paul Sorenson describe flow in their paper “&lt;a href="https://www.sciencedirect.com/science/article/pii/S2351978915004370" rel="noopener noreferrer"&gt;Keeping Users in the Flow: Mapping System Responsiveness with User Experience&lt;/a&gt;” as:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“…flow is commonly described as the natural, fluid state of being productively engaged with a task without being aware of the technology that is driving it. As such, if successful, technology can become virtually forgotten when a user is immersed in the experience, or is in the flow”.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A way to make sure we don’t break this flow is to avoid users going into the dreaded “passive state”.&lt;/p&gt;

&lt;h2&gt;
  
  
  The active and the passive state
&lt;/h2&gt;

&lt;p&gt;The way humans perceive time depends on the activity they’re doing. An activity can be broken into two states – the active state and the passive state.&lt;/p&gt;

&lt;p&gt;In the active state, we have a high level of mental activity, and in the passive state, our mental activity is low.&lt;/p&gt;

&lt;p&gt;We often enter the passive state when we're waiting for something, for instance standing in a queue. Professor Richard Larson ran an experiment for this very thing. His research showed that we tend to overestimate the time that has passed in the passive state by a whopping 36% (&lt;a href="https://www.nytimes.com/2012/08/19/opinion/sunday/why-waiting-in-line-is-torture.html" rel="noopener noreferrer"&gt;Source&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Okay, so avoid queues on my website. Got it. Why are you telling me this?”.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because we are constantly switching between the active and the passive state – including when we browse the web.&lt;/p&gt;

&lt;p&gt;We of course are in the active state when we are navigating the web. Can you guess when we enter the passive? Bingo. When we’re waiting for a page to load.&lt;/p&gt;

&lt;p&gt;The good news is we don’t enter this state immediately. It takes about a second for us to transition to this state, according to research done by the Nielsen Norman Group.&lt;/p&gt;

&lt;p&gt;This means, that whenever the load time for a page is 1 second or more, we risk the user entering the passive state. What’s worse is, they experience the load time as much longer than it is.&lt;/p&gt;

&lt;p&gt;As we saw, people on average overestimate the passive state by 36%. If we have a page that takes 4 seconds to load, the user will actually experience this as 5 seconds (25% slower than it really is).&lt;/p&gt;

&lt;p&gt;The first second is in the active state, but in the next three seconds, the user risks moving to the passive state, which they then will experience as 4.08 seconds (3*1.36), which will result in a total perceived load time of 5 seconds (1 + 4.08).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdh1ey3kvsxwlokc2fdfb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdh1ey3kvsxwlokc2fdfb.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may be slamming your fist on the table by now, saying “TIME IS TIME! This isn’t fair!”.&lt;/p&gt;

&lt;p&gt;And no, it isn’t (to both statements). But luckily, there are tricks we can use to overcome this hurdle.&lt;/p&gt;

&lt;p&gt;We have two options here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid users entering the passive state by keeping them in the active state.&lt;/li&gt;
&lt;li&gt;Making the passive states feel faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s look into each.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping the use in the active state
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Loaders
&lt;/h4&gt;

&lt;p&gt;The first thing we can do to keep the users in the active state is to be mindful of when we use loaders (spinner, loading bars, etc.).&lt;/p&gt;

&lt;p&gt;I know this seems counterintuitive but hear me out. Remember &lt;a href="https://www.youtube.com/watch?v=Z-hHGY1MsBA" rel="noopener noreferrer"&gt;the scene&lt;/a&gt; in the first “The Fast and the Furious”-movie (yes, I’m that old), where Jesse presses the Nitrous too early? Johnny then says “Too soon junior”, presses the nitrous, and overtakes poor Jesse. This is the same thing (except, not quite as cool).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdvpl2up7esft10yjb8zs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdvpl2up7esft10yjb8zs.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We don’t want to show the loaders too soon.&lt;/p&gt;

&lt;p&gt;We know it takes the user about a second to naturally transition to the passive state. This means that showing them the loaders when the wait (loading) is under 1 second, is not only unnecessary, but it can also harm the perceived performance.&lt;/p&gt;

&lt;p&gt;Since loaders tell the user that “now they have to wait”, it will force them from an active state into a passive state. Therefore, we should avoid using loaders if we anticipate the wait time to be under a second.&lt;/p&gt;

&lt;h4&gt;
  
  
  Avoid layout shifts
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyojk8jb5tdplqetekydp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyojk8jb5tdplqetekydp.png" alt="Example of CLS"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Image from &lt;a href="https://web.dev/optimize-cls/" rel="noopener noreferrer"&gt;Web.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Content “jumping around“ can make it feel like the page is still loading and send the user into a passive state.&lt;/p&gt;

&lt;p&gt;We call them layout shifts and they are often caused by images being loaded, which then causes the content to “jump”.&lt;/p&gt;

&lt;p&gt;Another culprit can be custom fonts being downloaded. If the custom font isn’t the same size as the fallback font (the font being loaded before showing the custom font), it might cause layout shifts.&lt;/p&gt;

&lt;p&gt;To avoid these layout shifts, make sure you give your image element a width and a height (required if you’re using &lt;a href="https://nextjs.org/docs/api-reference/next/image" rel="noopener noreferrer"&gt;next/image&lt;/a&gt;). For layout shifts caused by fonts, make sure to preload your custom fonts and find a fallback font that matches the custom font in size.&lt;/p&gt;

&lt;p&gt;Layout shifts are measured in Google Lighthouse via the CLS (Cumulative Layout Shift) metric.&lt;/p&gt;

&lt;h4&gt;
  
  
  Animations
&lt;/h4&gt;

&lt;p&gt;Another way to keep the user in the active state is by making sure your animations are smooth. If your animations don’t feel natural and don’t move how the user might expect (for instance a slight lag), this can cause the experience to feel slow. If the animations start feeling “laggy”, it can cause the user to transition to the passive state.&lt;/p&gt;

&lt;p&gt;So, make sure your animations aren’t too complex for the user and that they feel natural. A good way to provide a smooth experience is to &lt;a href="https://allenpike.com/2011/providing-joy-at-60-fps" rel="noopener noreferrer"&gt;render your animations at 60 fps&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  The :active selector
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbdsnpkipnn1v1p8c0oqi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbdsnpkipnn1v1p8c0oqi.png" alt="Active button state"&gt;&lt;/a&gt; &lt;em&gt;Image from &lt;a href="https://xd.adobe.com/ideas/process/ui-design/designing-interactive-buttons-states/" rel="noopener noreferrer"&gt;Adobe.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The secret to keeping the user in the active state is to provide immediate feedback. The user likes to know, that something is happening at the other end when they take an action.&lt;/p&gt;

&lt;p&gt;This immediacy keeps the users from going into the passive state.&lt;/p&gt;

&lt;p&gt;A super easy way to implement this in your UI is to add an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:active" rel="noopener noreferrer"&gt;:active selector&lt;/a&gt; to all your button and link elements.&lt;/p&gt;

&lt;p&gt;The active pseudo-class represents that an element has been activated by the user. Changing the background color of the button (or shadow, border color, etc.), when the user clicks (activating the :active pseudo-class), provides the user with feedback, that something is happening.&lt;/p&gt;

&lt;h4&gt;
  
  
  Preloading
&lt;/h4&gt;

&lt;p&gt;As the word might suggest, preloading means that we load something in advance. There are two ways we can think about preloading. One way is that we start loading something which we will need later on. Another way is to show something before it’s done loading. Let’s look at each one.&lt;/p&gt;

&lt;p&gt;In the first case, loading something before we need it, we anticipate what the user will do next. Instagram for instance does this by beginning to upload your image as soon as you select it. They anticipate you will publish your post, so they do the heavy work before you hit the post button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovkqsdbbgh55uat37f5u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovkqsdbbgh55uat37f5u.jpg" alt="Posting to Instagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can do a similar thing on our website by preloading pages before users navigate to them.&lt;/p&gt;

&lt;p&gt;Frameworks like Next.js have this feature built into their router. In Next.js, when using &lt;a href="https://nextjs.org/docs/api-reference/next/link" rel="noopener noreferrer"&gt;next/link&lt;/a&gt;, you can add the “prefetch”-prop. By adding this, any link that is in the viewport (initially or through scroll) will be preloaded.&lt;/p&gt;

&lt;p&gt;In the second case, we start showing something before it’s completely done loading. We see this all the time on various streaming platforms. If you open a YouTube video, it will start playing, even though it hasn’t downloaded the whole video. Instead, it will estimate how fast you can stream it and wait for that portion of the video to be loaded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3f189xros5n3djcmjac.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3f189xros5n3djcmjac.jpg" alt="Youtube showing video before done loading completely"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the same, as when we chose to load above-the-fold content first on our website. We want the user to be able to interact with the page right away, without having to load the entire page. A way we achieve this is by lazy loading images and components, as well as deferring non-essential scripts.&lt;/p&gt;

&lt;p&gt;Okay, now let’s look at how we can make the passive state feel faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making the passive state feel faster
&lt;/h3&gt;

&lt;p&gt;All right then, it’s time to manipulate time. Don’t worry, we’re not going to create any time paradoxes – we wouldn’t want to unravel the very fabric of the space-time continuum and destroy the entire universe (&lt;em&gt;Great Scott!&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Instead, we will use smoke and mirrors to make the passive state feel much faster than it really is. Let’s first look at a real-world example.&lt;/p&gt;

&lt;p&gt;Back in the early industrial age, buildings started growing taller and taller, which meant more people started using elevators. Back then elevators were quite slow, so naturally, people did what people do best – they began complaining about it.&lt;/p&gt;

&lt;p&gt;As a result, elevator companies started tackling this problem by designing elevators that were faster and safer. Unfortunately, at the time this was a very expensive thing to do.&lt;/p&gt;

&lt;p&gt;However, in another elevator company, one engineer had a different take on the problem. Speaking like a true (software) engineer he blamed the user, as he said:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“I think our elevator speeds are just fine, people are crazy.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This guy was basically a real-life Principal Skinner!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpbfn5nefzyfia2shm1r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpbfn5nefzyfia2shm1r.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;His theory was, that the problem wasn’t about the elevators' speed (objective), but rather that the users thought it was slow (subjective).&lt;/p&gt;

&lt;p&gt;So, while the other companies went off trying to optimize their motor and pulley design, this company tackled the problem differently – they wanted to solve why the user thought it was slow.&lt;/p&gt;

&lt;p&gt;Turns out that being trapped inside a metal box suspended many meters up in the air, held up only by a metal wire, isn’t exactly fun. The time seemed much longer when you had nothing to do but stare at the wall, trying to cope with your fear of being trapped.&lt;/p&gt;

&lt;p&gt;So how did they solve this problem? They installed mirrors.&lt;/p&gt;

&lt;p&gt;Yes, mirrors. Seems silly, but that was really all it took. Suddenly the users perceived the elevator rides as much faster than before.&lt;/p&gt;

&lt;p&gt;Now the users could look at themselves, check if their hair and makeup were okay, and become distracted by their own reflection. As an added benefit, it helped people suffering from claustrophobia since the space now seemed bigger than it really was.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9ml0fayfofltt0t9f65.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9ml0fayfofltt0t9f65.jpg" alt="Photo of an old elevator with a mirror"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And by that, we conclude today’s history lesson. Let’s look at what you can do to make the passive state feel faster.&lt;/p&gt;

&lt;p&gt;If we can’t keep the users in the active state, we have to utilize loaders in the best way possible.&lt;/p&gt;

&lt;h4&gt;
  
  
  Experiment with the design of the loader
&lt;/h4&gt;

&lt;p&gt;There are many ways to design a loading animation – just look at &lt;a href="https://cssloaders.github.io/" rel="noopener noreferrer"&gt;cssloaders.github.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Probably the most famous loading animation is the spinner. However, even a spinner can be designed in multiple ways.&lt;/p&gt;

&lt;p&gt;If you look at the different variations, you will probably realize that you interpret some as slow, whereas other seems fast.&lt;/p&gt;

&lt;p&gt;As we learned earlier, how an animation is designed can have a big impact on the user’s sense of speed.&lt;/p&gt;

&lt;p&gt;If you want to be devious like Facebook, you can even try to shift the blame of a slow site/app to the user’s own operating system.&lt;/p&gt;

&lt;p&gt;Doing an A/B test, they found out that by mimicking the iOS loading animation, the user started to blame iOS for the slowness instead of Facebook. Oh Zuck, you sneaky Lizard-man.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5034eizqwsz9nsug72h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5034eizqwsz9nsug72h.png" alt="Tweet showing Facebooks A/B test of loaders"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Use Skeleton loaders
&lt;/h4&gt;

&lt;p&gt;Skeleton loaders, even though they might sound &lt;a href="https://www.youtube.com/watch?v=sVjk5nrb_lI" rel="noopener noreferrer"&gt;spooky and scary&lt;/a&gt;, are a fantastic way to make your website seem faster.&lt;/p&gt;

&lt;p&gt;A skeleton loader is an animation placeholder that mimics the structure and looks of the content being loaded. They often have a gray color along with a pulsing or waving animation.&lt;/p&gt;

&lt;p&gt;It gives the user an idea of how the content is going to look and feel.&lt;/p&gt;

&lt;p&gt;You’ll see skeleton loaders implemented on big sites like Facebook, LinkedIn, Medium, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe87l7juentjeo7zg5cx2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe87l7juentjeo7zg5cx2.gif" alt="Example of skeleton loader"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example of Skeleton loader from &lt;a href="https://www.npmjs.com/package/react-native-skeleton-content" rel="noopener noreferrer"&gt;React Native Skeleton Content&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are plenty of packages available for frameworks/libraries like React, Vue, etc., and if you’re using a UI framework, it might be built in, as &lt;a href="https://chakra-ui.com/docs/components/skeleton" rel="noopener noreferrer"&gt;Chakra UI&lt;/a&gt; has.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tell the users what’s going on
&lt;/h4&gt;

&lt;p&gt;If your website or app has a multi-step process that takes time, it can be beneficial to tell the user what’s going on, instead of simply saying “loading”.&lt;/p&gt;

&lt;p&gt;At Enterspeed, when bulk deploying schemas, we have implemented a loader that tells you if it’s validating or deploying schema, as well as how many schemas are left. Often you won’t get to see this, because it goes fast, but just in case you have a slow connection, you can see what’s going on.&lt;/p&gt;

&lt;p&gt;Moreover, if the process takes more than 4 seconds, you should switch the spinner out with a progress bar, so the user can see the progress being made.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvlnm7uwuxo9f0z0vtqv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvlnm7uwuxo9f0z0vtqv.png" alt="Example og progress bar used in Windows"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image from &lt;a href="https://learn.microsoft.com/en-us/windows/win32/uxguide/progress-bars" rel="noopener noreferrer"&gt;Microsoft.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Distract the user in fun ways
&lt;/h4&gt;

&lt;p&gt;For processes that take a very long time, you can try to find a fun way to distract the user. A real-world example is the &lt;a href="https://www.boredpanda.com/cross-walk-pong-game-streetpong-actiwait-germany/" rel="noopener noreferrer"&gt;crosswalk in Germany with a built-in pong game&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To distract the user while they wait, you can show them a random fact (e.g., a random cat fact via the &lt;a href="https://catfact.ninja/" rel="noopener noreferrer"&gt;Cat Fact API&lt;/a&gt;), or maybe something related to your industry.&lt;/p&gt;

&lt;p&gt;For instance, in a performance monitoring tool I helped build, we implemented a random fact about Conversion Rate Optimization, which changed every 10 seconds, while the user waited for their report to be generated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; also does a great job distracting the user, while you wait for your site to deploy. They give you the option to play a game while you wait, and even show you the deployment process meanwhile – how cool!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsj7frw8xhd5ylfyrwpd2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsj7frw8xhd5ylfyrwpd2.png" alt="Netlify game when deploying a site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we measure perceived performance?
&lt;/h2&gt;

&lt;p&gt;It’s always quite difficult to measure and quantify a subjective thing. The same goes for perceived performance.&lt;/p&gt;

&lt;p&gt;We can interview our users and ask them if a site felt slow or fast, using feedback plugins, or by performing user tests. However, this can be difficult to do for smaller websites&lt;/p&gt;

&lt;p&gt;Luckily, we also have metrics available that we can use as indicators for perceived performance.&lt;/p&gt;

&lt;p&gt;I began the article by saying &lt;em&gt;“We are so enthralled in those goddamn Lighthouse scores, that we risk forgetting about the user.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But truth be told, Lighthouse has done a lot to make sure that a website is perceived as fast, rather than just loads fast.&lt;/p&gt;

&lt;p&gt;Back in the day, we used to simply measure load time and page size – these were the two metrics we were focusing on. That was until one day Google changed the way we thought about performance by introducing Google Lighthouse (…and of course by making it a ranking factor, which we couldn’t ignore).&lt;/p&gt;

&lt;p&gt;The metrics we find in the Google Lighthouse performance report are a way to quantify and measure user experience, here including the perceived performance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgygh1ylhz0h4mevrwpmf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgygh1ylhz0h4mevrwpmf.png" alt="Google Lighthouse example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some of the metrics you can use as indicators are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.dev/first-contentful-paint/" rel="noopener noreferrer"&gt;First Contentful Paint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://web.dev/fid/" rel="noopener noreferrer"&gt;First Input Delay&lt;/a&gt; (can't be measured synthetically)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/first-meaningful-paint/" rel="noopener noreferrer"&gt;First Meaningful Paint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/lcp/" rel="noopener noreferrer"&gt;Largest Contentful Paint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/cls/" rel="noopener noreferrer"&gt;Cumulative Layout Shift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/speed-index/" rel="noopener noreferrer"&gt;Speed Index&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/interactive/" rel="noopener noreferrer"&gt;Time To Interactive&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can run a Lighthouse test via Chrome DevTools or via &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;PageSpeed Insights&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just remember, these are only indicators and not a metric on how fast your site “feels”. For this, you need to ask the users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The way humans perceive time doesn’t necessarily match the time we see on a clock. The way we perceive time depends on the activity we’re doing.&lt;/p&gt;

&lt;p&gt;Activities can be broken into two states – the active state and the passive state.&lt;/p&gt;

&lt;p&gt;In the active state, we have a high mental activity, whereas in the passive state our mental activity is low. In the passive state, we tend to overestimate the time passed by an average of 36%.&lt;/p&gt;

&lt;p&gt;We’re constantly switching between the active and the passive state – even when browsing the web. When we wait for a page to load, we risk moving into the passive state.&lt;/p&gt;

&lt;p&gt;You can improve your website’s perceived performance by avoiding users switching to the passive state. If they do end in the passive state, you can make tweaks to make the passive state feel faster.&lt;/p&gt;

&lt;p&gt;It’s difficult to measure and quantity perceived performance, instead surveys and user tests can be used. The metrics in Google Lighthouse can be helpful indicators for measuring perceived performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledgment
&lt;/h2&gt;

&lt;p&gt;A huge shout-out to &lt;a href="http://eli.wtf/" rel="noopener noreferrer"&gt;Eli Fitch&lt;/a&gt; and his great video about “&lt;a href="https://www.youtube.com/watch?v=8oWpsNSOqOw" rel="noopener noreferrer"&gt;Perceived performance: The only kind that really matters&lt;/a&gt;”, which helped form this article!&lt;/p&gt;

&lt;p&gt;The same goes for &lt;a href="https://www.linkedin.com/in/raelenemorey/" rel="noopener noreferrer"&gt;Raelene Morey&lt;/a&gt;’s fantastic article on “&lt;a href="https://wp-rocket.me/blog/perceived-performance-need-optimize/" rel="noopener noreferrer"&gt;What is Perceived Performance and Why You Need to Optimize It&lt;/a&gt;”, and &lt;a href="https://lukejones.me/" rel="noopener noreferrer"&gt;Luke Jones&lt;/a&gt;’s awesome article on “&lt;a href="https://marvelapp.com/blog/a-designers-guide-to-perceived-performance/" rel="noopener noreferrer"&gt;A Designer’s Guide to Perceived Performance&lt;/a&gt;”.&lt;/p&gt;

&lt;p&gt;Y’all rock 🤘&lt;/p&gt;

&lt;p&gt;Examples, as well as tips and tricks, have been shamelessly stolen from these great resources.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Making a fast website is SUPER EASY 😏</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Mon, 19 Sep 2022 07:26:18 +0000</pubDate>
      <link>https://forem.com/enterspeed/making-a-fast-website-is-super-easy-9lb</link>
      <guid>https://forem.com/enterspeed/making-a-fast-website-is-super-easy-9lb</guid>
      <description>&lt;p&gt;That's right, I said it. Making a fast website is super easy, barely an inconvenience.&lt;/p&gt;

&lt;p&gt;I've built a website that gets a perfect score in Google Lighthouse and that can be deployed right to the edge.&lt;/p&gt;

&lt;p&gt;It's built completely in a high-performant, battle-tested language that will last for ages.&lt;/p&gt;

&lt;p&gt;Want to see for yourself how fast it really is? You can check it out right here: &lt;a href="https://enterspeed-hw.netlify.app/" rel="noopener noreferrer"&gt;https://enterspeed-hw.netlify.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Did you see how fast it was?&lt;/p&gt;

&lt;p&gt;Okay, I’ll admit, this was a cheap shot. Some of you might sit with a bitter feeling in your mouth, feeling cheated about this almost clickbaity article. But hang on, there’s a point to the madness.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs88e4mxevoi5s82v3mwc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs88e4mxevoi5s82v3mwc.jpg" alt="My website scores 100/100 in Google Lighthouse"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The point is anyone can make a fast website. Making a website that looks good and has the functionality you want while being high-performant is where the challenge lies.&lt;/p&gt;

&lt;p&gt;We have properly all stumbled on a post on LinkedIn where someone is bragging about their website scoring 100/100 in Google Lighthouse.&lt;/p&gt;

&lt;p&gt;A score like that is also quite an achievement and should indeed be celebrated. However, I think many of us also have seen high performant websites which, how should I put it… Looks like shit. Excuse my language.&lt;/p&gt;

&lt;p&gt;You know the type. Late 2000’s-look, barely any images, maybe a few sorry-looking icons, and just text as far as the eye can see.&lt;/p&gt;

&lt;p&gt;Don’t get me wrong – for some websites, e.g., blogs, this makes perfect sense. However, for a website whose goal is to convert customers by selling goods or services, the website should also be attractive to look at.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvyc5e7e39ww048zbb82u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvyc5e7e39ww048zbb82u.jpg" alt="Your website is balogna!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whether a website looks good or not is of course, unlike a performance score, highly subjective. As the saying goes: “The beauty is in the eye of the beholder”.&lt;/p&gt;

&lt;p&gt;The visual aspect of a site can be costly. Each image, video, custom font, animation library, etc. is an extra request for the user and additional kilobytes which must be transferred to the client. &lt;/p&gt;

&lt;p&gt;Another side of the coin is the website’s functionality. My beautiful “Hello world”-example above, written in pure HTML, properly doesn’t scale that well to a full-blown company site.&lt;/p&gt;

&lt;p&gt;Then we introduce some kind of framework that increases the bundle size and the response time. How much overhead this framework adds can also vary depending on which you choose, as we found out in our previous article: &lt;a href="https://www.enterspeed.com/blog/we-measured-the-ssr-performance-of-6-js-frameworks-heres-what-we-found/" rel="noopener noreferrer"&gt;We measured the SSR performance of 6 JS frameworks - here's what we found&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Okay, now we got the foundation in place – now for the content. Marketing comes up with new ideas almost as often as a new JS framework is introduced. They don’t want to bother you all the time, and just as important, you don’t want to be bothered every time there’s a tiny change to be made.&lt;/p&gt;

&lt;p&gt;So now we go and implement a CMS. Depending on how we implement it, this can also introduce additional overhead in the performance. If we render it using SSG, it won’t have any meaningful performance changes. However, if we use SSR (or for some reason you choose CSR – please don’t), then we start seeing some extra requests enter the picture.&lt;/p&gt;

&lt;p&gt;Okay, now we have given marketing what they want – a way to edit and publish content, they’re properly satisfied now, right? Oh, my sweet summer child, you have clearly never worked with a marketing department.&lt;/p&gt;

&lt;p&gt;Remember the scene in Lord of the Rings where the thousands of orcs attack Minas Tirith? Picture this scene, Minas Tirith is your website, and the thousands of orcs are the shitton of tracking scripts that marketing wants to implement. In this case, you're Gandalf, trying your best to defend Minas Tirith.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmn0l8k6nyrhcrzckay7g.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmn0l8k6nyrhcrzckay7g.jpg" alt="Stop, stop! He's already dead"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As fun as it is to blame marketing for everything, the truth of the matter is they of course play a vital role.&lt;/p&gt;

&lt;p&gt;If a website doesn’t get any visitors, it doesn’t matter how fast it is. Moreover, if these visitors don’t convert (buy, sign up, book a call, download material, etc.) it also doesn’t matter.&lt;/p&gt;

&lt;p&gt;So, should we then just give the marketing department free rein?&lt;/p&gt;

&lt;p&gt;Eh…&lt;/p&gt;

&lt;p&gt;Just like a kid with a bowl of sugar, we should still keep a watchful eye and not allow them to turn their bowl of oatmeal with sugar into a bowl of sugar with oatmeal.&lt;/p&gt;

&lt;p&gt;As many &lt;a href="https://wpostats.com/" rel="noopener noreferrer"&gt;studies&lt;/a&gt; have shown, there’s a direct correlation between how fast a website is and how well it converts. &lt;/p&gt;

&lt;p&gt;Also in terms of search engine optimization, &lt;a href="https://web.dev/vitals/" rel="noopener noreferrer"&gt;core web vitals&lt;/a&gt; became an official &lt;a href="https://developers.google.com/search/blog/2020/11/timing-for-page-experience" rel="noopener noreferrer"&gt;Google ranking factor in 2021&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Therefore, we must balance the visual elements, the website’s functionality, and the website’s performance so they all can co-exist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqmvrsa347f3t6vynbw0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqmvrsa347f3t6vynbw0.jpeg" alt="Perfectly balanced... As all things should be"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I know what you’re thinking: “Enough fluff talk, this isn’t marketing, what can I actually do”? (Sorry, having switched from marketing to development myself, I simply can’t help it).&lt;/p&gt;

&lt;p&gt;And don’t worry, in the second part of this article we will look at some actionable tips.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making your website fast, functional, and visually pleasing 👌
&lt;/h2&gt;

&lt;p&gt;Let’s try to hit that magical trifactor that makes everyone happy. &lt;/p&gt;

&lt;h3&gt;
  
  
  Images
&lt;/h3&gt;

&lt;p&gt;Let’s start with one of the big performance sinners – images. Images take up a huge part of the website's total size.&lt;/p&gt;

&lt;p&gt;As of August 2022, images made up on average 45% on desktop and 44% on mobile of a page’s total weight. The number of image requests made up 32% on desktop and 30% on mobile of a page's total requests (Source: &lt;a href="https://httparchive.org/reports/page-weight" rel="noopener noreferrer"&gt;HTTP Archive&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Optimizing the images can therefore result in big and easy wins. I’ve previously made an article about this exact subject: &lt;a href="https://dev.to/kaspera/how-to-optimize-your-images-for-performance-24pn"&gt;How to optimize your images for performance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will quickly summarize the points from the article and move on to a concrete example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Choose the right image format (JPEG, PNG, GIF, SVG, WebP)&lt;/strong&gt;&lt;br&gt;
Each image format has its purpose. For instance, PNGs can be great for icons and smaller images, but the file size can quickly become pretty huge for larger images.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Be careful with using animated GIFs&lt;/strong&gt;&lt;br&gt;
GIFS can be fantastic for small “videos”, for instance in tutorials, but they come with a cost. File sizes can be enormous, so consider using a video instead. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Always try to compress images&lt;/strong&gt;&lt;br&gt;
It always amazes me how many kilobytes can be saved by running images through a compressing tool. I once managed to compress an SVG file with around 97% – absolutely bonkers.&lt;/p&gt;

&lt;p&gt;One of my all-time favorite tools to use is &lt;a href="https://tinypng.com/" rel="noopener noreferrer"&gt;TinyPNG&lt;/a&gt; (aka. TinyJPG). Their UI is beautiful, dead simple and more importantly – they have some insane compression results. They allow compression of PNGs, JPEGs, and WebP-files.&lt;/p&gt;

&lt;p&gt;Remember to always check the quality after. One thing many compression tools don’t do well is trying to compress gradient colors – the result can be quite janky.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjq201eqjn7dl5lgzaxpv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjq201eqjn7dl5lgzaxpv.jpg" alt="Y'all got anymore of them pixels?"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;u&gt;Online tools I can recommend:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://tinypng.com/" rel="noopener noreferrer"&gt;TinyPNG&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://squoosh.app/" rel="noopener noreferrer"&gt;Squoosh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vecta.io/nano" rel="noopener noreferrer"&gt;Vecta.io&lt;/a&gt; (Only SVG)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kraken.io/web-interface" rel="noopener noreferrer"&gt;Kraken.io&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://compressor.io/" rel="noopener noreferrer"&gt;Compressor.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jakearchibald.github.io/svgomg/" rel="noopener noreferrer"&gt;SVGOMG&lt;/a&gt; (Suggested by &lt;a class="mentioned-user" href="https://dev.to/cicirello"&gt;@cicirello&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Tip: &lt;a href="https://squoosh.app/" rel="noopener noreferrer"&gt;Squoosh&lt;/a&gt; lets you adjust the quality (compression rate) and see the results live. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Scale down your images&lt;/strong&gt;&lt;br&gt;
There’s absolutely no reason in serving a 3000px width image. Moreover, there’s no reason in serving a 500px width image if you only render it in 300px.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Serve responsive images&lt;/strong&gt;&lt;br&gt;
Instead of using a single image for both desktop and mobile, consider serving 3 – 5 different sizes of the image depending on the viewport.&lt;/p&gt;

&lt;p&gt;Check out Mozilla's article on responsive images here: &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images" rel="noopener noreferrer"&gt;Responsive images on MDN&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Lazy load images&lt;/strong&gt;&lt;br&gt;
Never load images until it’s necessary. Lazy loading images helps reduce the number of initial requests and page size resulting in a much faster site.&lt;/p&gt;

&lt;p&gt;Nowadays we’re blessed with a built-in lazy loading attribute supported by all major browsers: &lt;a href="https://web.dev/browser-level-image-lazy-loading/" rel="noopener noreferrer"&gt;Browser-level image lazy-loading for the web&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;⚠Warning: Never lazy-load your LCP element!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s look at an example of how we could optimize one of the big image performance sinners – the hero image.&lt;/p&gt;

&lt;h4&gt;
  
  
  Optimizing the hero image
&lt;/h4&gt;

&lt;p&gt;Almost all modern websites use some kind of image in their hero section (the area right under the logo and navigation – often mostly used on the homepage).&lt;/p&gt;

&lt;p&gt;The hero section may be the most important part of your website since it is responsible for quickly telling the visitor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What you offer (your product or service)&lt;/li&gt;
&lt;li&gt;What your USPs are (Unique Selling Proposition)&lt;/li&gt;
&lt;li&gt;Communicating trust (why the visitor should trust your site)&lt;/li&gt;
&lt;li&gt;The main call to action (Sign up, book call, buy now, etc).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are a thousand ways to design a hero section, but for now, we will split it into two categories:&lt;/p&gt;

&lt;p&gt;A) Full-size hero image (background)&lt;br&gt;
B) Partial-size hero image (often placed in the right section of the screen and taking up somewhere between 1/2 to 1/3 of the width).&lt;/p&gt;

&lt;p&gt;The reason why the hero image is interesting from a performance perspective is that it often will be the LCP element (Largest Contentful Paint), which in the Lighthouse performance scoring is the second largest weighted metric with a 25% weight, only outdone by TBT (Total Blocking Time) with a 30% weight.&lt;/p&gt;

&lt;p&gt;Moreover, the hero image tends to also be one of the largest when it comes to file size (especially for full-size hero images).&lt;/p&gt;

&lt;p&gt;For the partial-size hero images, depending on their size, they may not be the LCP – this can very well be the heading text – especially on mobile where designers tend to shrink the image to make room for the text and buttons.&lt;/p&gt;

&lt;p&gt;So, let’s optimize our hero image. For this example, our hero image will be a full-size hero image.&lt;/p&gt;

&lt;p&gt;The first thing we should do is make sure we don’t lazy load our hero image (some might choose to add the lazy=true attribute to all images) since this will negatively impact our score.&lt;/p&gt;

&lt;p&gt;The next thing we should do is preload it. Setting your hero image to preload will enable the browser’s preload scanner to load the element early in the page lifecycle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuvc3iwwcslzpw0e27tuo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuvc3iwwcslzpw0e27tuo.png" alt="Preloading LCP"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source: &lt;a href="https://web.dev/preload-scanner/" rel="noopener noreferrer"&gt;https://web.dev/preload-scanner/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Be aware, that if you use responsive images (different images depending on viewport size), then you need to specify the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-imagesrcset" rel="noopener noreferrer"&gt;imagesrcset attribute&lt;/a&gt; on the  element.&lt;/p&gt;

&lt;p&gt;Thing brings us to the next tip. Use responsive image sizes. &lt;/p&gt;

&lt;p&gt;We’re using a full-size background image with a width of 1920px. Loading a 1920px width image on for instance an iPhone SE which has a 375px width viewport would be absolutely insane.&lt;/p&gt;

&lt;p&gt;By shrinking from 1920px width to 375px width you can reduce your image size to just a 1/10 of the original size. Talk about saving data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu04i6necmp0lj03x8ciy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu04i6necmp0lj03x8ciy.png" alt="Responsive images"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I know what you’re thinking. Making all these variations sounds like a lot of work. I feel you – I’m lazy too, but luckily there are awesome tools available.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.responsivebreakpoints.com/" rel="noopener noreferrer"&gt;Responsive Image Breakpoints Generator&lt;/a&gt; automatically creates the number of images you wish based on your resolution range. Moreover, it even generates the HTML img tag for you.&lt;/p&gt;

&lt;p&gt;The tool is created by &lt;a href="https://cloudinary.com/" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt; (another fantastic service), which also has an API available to upload your images to the cloud and automatically generate breakpoints programmatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb0drwbmalyns3x7vo93.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb0drwbmalyns3x7vo93.png" alt="Cloudinary API"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Long live the lazy 🙌&lt;/p&gt;

&lt;p&gt;Now let’s look at optimizing the main image size. In this example, I will be using Photoshop, but there are plenty of free tools available for doing the same.&lt;/p&gt;

&lt;p&gt;We want to build this fantastic hero for our website:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aw8r6h2alr72kcsrs99.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aw8r6h2alr72kcsrs99.jpg" alt="Hero example - "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have found this &lt;a href="https://unsplash.com/photos/wXuzS9xR49M" rel="noopener noreferrer"&gt;beautiful image&lt;/a&gt; we want to use (Shout out to the photographer &lt;a href="https://unsplash.com/@cenali" rel="noopener noreferrer"&gt;Matheus Cenali&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;Our largest image size will be 1920x1080. Unfortunately, the image is a bit too big with a resolution of 1920x1440. Time for some cropping.&lt;/p&gt;

&lt;p&gt;I select the crop tool in Photoshop and input the resolution I want.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqacbwo9d2t5hspge6eb3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqacbwo9d2t5hspge6eb3.jpg" alt="Cropping in Photoshop"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Sweet, no redundant pixels here.&lt;/p&gt;

&lt;p&gt;Next, I click File &amp;gt; Export &amp;gt; Exports as. Here I can choose image format – and for JPG select image quality while inspecting quality and file size.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F602r1ef90jyksa3rb3y2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F602r1ef90jyksa3rb3y2.jpg" alt="Photoshop "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don’t want to use their predefined Quality scale (Good, Very Good, Excellent, etc.) you can use their legacy export tool, which lets you select a quality from 0 – 100, similar to &lt;a href="https://squoosh.app/" rel="noopener noreferrer"&gt;Squoosh&lt;/a&gt;. You’ll find the under File &amp;gt; Export &amp;gt; Save for Web (Legacy).&lt;/p&gt;

&lt;p&gt;I usually end up selecting the “Good”-quality, since it often hits the sweet spot between quality and file size.&lt;/p&gt;

&lt;p&gt;Now, we could be satisfied with the 188,9 KB file size, which also isn’t that bad for a large background image, but let me show you another cool trick.&lt;/p&gt;

&lt;p&gt;As you could see from our hero example above, we want to have a black overlay over the image to better view the text and make the CTA’s stand out.&lt;/p&gt;

&lt;p&gt;Aha! Then we just add some CSS to create a black overlay with some opacity, right? Yes, we could do that, but check this out.&lt;/p&gt;

&lt;p&gt;Now I take the same Apple image as above, open it in Photoshop and right click the layer, and select Blending options. Click color overlay, select a black color, and chose an opacity (in this example we use 60%).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzell449xeh9eokzw5cg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzell449xeh9eokzw5cg.jpg" alt="Setting black overlay in Photoshop"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s try to export it again in the “Good”-quality as before, which resulted in a 188,9 KB file size.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60k5bc4tzfh9ckc4e5t3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60k5bc4tzfh9ckc4e5t3.jpg" alt="Photoshop "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our file size has shrunk to 99,5 KB! That’s almost a 50% reduction. Fewer image details result in a smaller image size.&lt;/p&gt;

&lt;p&gt;The same would have been the case if we have chosen to blur our image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg2gkx6nhmsgfdcx8g6b1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg2gkx6nhmsgfdcx8g6b1.jpg" alt="Photoshop "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have a 78,7 KB file size. I wonder what happens if we add a black overlay to our blurred image?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ckksqktjqhckl3wkdgn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ckksqktjqhckl3wkdgn.jpg" alt="Photoshop "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will end up with an impressive file size of 49,7 KB. This gives you an idea of how many kilobytes can be saved when playing around with effects in Photoshop.&lt;/p&gt;

&lt;p&gt;Okay, enough about images – we could go on forever optimizing the various assets. Let’s take a look at fonts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fonts
&lt;/h3&gt;

&lt;p&gt;If there are one thing designers absolutely love, it’s fonts. So, it was no wonder that the entire web design community almost went bananas when Google recently launched their &lt;a href="https://material.io/blog/color-fonts-are-here" rel="noopener noreferrer"&gt;Color Fonts on Google Fonts&lt;/a&gt;, which to be honest is a pretty cool feature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8thma8mxcwi1e9pyfuzw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8thma8mxcwi1e9pyfuzw.jpg" alt="Color Fonts on Google Fonts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, let’s talk about fonts and how to optimize them. We’re not going to cover every aspect in this article since, believe it or not, actually is quite a big subject. &lt;/p&gt;

&lt;p&gt;First, why are fonts/typography important for web design? Let’s take a look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Readability&lt;/strong&gt;&lt;br&gt;
Remember the old days of the web where it seemed like a universal law that  your site should have an 11px body font? (&lt;a href="https://youtu.be/mBNom46c4tQ?t=28s" rel="noopener noreferrer"&gt;What is this? A website for ants?&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Luckily the days of squinting our eyes together to browse the web are over. Now most, but definitely not all, websites run a decent font size.&lt;/p&gt;

&lt;p&gt;The choice of font is of course also important. The &lt;a href="https://www.boia.org/blog/best-fonts-to-use-for-website-accessibility" rel="noopener noreferrer"&gt;Bureau of Internet Accessibility&lt;/a&gt; has recommended the following fonts for accessibility: Times New Roman, Verdana, Arial, Tahoma, Helvetica, and Calibri.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Legibility&lt;/strong&gt;&lt;br&gt;
Related to readability, legibility is the measure of how distinguishable individual characters and words are to the eye of the reader; readability is the measure of how easy it is to read the text overall (Source: &lt;a href="https://fonts.google.com/knowledge/glossary/legibility_readability" rel="noopener noreferrer"&gt;Legibility &amp;amp; readability&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Color and contrast&lt;/strong&gt;&lt;br&gt;
Not everyone perceives colors the same way.  Some color combinations can be very difficult or impossible for some people to read.&lt;/p&gt;

&lt;p&gt;Usually, this is because of poor color contrast (the foreground and the background color being similar).&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://webaim.org/standards/wcag/" rel="noopener noreferrer"&gt;WebAIM guidelines&lt;/a&gt; recommend an AA (minimum) contrast ratio of 4.5:1 for all text. However, large text (120 – 150% larger than the default body) is an exception, here the ratio can go down to 3:1 (Source: &lt;a href="https://web.dev/color-and-contrast-accessibility/" rel="noopener noreferrer"&gt;Color and contrast accessibility&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Branding&lt;/strong&gt;&lt;br&gt;
That’s right, you thought we were done with marketing, but just like the Undertaker, they sneak their way in when you least expect it. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjuitqd19hbulyffczaj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjuitqd19hbulyffczaj.jpg" alt="Marketing sneaking in the developer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fonts are a crucial part of the brand. They can evoke emotions and communicate what the core of the brand is to the consumers.&lt;/p&gt;

&lt;p&gt;Don’t believe me? Check out these &lt;a href="https://www.behance.net/gallery/97066199/LOGO-SWAP" rel="noopener noreferrer"&gt;fantastic logo swaps&lt;/a&gt; and prepare to feel uncomfortable.&lt;/p&gt;

&lt;p&gt;Choosing the right font combination for your website can be what makes or breaks a good design. Check out &lt;a href="https://www.canva.com/learn/the-ultimate-guide-to-font-pairing/" rel="noopener noreferrer"&gt;Canva's ultimate guide to font pairing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before moving on to how to optimize fonts for performance, we also need to talk about the two basic types of web fonts: Web-safe fonts and Web fonts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web-safe fonts&lt;/strong&gt;&lt;br&gt;
These are the fonts that are pre-installed on your device – also known as system fonts. Examples of these are Arial, Times New Roman, and Courier. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web fonts&lt;/strong&gt;&lt;br&gt;
These are fonts not pre-installed on your device and which must be downloaded by the browser before they can be displayed. These can either be self-hosted (on your own web server) or via a third-party host like Google Fonts, Adobe Typekit, etc.&lt;/p&gt;

&lt;p&gt;The attentive reader properly already knows which type we’re going to optimize – that’s right, web fonts. As nice as it could be to only use web-safe fonts in your design, it’s hard to make a website look good with just these. &lt;/p&gt;

&lt;p&gt;So, let’s see what we can do.&lt;/p&gt;

&lt;p&gt;The first thing you should do is to make sure you’re not downloading unnecessary font variations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kzr5ajdl9pgzmx3agve.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kzr5ajdl9pgzmx3agve.jpg" alt="Downloading unnecessary resources - that's a paddlin'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re using a theme, boilerplate, etc., it may come with a lot of unnecessary font variations.&lt;/p&gt;

&lt;p&gt;Take for instance the popular &lt;a href="https://fonts.google.com/specimen/Roboto" rel="noopener noreferrer"&gt;Roboto font&lt;/a&gt; – it comes in 12 different styles (6 different font weights with an italic and non-italic version of each). Each variation is extra kilobytes the user must download.&lt;/p&gt;

&lt;p&gt;Luckily, it’s easy to find out which styles you are using and which you are not. Afterward, you can change your link / @import to only download these variations.&lt;/p&gt;

&lt;p&gt;Next, let’s see how these fonts may impact our performance score. Web fonts can negatively impact core web vitals like Cumulative Layout Shift (CLS) and Largest Contentful Paint (LCP), and non-core web vitals like First Contentful Paint (FCP), which is also used to calculate the &lt;a href="https://web.dev/performance-scoring/" rel="noopener noreferrer"&gt;Lighthouse performance score&lt;/a&gt; with a 10% weight.&lt;/p&gt;

&lt;p&gt;So, how do we optimize all of these? We can’t.&lt;/p&gt;

&lt;p&gt;Wait, what? We can’t?&lt;/p&gt;

&lt;p&gt;No, optimizing fonts for CLS and LCP/FCP conflict with each other. It all has to do with how we use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display" rel="noopener noreferrer"&gt;font-display&lt;/a&gt; property.&lt;/p&gt;

&lt;p&gt;When loading custom fonts, each browser has its &lt;a href="https://web.dev/avoid-invisible-text/#option-#1-use-font-display" rel="noopener noreferrer"&gt;own behavior&lt;/a&gt;. Edge swaps it out with a system font until it’s ready, Chrome and Firefox will wait 3 seconds before swapping it out with a system font and Safari will simply hide it until the font is ready.&lt;/p&gt;

&lt;p&gt;We can modify this default behavior by using font-display.&lt;/p&gt;

&lt;p&gt;So first let’s try to optimize for LCP/FCP.&lt;/p&gt;

&lt;p&gt;The First Contentful Paint will often be your text which, as we learned previously, also can be your Largest Contentful Paint. The quicker these elements load the better.&lt;/p&gt;

&lt;p&gt;Therefore, the loading behavior of Chrome, Firefox, and Safari, can potentially harm these metrics. The same if you choose to manually set font-display to “block”, which gives the font face a short block period and an infinite swap period.&lt;/p&gt;

&lt;p&gt;We can avoid these “invisible fonts” by setting the font-display to “swap” making the block period extremely small. This will load a system font and “swap the font” when the custom font is ready.&lt;/p&gt;

&lt;p&gt;Pretty nifty, but do you know what can happen when you “swap content” during load? Your layout can shift, resulting in a poor Cumulative Layout Shift (CLS) score.&lt;/p&gt;

&lt;p&gt;So, unfortunately, there’s no solution that fixes all things. If your text is your LCP, it makes sense to use the swap method, since LCP and FCP have a total weight of 35% (25% + 10%) in the Lighthouse performance score.&lt;/p&gt;

&lt;p&gt;But there’s also a way we can reduce the amount of layout shift happening. We can make sure our &lt;a href="https://www.w3schools.com/css/css_font_fallbacks.asp" rel="noopener noreferrer"&gt;fallback font&lt;/a&gt; best matches our custom font. You can use the &lt;a href="https://meowni.ca/font-style-matcher/" rel="noopener noreferrer"&gt;Font style matcher tool&lt;/a&gt; for this. &lt;/p&gt;

&lt;p&gt;Now, let’s try to optimize how fast we get the actual font. We can use &lt;a href="https://web.dev/uses-rel-preconnect/" rel="noopener noreferrer"&gt;preconnect&lt;/a&gt; to establish early connections to important third-party origins, here our fonts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3rwpl2v0rmfj73aaq6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3rwpl2v0rmfj73aaq6g.png" alt="Preconnect Google Fonts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next thing we can do is to tell the browser to load our font early in the page’s lifecycle. We do this using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload" rel="noopener noreferrer"&gt;preload&lt;/a&gt; in the link element. &lt;/p&gt;

&lt;p&gt;That way our font is less likely to block the page’s render since it will load before the browsers' main rendering engine starts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7zt76wgg6azy26knhgr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7zt76wgg6azy26knhgr.png" alt="Preload Google Fonts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, if you choose to self-host your fonts, make sure you use a modern web font format – here WOFF 2.0. WOFF 2.0 offers up to 30% better compression than its predecessor WOFF.&lt;/p&gt;

&lt;h3&gt;
  
  
  Third-party scripts (Tracking scripts and other fun stuff)
&lt;/h3&gt;

&lt;p&gt;Now for something that’s even more frustrating than the Game of Thrones ending (I’m sorry Bran, but anyone has a better story than you) – third-party scripts.&lt;/p&gt;

&lt;p&gt;From Facebook to Google Analytics, to LinkedIn Insight Tag. The amount of third-party scripts almost seems endless.&lt;/p&gt;

&lt;p&gt;When a certain department, who shall remain nameless, then comes to you and ask you to also add the Facebook Page Plugin so “our customers easily can follow our company page”, you can almost feel your soul leave your body.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtwjkm4ws5xtlqetgmux.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtwjkm4ws5xtlqetgmux.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But don’t worry, there’s a way to overcome these obstacles. Remember what we said about balance?&lt;/p&gt;

&lt;p&gt;Let’s start with the easy one – a third-party script rendering content, like the &lt;a href="https://developers.facebook.com/docs/plugins/page-plugin/" rel="noopener noreferrer"&gt;Facebook Page Plugin&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Scripts which are responsible for rendering some kind of content should always be lazy-loaded, meaning the request is first made when you scroll to the content.&lt;/p&gt;

&lt;p&gt;The Facebook Page Plugin actually has this built in. Simply insert data-lazy=”true” and you’re good to go.&lt;/p&gt;

&lt;p&gt;Now for the more annoying part – all the tracking scripts.&lt;/p&gt;

&lt;p&gt;If your website only targets European visitors, you’re in luck. Due to the GDPR, and the ePrivacy Directive you can’t legally set a tracking cookie until the user has accepted it.&lt;/p&gt;

&lt;p&gt;This means your initial load time – and here your Core Web Vitals won’t be affected by these third-party scripts.&lt;/p&gt;

&lt;p&gt;Problem solved. Shut down your website for the rest of the world and move to Europe – don’t worry, we got beer 🍻&lt;/p&gt;

&lt;p&gt;No? Okay, let me show a trick then.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hold back your requests
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs2ocvsi5q73okhb2jx4t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs2ocvsi5q73okhb2jx4t.jpg" alt="Braveheart - "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First things first. You should of course only load third-party scripts which are needed on the particular page. This means, that if you have a cool animation that requires animate.css (not a script but a stylesheet but bear with me) that you use on your company page – then this shouldn’t be loaded on every other page.&lt;/p&gt;

&lt;p&gt;You can use a tool like &lt;a href="https://developers.google.com/tag-platform/tag-manager" rel="noopener noreferrer"&gt;Google Tag Manager&lt;/a&gt; to manage and orchestra all your scripts. This is exactly also what we are going to use for this nifty trick.&lt;/p&gt;

&lt;p&gt;Third-party scripts like Facebook Pixel, LinkedIn Insight Tag, or even your chat plugin aren’t strictly necessary to request right away. They can wait until the user has interacted with the site – or some time has passed.&lt;/p&gt;

&lt;p&gt;I don’t necessarily recommend doing this with Google Analytics, since you might risk losing valuable data.&lt;/p&gt;

&lt;p&gt;However, for all the nice-to-have stuff, they can wait their turn.&lt;/p&gt;

&lt;p&gt;So how do we do this?&lt;/p&gt;

&lt;p&gt;Inside Google Tag Manager you set up &lt;a href="https://support.google.com/tagmanager/topic/7679108" rel="noopener noreferrer"&gt;Triggers&lt;/a&gt; that tell the tag/script when to fire. These triggers can be conditional, meaning you also can chain them into an OR statement. &lt;/p&gt;

&lt;p&gt;So, for our non-critical scripts, we could set up a condition that looked for if:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user has clicked on any element (useful for navigation)&lt;/li&gt;
&lt;li&gt;Has scrolled 5% (indicated the user starts interacting with the site)&lt;/li&gt;
&lt;li&gt;5 seconds have passed (our initial load is done).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Be sure to test these things out and see if everything works. As someone who lives under &lt;a href="https://ec.europa.eu/commission/commissioners/2019-2024/vestager_en" rel="noopener noreferrer"&gt;Margrethe Vestager&lt;/a&gt;’s watchful eyes, I don’t use this technique myself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading content from your CMS
&lt;/h3&gt;

&lt;p&gt;That’s right, we’ve saved the best for last – serving the actual content.&lt;/p&gt;

&lt;p&gt;There are multiple ways to go about it and not a subject we can cover completely in this article. &lt;/p&gt;

&lt;p&gt;One way to make sure your content loads really fast is to use Static Site Generation (SSG) which renders your content at build time and serves it as (you guessed it) – static content.&lt;/p&gt;

&lt;p&gt;This is really performant, but not always that flexible. Another alternative is to use something like Next.js’ &lt;a href="https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration" rel="noopener noreferrer"&gt;Incremental Static Regeneration&lt;/a&gt; (ISR), which is a hybrid between Server Side Rendering (SSR) and Static Site Generation (SSG).&lt;/p&gt;

&lt;p&gt;Again, cool stuff – but not everyone can utilize these rendering strategies – many organizations have some dynamic content that needs to be rendered via SSR. &lt;/p&gt;

&lt;p&gt;These organizations are also typically not the type you can easily convince to move away from their old CMS and on to the latest and greatest headless CMS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft4kvba7s7drp4otaf656.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft4kvba7s7drp4otaf656.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fetching data from these CMS’ can be slow and risk creating a bottleneck. So, what do we do?&lt;/p&gt;

&lt;p&gt;We decouple it, baby.&lt;/p&gt;

&lt;p&gt;If you read my previous article, &lt;a href="https://www.enterspeed.com/blog/using-google-sheets-as-your-cms/" rel="noopener noreferrer"&gt;Using Google Sheets as your CMS&lt;/a&gt;, you might be familiar with the concept.&lt;/p&gt;

&lt;p&gt;Using a solution like our own product, &lt;a href="https://www.enterspeed.com/" rel="noopener noreferrer"&gt;Enterspeed&lt;/a&gt;, you can sync your data to a high-performant data store, and still keep the editor experience. Moreover, you’re able to also transform and combine multiple data sources into a single call – reducing the number of requests.&lt;/p&gt;

&lt;p&gt;Decoupling your CMS also brings other benefits. You’re able to scale down your server since it’s no longer going to take the high traffic load. &lt;/p&gt;

&lt;p&gt;If you need to do maintenance, you can even shut it off without affecting your site. &lt;/p&gt;

&lt;p&gt;Syncing your data to Enterspeed also makes it possible to migrate to another CMS – or a newer version of your current one. Check out how we &lt;a href="https://www.youtube.com/watch?v=8DOUJpbc6CM" rel="noopener noreferrer"&gt;migrated from a Umbraco V7 setup to an Umbraco V10 setup&lt;/a&gt; in no time.&lt;/p&gt;

&lt;p&gt;Not to toot our own horn, but that is pretty sweet 🙌&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;This article could go on forever (and it kind of feels like it already has), but we must stop at some point. We haven’t covered all the topics of performance optimization, since there’s enough content there to cover a full book.&lt;/p&gt;

&lt;p&gt;Some of the things we didn’t cover were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The importance of choosing a good web host (We ♥ &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/lazy-loading-video/" rel="noopener noreferrer"&gt;Lazy-loading videos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/reduce-javascript-payloads-with-tree-shaking/" rel="noopener noreferrer"&gt;Utilizing three shaking when possible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/reduce-network-payloads-using-text-compression/" rel="noopener noreferrer"&gt;Minifying and compressing your CSS and JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/content-delivery-networks/" rel="noopener noreferrer"&gt;Using a CDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/time-to-first-byte/" rel="noopener noreferrer"&gt;Reducing your TTFB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/remove-unused-code/" rel="noopener noreferrer"&gt;Removing unused code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/redirects/" rel="noopener noreferrer"&gt;Removing unnecessary redirects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/love-your-cache/" rel="noopener noreferrer"&gt;Utilizing caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/render-blocking-resources/" rel="noopener noreferrer"&gt;Eliminating render-blocking resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/reduce-javascript-payloads-with-code-splitting/" rel="noopener noreferrer"&gt;Using code splitting when possible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/defer-non-critical-css/" rel="noopener noreferrer"&gt;Deferring non-critical CSS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It’s not hard to build a superfast website. It can be hard to do this while making it look visually stunning and having the functionality all department wants.&lt;/p&gt;

&lt;p&gt;Luckily, we can solve many of these obstacles by doing some optimization tweaks and learning to achieve a balance between speed, functionality, and visual elements.&lt;/p&gt;

&lt;p&gt;Remember, Rome wasn’t built in a day. These things take time and comprise sometimes must be made. Talk to each stakeholder for the website and make sure they understand why there needs to be a synergy between all elements.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>We measured the SSR performance of 6 JS frameworks - here's what we found</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Mon, 05 Sep 2022 06:35:44 +0000</pubDate>
      <link>https://forem.com/enterspeed/we-measured-the-ssr-performance-of-6-js-frameworks-heres-what-we-found-1ck0</link>
      <guid>https://forem.com/enterspeed/we-measured-the-ssr-performance-of-6-js-frameworks-heres-what-we-found-1ck0</guid>
      <description>&lt;p&gt;To say that there are quite a few JS frameworks to choose from would properly be the understatement of the year.&lt;/p&gt;

&lt;p&gt;It seems like every few months a new, revolutionary framework enters the ecosystem with promises of unseen performance and/or ease of use.&lt;/p&gt;

&lt;p&gt;It's gotten to the point where we almost need a framework to choose our next framework (&lt;a href="https://knowyourmeme.com/memes/xzibit-yo-dawg" rel="noopener noreferrer"&gt;Yo Dawg&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Although, as nice as it would be for a developer to be able to take a vacation and come back without having to learn yet another framework, we also kind of dig it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyvkwhhb05p44oy6lx2om.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyvkwhhb05p44oy6lx2om.png" alt="Frontend developer looking at a new javascript framework"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;New frameworks often bring innovation to the JS ecosystem. This can be in how we build apps and/or how performant they are.&lt;/p&gt;

&lt;p&gt;These performance improvements help push the boundaries on what is possible - and as a result, also help inspire other frameworks to implement similar solutions.&lt;/p&gt;

&lt;p&gt;But now for the fun question - is there really that big of a performance difference across the different frameworks? This is the question we wanted to answer. &lt;/p&gt;

&lt;p&gt;However, as the quite gruesome saying goes: "there's more than one way to skin a cat" 🙀&lt;/p&gt;

&lt;p&gt;As the title of this post might already have spoiled, we chose to test which framework was the fastest when it came to SSR - Server-Side Rendering.&lt;/p&gt;

&lt;p&gt;The results were interesting, but just like Formula 1, it was often milliseconds that separated the good from the great. &lt;/p&gt;

&lt;p&gt;But before we get into the nitty-gritty, first a very big disclaimer.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔔 DISCLAIMER - YOUR MILEAGE MAY VARY!
&lt;/h2&gt;

&lt;p&gt;As anyone who has ever run any kind of performance test can tell you - results can vary. You can try your best to make the conditions as uniformly as possible and still get different results. &lt;/p&gt;

&lt;p&gt;We have tried to set up each demo similarly to the others. However, there may have been a better way of doing this in one particular framework that we were not familiar with at the time. &lt;/p&gt;

&lt;p&gt;If you feel something could have been different and/or better, please drop a comment or send us an email at &lt;a href="mailto:info@enterspeed.com"&gt;info@enterspeed.com&lt;/a&gt;. The source code for each demo can be found in our demo repo: &lt;a href="https://github.com/enterspeedhq/enterspeed-demos" rel="noopener noreferrer"&gt;https://github.com/enterspeedhq/enterspeed-demos&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the 6 contestants
&lt;/h2&gt;

&lt;p&gt;The 6 contestants we chose, are a mix of some of the most popular frameworks and some of the newer more hyped ones.&lt;/p&gt;

&lt;p&gt;The frameworks we tested were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Astro&lt;/strong&gt;: 18,2k stars on Github, created March 2021&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gatsby&lt;/strong&gt;: 53,4k stars on Github, created May 2015&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt;: 91,8k stars on Github, created October 2016&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nuxt 3&lt;/strong&gt;: 8,7k stars on Github, created March 2021&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remix&lt;/strong&gt;: 19k stars on Github, created October 2020&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SvelteKit&lt;/strong&gt;: 10.1k stars on Github, created October 2020&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All 6 frameworks were set up using SSR. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is SSR?
&lt;/h2&gt;

&lt;p&gt;SSR stands for Server-Side Rendering and is a rendering strategy that converts your application into HTML on the server. &lt;/p&gt;

&lt;p&gt;Some other rendering strategies are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSR - Client-Side Rendering, which renders in the browser.&lt;/li&gt;
&lt;li&gt;SSG - Static Site Generation, which generates the HTML on build (and therefore only fetches data once).&lt;/li&gt;
&lt;li&gt;ISR - Incremental Static Regeneration, which is a combination of SSG and SSR that allows you to create or update static pages after you’ve built your site.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike traditional SPA's (Single Page Application) which use CSR to render their content, SSR gives a faster "time-to-content" and is better for SEO, since crawlers will see the fully rendered page.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we build
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04xs7hsz2ges8d5ve5n1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04xs7hsz2ges8d5ve5n1.jpg" alt="Screenshot of one of the demos&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our demo site which we replicated for each framework is a &lt;strong&gt;beautiful&lt;/strong&gt; blog containing 6 blog posts.&lt;/p&gt;

&lt;p&gt;It uses Tailwind for styling and fetches its content from Enterspeed (a high-performant data store).&lt;/p&gt;

&lt;p&gt;Bonus info - all thumbnails were generated using &lt;a href="https://openai.com/dall-e-2/" rel="noopener noreferrer"&gt;Dall-E 2&lt;/a&gt; with these phrases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"A cat driving a formula 1 car digital art"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"A Llama flying a fighter jet, pixel art"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"A cool, fearless bee riding a motorcycle"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"A falcon flying through outer space in a photorealistic style"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"A person outrunning a cheetah, digital art"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"A photo of a teddy bear riding a rocket"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All demos are public and hosted on Netlify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Astro - &lt;a href="https://enterspeed-astro.netlify.app/" rel="noopener noreferrer"&gt;https://enterspeed-astro.netlify.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gatsby - &lt;a href="https://enterspeed-gatsby.netlify.app/" rel="noopener noreferrer"&gt;https://enterspeed-gatsby.netlify.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next.js - &lt;a href="https://enterspeed-nextjs.netlify.app/" rel="noopener noreferrer"&gt;https://enterspeed-nextjs.netlify.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nuxt 3 - &lt;a href="https://enterspeed-nuxt.netlify.app/" rel="noopener noreferrer"&gt;https://enterspeed-nuxt.netlify.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remix - &lt;a href="https://enterspeed-remix.netlify.app/" rel="noopener noreferrer"&gt;https://enterspeed-remix.netlify.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SvelteKit - &lt;a href="https://enterspeed-sveltekit.netlify.app/" rel="noopener noreferrer"&gt;https://enterspeed-sveltekit.netlify.app/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The GitHub repo for all the demos can be found here: &lt;a href="https://github.com/enterspeedhq/enterspeed-demos" rel="noopener noreferrer"&gt;https://github.com/enterspeedhq/enterspeed-demos&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What we measured and how
&lt;/h2&gt;

&lt;p&gt;To measure the SSR performance of our JS frameworks we used Google Lighthouse (here &lt;a href="https://web.dev/measure/" rel="noopener noreferrer"&gt;Web.dev/measure&lt;/a&gt;). We ran each audit 5 times and calculated the average for each metric.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;💡 An explanation of each metric (and acronym) will come further below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Google Lighthouse measures Core Web Vitals (LCP, FID, CLS), which has become a ranking factor in the Google Search Algorithm, as well as other web vitals (FCP, Speed index, TTI, TBT, and CLS).&lt;/p&gt;

&lt;p&gt;We didn't measure FID (First Input Delay), since this cannot be measured in the lab. However, TBT (Total Blocking Time) correlates well with FID in the field.&lt;/p&gt;

&lt;p&gt;CLS (Cumulative Layout Shift) isn't included in the results either, since all the demos scored 0 in this category.&lt;/p&gt;

&lt;p&gt;Lastly, we wanted to measure TTFB (Time To First Byte), since it can help measure the web server responsiveness which is highly relevant when it comes to SSR.&lt;/p&gt;

&lt;p&gt;To measure TTFB we used &lt;a href="https://github.com/rakyll/hey" rel="noopener noreferrer"&gt;hey&lt;/a&gt;, where we send 250 requests to each demo and measured the average TTFB.&lt;/p&gt;

&lt;p&gt;We noticed that the results fluctuated quite a bit when sending requests to our hosted sites on Netlify, so we chose to run each application locally and measure it that way.&lt;/p&gt;

&lt;p&gt;We also chose to reduce the number of concurrent workers from 50 to just 1 since our Nuxt 3 application kept crashing when sending multiple requests. &lt;/p&gt;

&lt;h2&gt;
  
  
  What does each metric mean?
&lt;/h2&gt;

&lt;p&gt;All metrics (except TTFB) are based on Google's initiative: &lt;a href="https://web.dev/vitals/" rel="noopener noreferrer"&gt;Web vitals&lt;/a&gt;. Google made these metrics to provide unified guidance for quality signals.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;✂️ Explanations borrowed from &lt;a href="https://web.dev/" rel="noopener noreferrer"&gt;Web.dev&lt;/a&gt;&lt;/em&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Google Lighthouse Performance score
&lt;/h3&gt;

&lt;p&gt;The Performance score in Google Lighthouse is a score from 0 - 100. It is a weighted average of the Web Vitals score. Each Web Vital is weighted as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First Contentful Paint: &lt;strong&gt;10%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Speed Index: &lt;strong&gt;10%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Largest Contentful Paint: &lt;strong&gt;25%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Time to Interactive: &lt;strong&gt;10%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Total Blocking Time: &lt;strong&gt;30%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Cumulative Layout Shift: &lt;strong&gt;15%&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The performance score is grouped into three categories (Poor, Needs Improvement, and Good): Needs Improvement, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0 to 49 (red): Poor&lt;/li&gt;
&lt;li&gt;50 to 89 (orange): Needs Improvement&lt;/li&gt;
&lt;li&gt;90 to 100 (green): Good&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: A "perfect" score of 100 is extremely challenging to achieve and not expected.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  First Contentful Paint (FCP)
&lt;/h3&gt;

&lt;p&gt;The First Contentful Paint (FCP) metric measures the time from when the page starts loading to when any part of the page's content is rendered on the screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed Index
&lt;/h3&gt;

&lt;p&gt;Speed Index measures how quickly content is visually displayed during page load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Largest Contentful Paint (LCP)
&lt;/h3&gt;

&lt;p&gt;The Largest Contentful Paint (LCP) metric reports the render time of the largest &lt;a href="https://web.dev/lcp/#what-elements-are-considered" rel="noopener noreferrer"&gt;image or text block&lt;/a&gt; visible within the viewport, relative to when the page &lt;a href="https://w3c.github.io/hr-time/#timeorigin-attribute" rel="noopener noreferrer"&gt;first started loading&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time to Interactive (TTI)
&lt;/h3&gt;

&lt;p&gt;The TTI metric measures the time from when the page starts loading to when its main sub-resources have loaded and it is capable of reliably responding to user input quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Total Blocking Time (TBT)
&lt;/h3&gt;

&lt;p&gt;The Total Blocking Time (TBT) metric measures the total amount of time between First Contentful Paint (FCP) and Time to Interactive (TTI) where the main thread was blocked for long enough to prevent input responsiveness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time To First Byte (TTFB)
&lt;/h3&gt;

&lt;p&gt;TTFB is a metric that measures the time between the request for a resource and when the first byte of a response begins to arrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Drumroll, please...&lt;/em&gt; The results are as follows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Lighthouse Performance score
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. 🏆 Astro - 99,2&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;SvelteKit - 99&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nuxt 3 &amp;amp; Remix - 98,8&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next.js - 98,6&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gatsby - 95,6&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  First Contentful Paint (FCP)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. 🏆 Astro, Gatsby, and Remix - 0,8s&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Next.js &amp;amp; SvelteKit - 0,9&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nuxt 3 - 1,1&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Speed Index
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. 🏆 SvelteKit - 2,3s&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Astro &amp;amp; Remix - 2,8s&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nuxt 3 - 2,9s&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next.js - 3,2s&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gatsby - 5,6s&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Largest Contentful Paint (LCP)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. 🏆 Astro - 0,8s&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;SvelteKit - 0,9s&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next.js, Nuxt 3, Remix - 1.2s&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gatsby - 1,9s&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Time To Interactive (TTI)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. 🏆 Astro - 0,8s&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;SvelteKit - 1,0s&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nuxt 3 - 1,2s&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remix &amp;amp; Gatsby - 1,5s&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next.js - 1,7s&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Total Blocking Time (TBT)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. 🏆 Astro - 0ms&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Nuxt 3 - 20ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gatsby - 28ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remix - 30ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SvelteKit - 36ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next.js - 54ms&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Time To First Byte (TTFB)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. 🏆 SvelteKit - 62ms&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Next.js - 63 ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gatsby - 133ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remix - 136ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Astro - 137ms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nuxt - 438ms&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3nne8rvs3zwh23r4xfw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3nne8rvs3zwh23r4xfw.jpg" alt="Angry mob of JS frameworks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, now, before you start burning down the place, remember what we said in the beginning - your mileage may vary!&lt;/p&gt;

&lt;p&gt;Based on our results, it seems Astro really is the new, fast kid in the class - although not as much in TTFB.&lt;/p&gt;

&lt;p&gt;However, the TTFB results can also be the development server not showing itself from its best side. &lt;/p&gt;

&lt;p&gt;SvelteKit also had some impressive results and is also one of the new frameworks getting a lot of praise when it comes to speed. &lt;/p&gt;

&lt;p&gt;Does this mean you should skip the other frameworks and choose one of these two? Absolutely not. &lt;/p&gt;

&lt;p&gt;Each framework has its use case and its benefits. We are personally big fans of all the fantastic features Next.js provide.&lt;/p&gt;

&lt;p&gt;Moreover, many people use a combination of rendering strategies, e.g. SSG for their homepage, SSR/ISR for their blog pages, and so on.&lt;/p&gt;

&lt;p&gt;Therefore, choose the framework that best fits your needs.&lt;/p&gt;

&lt;p&gt;🩸🔥 Is your blood still boiling and do you need to calm down? Don't worry, &lt;a href="https://www.youtube.com/watch?v=7jfRG-fgvQ8" rel="noopener noreferrer"&gt;we got you&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Enterspeed? 🏎
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://enterspeed.com/" rel="noopener noreferrer"&gt;Enterspeed&lt;/a&gt; is a high-performant data store that makes it possible to gain speed &amp;amp; flexibility by combining your services.&lt;/p&gt;

&lt;p&gt;Connect and combine all your services into a single API endpoint. Easily transform your data with our low-code editor, to get exactly what you need – all stored in our blazing-fast edge network.&lt;/p&gt;

&lt;p&gt;Enterspeed is used in this article to give the data layer (the blog posts) high and consistence performance across all the tests. &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Sprout endless sites from a single Next.js and Umbraco instance</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Thu, 14 Jul 2022 14:50:16 +0000</pubDate>
      <link>https://forem.com/enterspeed/sprout-endless-sites-from-a-single-nextjs-and-umbraco-instance-3227</link>
      <guid>https://forem.com/enterspeed/sprout-endless-sites-from-a-single-nextjs-and-umbraco-instance-3227</guid>
      <description>&lt;p&gt;Has building ordinary, single websites become monotonous for you? Do you need to let loose and just try something new for a bit? Well, you're in luck, we've got just the thing to spice up your developer life.&lt;/p&gt;

&lt;p&gt;In this article, we will show you how to build multiple different websites using just a single Umbraco instance as the CMS and a single Next.js as the frontend. And to get that sweet, sweet performance, we're also going to use Enterspeed as a speed layer to serve our content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffx1a5b883n5pbm33y5a7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffx1a5b883n5pbm33y5a7.gif" alt="Is such a thing even possible? Yes it is!&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some of you might sit with a puzzled look on your face, thinking: "Why on earth would one do that? Is this merely an insane idea like using &lt;a href="https://www.enterspeed.com/blog/using-google-sheets-as-your-cms" rel="noopener noreferrer"&gt;Google Sheets as a CMS&lt;/a&gt;?".&lt;/p&gt;

&lt;p&gt;Even though I'll admit that we love to push the envelope here at Enterspeed and try out crazy things, there are actually several use cases for this scenario. One of these scenarios is microsites. Microsites can, if used correctly, be an extremely powerful marketing strategy for your business.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pssst... Don't care about marketing and strategy but simply want the geeky stuff? 🤓 Then you can safely skip the section below.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The power of Microsites 🐜
&lt;/h2&gt;

&lt;p&gt;First of all, what the dickens is a microsite? &lt;/p&gt;

&lt;p&gt;A microsite is a small website that exists separate from the primary website, often on its own domain or subdomain. Unlike the primary website, it often has just a single goal, for instance, to promote a single product, service, or event.&lt;/p&gt;

&lt;p&gt;Even though they share similarities, a microsite is not a landing page since a landing page is just a single page. A landing page is also focused mainly on hard conversions, whereas a microsite focus can be more about an experience, which can help to assist conversions later down the road.&lt;/p&gt;

&lt;p&gt;Let's look at a few examples.&lt;/p&gt;

&lt;p&gt;Say we are a business that owns several sub-brands or product lines. Each of these brands/products may have its own unique design and may also be targeting different demographics. This could for instance be a brewery selling both alcoholic and non-alcoholic beers.&lt;/p&gt;

&lt;p&gt;Having a unique microsite, where they can fully control the visual identity and the communication, would not only make the message more powerful - it would also help with ad relevance when driving paid traffic to it.&lt;/p&gt;

&lt;p&gt;Another similar example could be a brand with a single product that targets different demographics - this could be a nutrient supplement.&lt;/p&gt;

&lt;p&gt;The target groups for this nutrient supplement could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Older people who wish to reduce the risk of diseases like osteoporosis&lt;/li&gt;
&lt;li&gt;Athletes who are interested in gaining the extra performance&lt;/li&gt;
&lt;li&gt;Health-focused adults who are all about a healthy lifestyle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just to name a few. How you market a product to these three groups differs highly - both in communication and design.&lt;/p&gt;

&lt;p&gt;A third example could be a product or a service with an extremely long and complicated buyer's journey - for instance prefabricated houses. &lt;/p&gt;

&lt;p&gt;The time it takes for a family looking for a house to go from idea (top-funnel) to actual purchase (bottom-funnel) can be literal years.&lt;/p&gt;

&lt;p&gt;This whole sales funnel can contain hundreds of touchpoints and consist of several questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Rent or buy?"&lt;/li&gt;
&lt;li&gt;"How much money for a down payment?"&lt;/li&gt;
&lt;li&gt;"New house vs. old house"&lt;/li&gt;
&lt;li&gt;And much, much, much more...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having strategic microsites, which assist along the whole buyer's journey can be their bytes worth in gold.&lt;/p&gt;

&lt;p&gt;Last example, before jumping into the nitty-gritty details. Say you are a pest control company. You want to increase your organic presence in the search engines to gain more customers and reduce your reliance on SEM (paid advertisement in search engines).&lt;/p&gt;

&lt;p&gt;A strategy here could be to build a microsite for each type of house pest, e.g. flies, bed bugs, wasps, rats, termites, etc.&lt;/p&gt;

&lt;p&gt;Each of these sites will be highly relevant for this specific topic - and can be easier to build links to (some website owners prefer to link to a topic website instead of a company website).&lt;/p&gt;

&lt;p&gt;Phew, that was a lot of marketing talk. To quote Linus Torvalds: &lt;em&gt;“Talk is cheap. Show me the code.”&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up your project
&lt;/h2&gt;

&lt;p&gt;This example will be built around our fictional company - Enterbrew.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawrge65rtqvp04rcbmxi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawrge65rtqvp04rcbmxi.jpg" alt="Enterbrew microsites&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enterbrew is a brewery consisting of 4 subbrands targeting different demographics.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dev Beer&lt;/li&gt;
&lt;li&gt;ToBeer&lt;/li&gt;
&lt;li&gt;Lorem Beer&lt;/li&gt;
&lt;li&gt;Ice beer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these microsites will have a homepage, a page listing the different beers, individual product pages for each beer, and finally an about page.&lt;/p&gt;

&lt;p&gt;However, even though we're going to run all of this from the same backend and the same frontend, we're not limited to this structure. If we wanted, we could use different page types for each page.&lt;/p&gt;

&lt;p&gt;Let's start by setting up a Umbraco instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Umbraco
&lt;/h2&gt;

&lt;p&gt;Start by &lt;a href="https://our.umbraco.com/download/" rel="noopener noreferrer"&gt;downloading&lt;/a&gt; and installing Umbraco, e.g. on your local machine (You can also use &lt;a href="https://umbraco.com/products/umbraco-cloud/" rel="noopener noreferrer"&gt;Umbraco Cloud&lt;/a&gt; if you prefer).&lt;/p&gt;

&lt;p&gt;Note: &lt;em&gt;We're currently working on an Enterspeed-integration for version 10, for now, we're only supporting V7, V8, and V9.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Afterward, go to the Umbraco Backoffice, click on &lt;em&gt;Settings&lt;/em&gt; select &lt;em&gt;Document Types&lt;/em&gt;. In this example we're using 4 document types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Home - the homepage which will also be our root node&lt;/li&gt;
&lt;li&gt;Beers - the parent page of all our beers&lt;/li&gt;
&lt;li&gt;Beer - the single product page&lt;/li&gt;
&lt;li&gt;Content page - used for general content - here our about page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're not going to go over all the properties of each template, since they can be whatever you want. The important thing is that you set one of your document types, in our example, Home, as your root node. This is done under &lt;em&gt;Permissions&lt;/em&gt; in the template, where you set the &lt;em&gt;Allow as root&lt;/em&gt; to &lt;em&gt;true&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Next, go to &lt;em&gt;Content&lt;/em&gt; and create a new item with the root node type, in our example, Home, you just created. Once it's been created, right-click on it and choose &lt;em&gt;Culture and Hostnames&lt;/em&gt;. Add the domain you wish to use for this site, e.g. &lt;a href="https://www.devbeer.com" rel="noopener noreferrer"&gt;https://www.devbeer.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Repeat the step above for all the sites you wish to create.&lt;/p&gt;

&lt;p&gt;Now it's time to set up the Enterspeed integration. Once we've connected our Umbraco site with our Enterspeed account, all of our content will be automatically synced to Enterspeed providing us with a blazing-fast speed layer, and essentially decoupling our Umbraco instance.&lt;/p&gt;

&lt;p&gt;But before we start blasting content into Enterspeed, we first need to create a data source inside Enterspeed, which provides us with an API key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctduajginap7e0kwvi31.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctduajginap7e0kwvi31.jpg" alt="So anyway, I started blasting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to Enterspeed, click on &lt;em&gt;Settings&lt;/em&gt; and select &lt;em&gt;Data sources&lt;/em&gt;. Click &lt;em&gt;Create Group&lt;/em&gt; and give your group a name and a type. Next, add a data source (our Umbraco instance) and select an environment, e.g. &lt;em&gt;Development&lt;/em&gt;. Click &lt;em&gt;Add&lt;/em&gt; and then click &lt;em&gt;Create&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pssst... If you don't have an account already, you can create one right here 👉 &lt;a href="https://app.enterspeed.com/signup" rel="noopener noreferrer"&gt;https://app.enterspeed.com/signup&lt;/a&gt; (no credit card required).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Awesome, now we got an API key. Let's head back to our Umbraco instance.&lt;/p&gt;

&lt;p&gt;Start by adding the Enterspeed integration via &lt;a href="https://www.nuget.org/" rel="noopener noreferrer"&gt;NuGet&lt;/a&gt;. We currently have integrations for versions &lt;a href="https://www.nuget.org/packages/Enterspeed.Source.UmbracoCms.V7" rel="noopener noreferrer"&gt;V7&lt;/a&gt;, &lt;a href="https://www.nuget.org/packages/Enterspeed.Source.UmbracoCms.V8" rel="noopener noreferrer"&gt;V8&lt;/a&gt;, and &lt;a href="https://www.nuget.org/packages/Enterspeed.Source.UmbracoCms.V9/" rel="noopener noreferrer"&gt;V9&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Once you have installed the integration, you'll see a new tab under &lt;em&gt;Settings&lt;/em&gt; called &lt;em&gt;Enterspeed Settings&lt;/em&gt;. Add &lt;em&gt;&lt;a href="https://api.enterspeed.com" rel="noopener noreferrer"&gt;https://api.enterspeed.com&lt;/a&gt;&lt;/em&gt; as the Enterspeed endpoint and the API key you just created under &lt;em&gt;API key&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Test your connection and click &lt;em&gt;Save configuration&lt;/em&gt; if everything goes well.&lt;/p&gt;

&lt;p&gt;All content will now be synced to your Enterspeed account when you save and publish. If you need to seed already published content then click on &lt;em&gt;Content&lt;/em&gt; and select the &lt;em&gt;Enterspeed Content&lt;/em&gt; tab. Choose &lt;em&gt;Seed&lt;/em&gt; and click on the &lt;em&gt;Seed&lt;/em&gt;-button.&lt;/p&gt;

&lt;p&gt;Excellent - now it's time to set up Enterspeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Enterspeed
&lt;/h2&gt;

&lt;p&gt;Verify that all your content has been synced, by going to &lt;em&gt;Source entities&lt;/em&gt; under &lt;em&gt;Home&lt;/em&gt;. Choose your data source and check if all your content is present. Furthermore, confirm that the domain you entered under &lt;em&gt;Culture and Hostnames&lt;/em&gt; in Umbraco matches the one in the URL column.&lt;/p&gt;

&lt;p&gt;If everything is good, then it's time to create our first schemas. So first, what is a schema, and why do we need them?&lt;/p&gt;

&lt;p&gt;One of the powerful things about Enterspeed is its ability to transform and model content into exactly the views you need. This is done by creating schemas. &lt;/p&gt;

&lt;p&gt;Schemas make it easy to combine multiple data sources, and to make deeply nested properties more accessible.&lt;/p&gt;

&lt;p&gt;For our example, we will create a schema for each document type we created in Umbraco, and two for the navigation (one for single navigation items and one containing all the navigation items).&lt;/p&gt;

&lt;p&gt;Once we have created and deployed the schemas, a view will be generated for each source entity that matches the schemas trigger. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;💡 A trigger defines what you want the schema to "trigger on". It consists of a source group (your data source) and contains an array of one or more soure entity types (types of content).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjj1jhvydpleekqxp19q8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjj1jhvydpleekqxp19q8.png" alt="Enterspeed schema: Page&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the trigger property, we first define the source group (here our Umbraco data source group), and next the source entity types (e.g. our &lt;em&gt;page&lt;/em&gt; types).&lt;/p&gt;

&lt;p&gt;Furthermore, we also need to define how we want to access these generated views when called from the front end. Here we have two options: Handle and URL. &lt;/p&gt;

&lt;p&gt;All the schemas that are based on document types from Umbraco reflect an actual page, therefore we will use the URL route option. &lt;/p&gt;

&lt;p&gt;The navigation on the other hand is an entity that will be used across the entire app, therefore we will use the Handle route option.&lt;/p&gt;

&lt;p&gt;Let's take a look at each schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beer-schema
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F907kpzszw2dy8vmfs3jb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F907kpzszw2dy8vmfs3jb.png" alt="Enterspeed schema: Beer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This schema gets its content from the &lt;em&gt;Beer&lt;/em&gt; source entity type and is routable via URL. The URL value will reflect the one in the source entity, which is identical to the page URL in Umbraco.&lt;/p&gt;

&lt;p&gt;We have also set up some actions. The actions here will trigger a process of another schema. Since our &lt;em&gt;Beer&lt;/em&gt; is a child of &lt;em&gt;Beers&lt;/em&gt;, which will show a list of all its children (&lt;em&gt;Beer&lt;/em&gt;), we need a way to update the view whenever its children (&lt;em&gt;Beer&lt;/em&gt;) change. This is done via the action type &lt;em&gt;process&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;em&gt;process&lt;/em&gt; action, we also need to provide an &lt;em&gt;originId&lt;/em&gt;, which is the ID of the source entity given upon ingest (In Umbraco it reflects the page ID). We want this &lt;em&gt;originId&lt;/em&gt; to be the same as our current source entities' &lt;em&gt;(Beer) originParentId&lt;/em&gt; (also given upon ingest, here reflects the page's parent - &lt;em&gt;Beers&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;So to sum up actions. Every time one of our &lt;em&gt;Beer&lt;/em&gt; views changes, it triggers a processing of its parent, here the &lt;em&gt;Beers&lt;/em&gt; view.&lt;/p&gt;

&lt;p&gt;Below we have &lt;em&gt;properties&lt;/em&gt;, which is the actual content. Here we define a type, that we will use for rendering logic in our frontend and the URL to the page, which we will use to link to the individual beers on the overview page. Last, but not least, we grab all the content we have from Umbraco for this page, by using dynamic mapping. We group all of this under &lt;em&gt;content&lt;/em&gt; and map it all out by using: &lt;em&gt;"*": "p"&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Beers-schema
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lp2fl1hbs5malc8vbum.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lp2fl1hbs5malc8vbum.png" alt="Enterspeed schema: Beers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just like the schema above, we have defined triggers, set route to URL, and specified &lt;em&gt;beers&lt;/em&gt; as the &lt;em&gt;type&lt;/em&gt; under properties.&lt;/p&gt;

&lt;p&gt;Under &lt;em&gt;content&lt;/em&gt;, there's suddenly a lot going on, and even though it might seem complex at first, it's actually quite simple when we go through it step by step.&lt;/p&gt;

&lt;p&gt;Where we before had been working mapping out strings, we now want to output an array. So let's look at the structure.&lt;/p&gt;

&lt;p&gt;First, we define the &lt;em&gt;type&lt;/em&gt; as an &lt;em&gt;array&lt;/em&gt;. The reason why you don't have to do this on strings is simply syntactic sugar since strings are the most commonly used property type.&lt;/p&gt;

&lt;p&gt;Next is the &lt;em&gt;input&lt;/em&gt; property. This is where you wish to retrieve the collection from. We use the &lt;em&gt;$lookup&lt;/em&gt; type to define a query-like criteria that the source entities have to match. &lt;/p&gt;

&lt;p&gt;The &lt;em&gt;operator&lt;/em&gt; we want is the equal operator and the &lt;em&gt;sourceEntityProperty&lt;/em&gt; we want to look at is &lt;em&gt;originParentId&lt;/em&gt;. This should &lt;em&gt;matchValue&lt;/em&gt; of &lt;em&gt;originId&lt;/em&gt; (which here is the &lt;em&gt;Beers originId&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;So in other words. We want to source entities that have &lt;em&gt;Beers originId&lt;/em&gt; as their &lt;em&gt;OriginParentId&lt;/em&gt;. All the children of &lt;em&gt;Beers&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Cool. Now the last part - the content. The &lt;em&gt;items&lt;/em&gt; field is used to map the results.&lt;/p&gt;

&lt;p&gt;The type we are using here is a &lt;em&gt;reference&lt;/em&gt; type, which is used to reference another schema - in this case the &lt;em&gt;Beer&lt;/em&gt; schema we created before. We reference another schema using the schemas' &lt;em&gt;alias&lt;/em&gt; - which happens to also be &lt;em&gt;beer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, we need to specify the ID of the source entity, which is found under &lt;em&gt;id&lt;/em&gt;, and since we're looking for it "inside" one of the items, we prepend it with &lt;em&gt;item&lt;/em&gt;, which then becomes: &lt;em&gt;item.id&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And voilà. We now have an array of all our beers 🍻&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0y7i9m015tpfahsgoay3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0y7i9m015tpfahsgoay3.png" alt="Beers array (Postman screenshot)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Home-schema
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbv1tttpe8eu4j4vup9i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbv1tttpe8eu4j4vup9i.png" alt="Enterspeed schema: Home"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And now to relax with a simple schema. We grab the &lt;em&gt;home&lt;/em&gt; source entity type, make it routable via URL, set the &lt;em&gt;type&lt;/em&gt; to &lt;em&gt;home&lt;/em&gt;, and dynamically map all the content under &lt;em&gt;content&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Page-schema
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4utpog1mywcqq5496li.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4utpog1mywcqq5496li.png" alt="Enterspeed schema: Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This schema is structured the same way as the Home-schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation item-schema
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwaw34dfryyskeh9h25m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwaw34dfryyskeh9h25m.png" alt="Enterspeed schema: Navigation item"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it's time to create our individual navigation items. First, we define which source entity types we want the navigation to consist of. In our example, we just want a very simple navigation, consisting of only parent pages (meaning not the individual beers). We also don't want it to include our homepage since we will use the logo for navigating back to it.&lt;/p&gt;

&lt;p&gt;Unlike our previous schemas, we don't include a route, since this schema only will work as part of our navigation schema, and never will be called from the front end.&lt;/p&gt;

&lt;p&gt;Under actions, we do the same as we did in the &lt;em&gt;Beer&lt;/em&gt; schema. We tell it to process its parent whenever something new happens to views with source entity type &lt;em&gt;page&lt;/em&gt; or &lt;em&gt;beers&lt;/em&gt;. The parent in this case the site node, &lt;em&gt;home&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;For properties, we grab the &lt;em&gt;metaData.name&lt;/em&gt; for the &lt;em&gt;label&lt;/em&gt;, the &lt;em&gt;type&lt;/em&gt; we have given our other schemas, and the &lt;em&gt;url&lt;/em&gt; for &lt;em&gt;href&lt;/em&gt;. From Umbraco, we also get a sortOrder, which we can use to sort our navigation in the front end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation schema
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwaplcz6ccn3x2out7a8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwaplcz6ccn3x2out7a8a.png" alt="Enterspeed schema: Navigation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just like a &lt;a href="https://www.youtube.com/watch?v=_vGK008c_rA" rel="noopener noreferrer"&gt;decorative oriental rug&lt;/a&gt;, it's time to tie it all together. We use our root node, home, as our source entity type. &lt;/p&gt;

&lt;p&gt;In route, we will use &lt;em&gt;handles&lt;/em&gt;, since the navigation is a component that will be fetched throughout the website, and not just a page. &lt;/p&gt;

&lt;p&gt;However, since we're building a multi-site solution, we need a way to differentiate the navigation for each site from the other. We do this by appending the handle, &lt;em&gt;navigation&lt;/em&gt;, with &lt;em&gt;-{url}&lt;/em&gt;, which inserts the URL for home, which is the root URL.&lt;/p&gt;

&lt;p&gt;Inside &lt;em&gt;properties&lt;/em&gt;, we create an array that, just like our &lt;em&gt;Beers&lt;/em&gt; schema, uses a reference to another schema, here &lt;em&gt;navigationItem&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That was the last schema we needed to create. Now we just need to deploy our schemas so our brand new views can be generated.&lt;/p&gt;

&lt;p&gt;Once you have deployed the schemas, you can navigate to &lt;em&gt;Generated views&lt;/em&gt; under &lt;em&gt;Home&lt;/em&gt; to see the result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdwakcgiume80po8kakj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdwakcgiume80po8kakj.png" alt="Generated views in Enterspeed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating environment client(s)
&lt;/h3&gt;

&lt;p&gt;The last thing we need to do is to create one or more environment clients.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Well, what is it, one or more?" you ask.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgg40qmo7xqnsya94bht.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgg40qmo7xqnsya94bht.gif" alt="Rick and Morty - *snap* "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Normally you have one environment client per website and, as the same suggests, per environment.&lt;/p&gt;

&lt;p&gt;However, we are building a multisite based on a single Next.js instant, which makes it a little different.&lt;/p&gt;

&lt;p&gt;If you want to use Enterspeeds &lt;a href="https://docs.enterspeed.com/api#tag/Routes" rel="noopener noreferrer"&gt;Routes API&lt;/a&gt;, for instance, to build a sitemap, then you need individual environment clients. If not, then you can do it with just a single environment client. So, the choice is yours. &lt;/p&gt;

&lt;p&gt;Don't worry, we will show you how you can support multiple environment clients later on.&lt;/p&gt;

&lt;p&gt;To create an environment client, go to &lt;em&gt;Settings&lt;/em&gt; and select &lt;em&gt;Environment settings&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you have done everything correctly, then you should already see a list in the &lt;em&gt;Domains&lt;/em&gt; section of all the domains you have entered in Umbraco under &lt;em&gt;Culture and Hostnames&lt;/em&gt;. If not, then you need to manually create them and their hostnames. But for now, let's assume you've done everything correctly - well done 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febyolc1mdkexappa9nzl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febyolc1mdkexappa9nzl.png" alt="Domains Enterspeed"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Go to &lt;em&gt;Environment clients&lt;/em&gt; and click &lt;em&gt;Create&lt;/em&gt;. Give your environment client a name and choose an environment. Then click Create. Next, choose all the domains you want to attach to this environment client. If you're planning on only using one, then select them all.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: If you add more sites in the future, you need to go to edit the environment client and add these domains.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that we're ready to move on to the front end - our Next.js instance. Here we will also meet a very famous, red crustacean, which unfortunately is &lt;a href="https://knowyourmeme.com/memes/futurama-zoidberg-why-not-zoidberg" rel="noopener noreferrer"&gt;not Zoidberg&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvy7cef4vxgmtyprcdwro.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvy7cef4vxgmtyprcdwro.jpg" alt="Sad Zoidberg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Next.js
&lt;/h2&gt;

&lt;p&gt;Now it's time to set up our front end. For this, we're going to use Next.js, Express, and the secret sauce which makes it possible to use just a single Next.js instance, &lt;a href="https://github.com/micheleriva/krabs" rel="noopener noreferrer"&gt;Krabs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What is Krabs? The author describes it as: &lt;em&gt;"...an enterprise-ready Express.js/Fastify middleware for serving thousands of different websites from a single Next.js instance."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pretty freaking cool - and with an awesome name nonetheless. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5zly5nromxfjgqkxn0k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu5zly5nromxfjgqkxn0k.png" alt="Krabs description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shout out to &lt;a href="https://github.com/micheleriva" rel="noopener noreferrer"&gt;Michele Riva&lt;/a&gt; for this awesome package 👏&lt;/p&gt;

&lt;p&gt;Note: &lt;em&gt;Krabs forces you to use a custom server. Therefore, deployments to Vercel are not supported. Krabs is also only supporting SSR at the moment, and not SSG nor ISR.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;First things first - start by &lt;a href="https://nextjs.org/docs/api-reference/create-next-app" rel="noopener noreferrer"&gt;creating your Next app&lt;/a&gt;. Next, install &lt;a href="https://github.com/micheleriva/krabs" rel="noopener noreferrer"&gt;Krabs&lt;/a&gt; and &lt;a href="https://github.com/expressjs/express" rel="noopener noreferrer"&gt;Express&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note: &lt;em&gt;The steps for the Krabs configuration are borrowed from their &lt;a href="https://micheleriva.github.io/krabs/docs/tutorial-basics/getting-started" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7jo336u1p8hxh1wxili.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7jo336u1p8hxh1wxili.png" alt="Install Krabs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new file called server.js. This will be the entry point for our custom Express.js server, which handles our Next.js instance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5bwdj79nnsbqzffh82o7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5bwdj79nnsbqzffh82o7.png" alt="Server.js file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, create a file called .krabs.js (or .krabs.config.js) inside your project's root folder. Krabs will automatically import the configuration from this file.&lt;/p&gt;

&lt;p&gt;We have extended the standard configuration by also adding a property called &lt;em&gt;enterspeedDomain&lt;/em&gt;. The value for the &lt;em&gt;enterspeedDomain&lt;/em&gt; should be the same as the one given in Enterspeed (and Umbraco).&lt;/p&gt;

&lt;p&gt;To actually see what we're doing, we need to configure our &lt;a href="https://www.nublue.co.uk/guides/edit-hosts-file/" rel="noopener noreferrer"&gt;hosts file&lt;/a&gt;. Insert all the domain names you want to view in your local environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhfj9hhviwzazzumgooao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhfj9hhviwzazzumgooao.png" alt="Hosts file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Awesome, now to create the "home" for each website. Inside your &lt;em&gt;pages&lt;/em&gt; directory, create a folder for each of your websites matching the name provided in the Krabs configuration file.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: _app and _document pages are common to every website.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To test out that everything is working, you can create an index.js file with a simple "hello world"-function for each of the websites.&lt;/p&gt;

&lt;p&gt;To see the result run: &lt;em&gt;node server.js&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If everything is working correctly, you should now be able to view each website using the URL you entered in your hosts file and Krabs configuration.&lt;/p&gt;

&lt;p&gt;Next, let's create our connection to Enterspeed. Create a file called enterspeed.js &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2nmc2vee2yzocmt3fjkm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2nmc2vee2yzocmt3fjkm.png" alt="Enterspeed.js file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have a call function and a function for each route type: &lt;em&gt;getByHandle&lt;/em&gt; and &lt;em&gt;getByUrl&lt;/em&gt;. You will notice that we in the &lt;em&gt;getByUrl&lt;/em&gt; function have extended the response with &lt;em&gt;meta&lt;/em&gt;, that way we can correctly handle 404-errors. Each of the functions calls our call function with a handle/url and a "tenant" (the website name configured in Krabs).&lt;/p&gt;

&lt;p&gt;The reason why we include "tenant" is so we can differentiate between our websites and use a different environment client for each - if we choose. In our example, we have simply used the same. &lt;/p&gt;

&lt;p&gt;Next, create an _&lt;em&gt;app.js&lt;/em&gt; file in the &lt;em&gt;pages&lt;/em&gt; directory. Here we will get the tenant name (website name), the &lt;em&gt;enterspeedDomain&lt;/em&gt;, and also fetch our navigation.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fquhauqm7jx1gkyl27cms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fquhauqm7jx1gkyl27cms.png" alt="_app.js file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our &lt;em&gt;App.getInitialProps&lt;/em&gt; you'll see we fetch the navigation via getByHandle and we add the &lt;em&gt;${enterspeedDomain}&lt;/em&gt; to the name, which matches the route name we created in our navigation schema in Enterspeed.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;tenant&lt;/em&gt; and &lt;em&gt;enterspeedDomain&lt;/em&gt; are then added to the &lt;em&gt;pageProps&lt;/em&gt;, which gets returned alongside the navigation. These are now available in our &lt;em&gt;App&lt;/em&gt; function.&lt;/p&gt;

&lt;p&gt;In our &lt;em&gt;App&lt;/em&gt; function, you'll see we expand the tenant object to also include something called &lt;em&gt;tenantDetails&lt;/em&gt;. This is simply some extra layout configuration we've added, which also could have been done in Umbraco. This is just to show how the front-end developer could do this if they didn't have access to the Umbraco instance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffa1pmy1bn1wtuy45zpy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffa1pmy1bn1wtuy45zpy.png" alt="tenantDetails.js file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've chosen to add it in a separate file and not the Krabs configuration, so as to not mix layout specific into the Krabs configuration. &lt;/p&gt;

&lt;p&gt;Lastly, we wrap the App Component with a component called &lt;em&gt;MainLayout&lt;/em&gt;, where we pass &lt;em&gt;tenant&lt;/em&gt; and &lt;em&gt;navigation&lt;/em&gt; as props. Let's take a look at that component.&lt;/p&gt;

&lt;p&gt;We've made a simple layout, based on the awesome UI framework - &lt;a href="https://chakra-ui.com/" rel="noopener noreferrer"&gt;Chakra UI&lt;/a&gt;, but here's the neat part. We could have used a completely different UI framework/library for each of our sites - without hurting the main performance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flit6vdhuz3ecdokzn9f6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flit6vdhuz3ecdokzn9f6.jpg" alt="Amazed man meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, you read that correctly. Talking about bringing power and flexibility to the developer. &lt;/p&gt;

&lt;p&gt;No more internal battles between developers on which UI framework/library to use. No more trying to argue if Tailwind CSS is pure evil or not. Just like an &lt;a href="https://www.youtube.com/watch?v=CJMsFGH4eoQ" rel="noopener noreferrer"&gt;old Burger King commercial&lt;/a&gt;, everybody gets it their way.&lt;/p&gt;

&lt;p&gt;You are now able to choose the UI framework/library that best fits the use case. &lt;/p&gt;

&lt;p&gt;We can achieve this by using Next.js' &lt;a href="https://nextjs.org/docs/advanced-features/dynamic-import" rel="noopener noreferrer"&gt;Dynamic Import&lt;/a&gt;. In our _&lt;em&gt;app.js&lt;/em&gt; file, instead of using the &lt;em&gt;MainLayout&lt;/em&gt; component, we could have used a unique component for each website, based on the tenant name (website name). This could look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrcpim0g7gcgqmhqaqdo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrcpim0g7gcgqmhqaqdo.png" alt="_app.js file with dynamic layout"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's look at how we could set up the pages. We're going to use Next.js' Dynamic Routes - more specifically their &lt;a href="https://nextjs.org/docs/routing/dynamic-routes#optional-catch-all-routes" rel="noopener noreferrer"&gt;Optional catch all routes&lt;/a&gt;. This will allow us to catch all routes and match them with the corresponding view in Enterspeed.&lt;/p&gt;

&lt;p&gt;Inside each of your website folders, create a file called [[...slug]].js&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtdnfaw72qe20noh0tfm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtdnfaw72qe20noh0tfm.png" alt="Slug Js Optional Catch All Routes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our &lt;a href="http://getserversideprops/" rel="noopener noreferrer"&gt;getServerSideProps&lt;/a&gt; function, we hook into its context and grab the &lt;em&gt;tenant&lt;/em&gt;, &lt;em&gt;domain&lt;/em&gt;, and &lt;em&gt;slug&lt;/em&gt;. We pass these three variables on to the &lt;em&gt;getByUrl&lt;/em&gt; function we wrote in our &lt;em&gt;enterspeed.js&lt;/em&gt; file, which we set as our &lt;em&gt;data&lt;/em&gt; variable.&lt;/p&gt;

&lt;p&gt;We then check to see if &lt;em&gt;data.status&lt;/em&gt; is equal to &lt;em&gt;404&lt;/em&gt;, if so we change the &lt;em&gt;statusCode&lt;/em&gt; in our response object to 404 and set our &lt;em&gt;notFound&lt;/em&gt; variable to true.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;data&lt;/em&gt; and the &lt;em&gt;notFound&lt;/em&gt; are then returned as props.&lt;/p&gt;

&lt;p&gt;In our &lt;em&gt;Content&lt;/em&gt; component we check to see if &lt;em&gt;notFound&lt;/em&gt; is true, if so we use Next.js' &lt;a href="https://nextjs.org/docs/advanced-features/custom-error-page#reusing-the-built-in-error-page" rel="noopener noreferrer"&gt;Error component&lt;/a&gt;, if not we return our custom &lt;em&gt;Entity&lt;/em&gt; component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fif1cukwmf7y3cutsh4vg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fif1cukwmf7y3cutsh4vg.png" alt="Entity.js file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our Entity component is responsible for rendering the correct components, based on the type we defined in our schemas earlier.&lt;/p&gt;

&lt;p&gt;You may be thinking: &lt;em&gt;"Hmm, but what if I want to have a custom layout for just one specific page? Should I then create a whole new schema with its own unique type?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nope. Next.js got your back. Predefined routes always take precedence over dynamic routes.&lt;/p&gt;

&lt;p&gt;This means if you want a unique layout for your Contact page, you simply create a page called "contact.js", and this will now respond to domain.com/contact.&lt;/p&gt;

&lt;p&gt;Well, to round things up, let's look at how we would use our data to display a list of our beers.&lt;/p&gt;

&lt;p&gt;All of the data has been passed to the view prop, so all that is left is to map over our array of beers and render them in a nice grid view, which will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lzmle0c98gpklufam6u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lzmle0c98gpklufam6u.png" alt="Devbeer.com - Beers page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code, which is based on Chakra UI, could look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnfsyf1a8yr53dh7tj1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnfsyf1a8yr53dh7tj1n.png" alt="Beers.js file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. We hope you enjoyed the article and it provided some inspiration on how microsites can be used.&lt;/p&gt;

&lt;p&gt;Cheers 🍻&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Using Google Sheets as your CMS</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Thu, 16 Jun 2022 10:02:15 +0000</pubDate>
      <link>https://forem.com/enterspeed/using-google-sheets-as-your-cms-1h1p</link>
      <guid>https://forem.com/enterspeed/using-google-sheets-as-your-cms-1h1p</guid>
      <description>&lt;p&gt;Spreadsheets. They are everywhere. &lt;/p&gt;

&lt;p&gt;Managers especially seem to love them, which is why the world properly would stop functioning without them. &lt;/p&gt;

&lt;p&gt;Like some twisted version of Murphy's law, it seems that anything that can fit into a spreadsheet eventually will end up in a spreadsheet.&lt;/p&gt;

&lt;p&gt;This can result in abominations like a &lt;a href="https://www.bbc.com/news/technology-54423988" rel="noopener noreferrer"&gt;Microsoft Excel database for Covid-19 results&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs69kz06wdmv0oi9bhlhv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs69kz06wdmv0oi9bhlhv.jpg" alt="Blinking meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, following in the spirit of using spreadsheets other ways than intended, I thought - what if we could use a Google Sheet as a CMS?&lt;/p&gt;

&lt;p&gt;Hold on. Hold on. Lower your pitchforks and torches - this is merely a proof of concept to see how it could work. However... there might be some use cases for this - stick around to the end to find out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feo8jgz8kijoiyhvo6s3e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feo8jgz8kijoiyhvo6s3e.jpg" alt="Quote: "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So first thing first, how do we do it in a performant way? We could set up a script to generate a static site, which is something other developers also have played around with before.&lt;/p&gt;

&lt;p&gt;However, we don't want to &lt;strong&gt;have to&lt;/strong&gt; trigger a build each time we push a change in our sheet. We want to be able to use SSR, ISR, or CSR in a performant way.&lt;/p&gt;

&lt;p&gt;What we want to do is decouple our Google Sheets so that it only works as a CMS and not a database. &lt;/p&gt;

&lt;p&gt;Luckily, decoupling data is where Enterspeed really shines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3n1wu987fictqhpwawa.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3n1wu987fictqhpwawa.gif" alt="Decoupling is my middle name"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So let's get to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Set up a Google Sheet
&lt;/h2&gt;

&lt;p&gt;First, start by creating a Google Sheet. In our example, we're going to make a list containing all the movies from the best movie series in the world. &lt;/p&gt;

&lt;p&gt;No, not Marvel or Star Wars. I'm of course talking about the one and only: Olsen Banden (&lt;a href="https://en.wikipedia.org/wiki/Olsen_Gang" rel="noopener noreferrer"&gt;The Olsen Gang&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsp8neqw2h0zf70h4vkk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsp8neqw2h0zf70h4vkk.jpg" alt="Olsen Banden (The Olsen Gang)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The list contains the following columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The number it has in the series (Number)&lt;/li&gt;
&lt;li&gt;  The movies name (Name)&lt;/li&gt;
&lt;li&gt;  Which year it was produced (Year)&lt;/li&gt;
&lt;li&gt;  A link to the movie cover image (Image)&lt;/li&gt;
&lt;li&gt;  A link to its IMDB page (IMDB)&lt;/li&gt;
&lt;li&gt;  A short description (Description)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71dyvxl4em3nvy6wef47.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71dyvxl4em3nvy6wef47.png" alt="Google Sheets screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All content above is shamelessly copied from &lt;/em&gt;&lt;a href="https://www.imdb.com/" rel="noopener noreferrer"&gt;&lt;em&gt;IMDB.com&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that we have all of our data, it's time for step 2 - setting up a script that ingests all the data into Enterspeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Set up a Google Apps Script
&lt;/h2&gt;

&lt;p&gt;Inside your Google Sheet, click on &lt;strong&gt;Extensions &lt;/strong&gt;and select &lt;strong&gt;Apps script&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First, we create a function called &lt;em&gt;ingestToEnterspeed&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxprf76r8yk29qr6uodm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxprf76r8yk29qr6uodm.png" alt="Google Apps Script function (ingestToEnterspeed)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we declare two const to make the code easier to read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;sheet&lt;/em&gt; which equals &lt;em&gt;SpreadsheetApp.getActiveSheet()&lt;/em&gt; - this grabs our active sheet. &lt;/li&gt;
&lt;li&gt;  &lt;em&gt;sheetData&lt;/em&gt; which equals *sheet.getDataRange().getValues()*this grabs all the data from the active sheet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, we take our &lt;em&gt;sheetData-*const and run a *forEach&lt;/em&gt; function. We grab the index (&lt;em&gt;i&lt;/em&gt;) of each, so we can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Skip the first row, since this is the header&lt;/li&gt;
&lt;li&gt;  Know which row number to grab data from&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since our row numbers, unlike our index, don't start at 0, we declare a const called &lt;em&gt;rowNumber&lt;/em&gt; which equals &lt;em&gt;i + 1.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next, we declare a const for the &lt;a href="https://docs.enterspeed.com/api#tag/Ingest" rel="noopener noreferrer"&gt;ingest URL to Enterspeed&lt;/a&gt; (&lt;em&gt;ingestUrl&lt;/em&gt;). The part after /v2/ will be our originId inside Enterspeed and needs to be unique. &lt;/p&gt;

&lt;p&gt;Since we're only going to be ingesting "Olsen banden"-movies, we will simply use "olsen-banden-" + the movie's number in the series - here found in column A. We could also have made a function that grabs the sheet name and combines it with the row number instead, to make it more dynamic.&lt;/p&gt;

&lt;p&gt;The function we use to grab the cells content is: &lt;em&gt;sheet.getRange('A' + rowNumber).getValue()&lt;/em&gt; - this takes the active sheet, finds the defined column for the defined row number and gets the value.&lt;/p&gt;

&lt;p&gt;Now, we create an object called &lt;em&gt;payload&lt;/em&gt;, which specifies what we will send to Enterspeed.&lt;/p&gt;

&lt;p&gt;In the object, we need to define three properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A type (in our case we simply call it '&lt;em&gt;movie&lt;/em&gt;')&lt;/li&gt;
&lt;li&gt;  A URL, if we want it to be routable (if not, we can simply write &lt;em&gt;null&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;  Properties, which is our actual content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our URL follows the same pattern as our originId "olsen-banden-" + the movie number.&lt;/p&gt;

&lt;p&gt;Inside the properties object, we define all the data which we wish to use. The name of the keys can be whatever you like - to keep it simple we just matched the name of our columns.&lt;/p&gt;

&lt;p&gt;Then we use the same function as before to grab the content from the cell we want. &lt;/p&gt;

&lt;p&gt;Once we have defined our data (&lt;em&gt;payload&lt;/em&gt;), we need to make an &lt;em&gt;options&lt;/em&gt; object, which we will use in our fetch function. &lt;/p&gt;

&lt;p&gt;Create a &lt;em&gt;method&lt;/em&gt; property and set it to &lt;em&gt;post&lt;/em&gt;. Then create a &lt;em&gt;headers&lt;/em&gt; object where you define a &lt;em&gt;x-api-key&lt;/em&gt; property. Insert the API key for your data source, which you have created inside Enterspeed. Lastly, create a &lt;em&gt;payload&lt;/em&gt; property and insert &lt;em&gt;JSON.stringy(payload)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Next, for 💩 and giggles we can choose to log out each row inside Google Apps Script, to see what's going on, using &lt;em&gt;Logger.log('Ingesting: ' + row)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Now, it's time to ingest the data using the build-in &lt;em&gt;UrlFetchApp. *Use the *fetch *method and set *ingestUrl *and *options&lt;/em&gt; as parameter: &lt;em&gt;UrlFetchApp.fetch(ingestUrl, options);&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Save project&lt;/strong&gt; and then click &lt;strong&gt;Run&lt;/strong&gt;. You should now be able to see your content inside Enterspeed. Hooray 🎉&lt;/p&gt;

&lt;p&gt;As a bonus, we can create a function that adds a menu item to your Google Sheets menu. This makes it easier to ingest data from the sheet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqpx60wcjkc63i3m663y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqpx60wcjkc63i3m663y.png" alt="Google Apps Script function (onOpen)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google Apps Scripts even support triggers, which means you can set it up to ingest at time-driven events. That's pretty neat.&lt;/p&gt;

&lt;p&gt;Next, we need to ingest a parent source into Enterspeed, which acts as a parent for all our movies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Ingest a parent source
&lt;/h2&gt;

&lt;p&gt;Since we want to create a list of movies, we need to have each individual movie attached to a parent.&lt;/p&gt;

&lt;p&gt;You might have noticed that we added &lt;em&gt;"originParentId": "movies"&lt;/em&gt; in our Google Apps script. This is the one we're going to create.&lt;/p&gt;

&lt;p&gt;We could add many interesting details to this source entity, e.g., a description of the movie series, trivia, etc. But for now, we will simply ingest an empty source entity into Enterspeed since it only needs to act as a parent.&lt;/p&gt;

&lt;p&gt;So bring out your favorite API platform, e.g., &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; or &lt;a href="https://insomnia.rest/" rel="noopener noreferrer"&gt;Insomnia&lt;/a&gt;, and ingest an empty source entity into Enterspeed with the originId "movies": The URL should look like this: &lt;em&gt;&lt;a href="https://api.enterspeed.com/ingest/v2/movies" rel="noopener noreferrer"&gt;https://api.enterspeed.com/ingest/v2/movies&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Remember to add your source API key as a header using the "X-Api-key"-key.&lt;/p&gt;

&lt;p&gt;Awesome. Now that we got all of our data into Enterspeed, it's time to create a schema that generates the view we wish to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Create a schema in Enterspeed
&lt;/h2&gt;

&lt;p&gt;Open Enterspeed and create a new schema.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7t1oktgl01u8qyjtlxn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7t1oktgl01u8qyjtlxn.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're going to create a collection schema, which generates a single view for all the movies.&lt;/p&gt;

&lt;p&gt;Since we have ingested all of our rows individually (with the URL property), we also have the option to make separate pages for each movie.&lt;/p&gt;

&lt;p&gt;However, for now, we will simply make a collection schema.&lt;/p&gt;

&lt;p&gt;We start by setting up our triggers, where we define which data source it should use - and which source entity type (here, we use the "movies"-type which we just ingested in our "parent" source entity).&lt;/p&gt;

&lt;p&gt;We then choose how we should be able to "get the content" – this can be either via a handle or via a URL. In our case, we choose a handle called &lt;em&gt;movies&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In the properties-object is where we define the actual content. We create a property called &lt;em&gt;movies&lt;/em&gt;, which has the type &lt;em&gt;array&lt;/em&gt;. In the &lt;em&gt;input&lt;/em&gt; property, where we define the items we want to work with, we look for source entities with an originParentId that matches our originId (the "parent" source entity - movies).&lt;/p&gt;

&lt;p&gt;For ease of use, we set the collection iteration variable name to &lt;em&gt;movie&lt;/em&gt; instead of the default &lt;em&gt;item&lt;/em&gt; using the &lt;em&gt;var&lt;/em&gt; property.&lt;/p&gt;

&lt;p&gt;Under &lt;em&gt;items&lt;/em&gt;, which are used for mapping results, we choose to grab all the data and wrap them in an object called &lt;em&gt;movie&lt;/em&gt;. We're able to grab all the data by using &lt;a href="https://docs.enterspeed.com/reference/path-selector" rel="noopener noreferrer"&gt;dynamic mapping&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's it. Then we save the schema and deploy it to the environment we wish.&lt;/p&gt;

&lt;p&gt;All that's left now is to fetch the data from the frontend&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Fetching the data
&lt;/h2&gt;

&lt;p&gt;Now to actually show the data. &lt;/p&gt;

&lt;p&gt;We fetch it using the &lt;a href="https://docs.enterspeed.com/api#tag/Delivery" rel="noopener noreferrer"&gt;Enterspeed Delivery API&lt;/a&gt;. We call the URL and add the handle we wish to fetch (here &lt;em&gt;movies&lt;/em&gt;): &lt;em&gt;&lt;a href="https://delivery.enterspeed.com/v1?handle=movies" rel="noopener noreferrer"&gt;https://delivery.enterspeed.com/v1?handle=movies&lt;/a&gt;&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;In our headers, we insert our Environment Client API key in the &lt;em&gt;X-Api-key&lt;/em&gt; property.&lt;/p&gt;

&lt;p&gt;Afterward, we iterate over all the movies and insert them into a card component (in this case we have used &lt;a href="https://getbootstrap.com/" rel="noopener noreferrer"&gt;Bootstrap&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznzjozr517fwq7dn0le8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznzjozr517fwq7dn0le8.png" alt="Website showcasing the movies"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Voilà. We have successfully used Google Sheets as a CMS - but is this merely a proof of concept, or?&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Sheets as a CMS - madness?
&lt;/h2&gt;

&lt;p&gt;So could Google Sheets work as a CMS? In my opinion, yes.&lt;/p&gt;

&lt;p&gt;Now, I know what you're thinking: &lt;em&gt;"Madman! You're a madman! No man, developer or client, should use Google Sheets as a CMS!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But oh, I've chosen my words carefully.&lt;/p&gt;

&lt;p&gt;(P.s. if you don't get the reference - &lt;a href="https://www.youtube.com/watch?v=4Prc1UfuokY" rel="noopener noreferrer"&gt;here you go&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Which CMS you should use all depends on the use case. Should you use Google Sheets for a blog? Properly not. A full fledge company website? Also no.&lt;/p&gt;

&lt;p&gt;But say you are a small café, that often switches up its menu and wants to display the dish of the day. Or a small soccer club, that wishes to showcase all their players. Then yes, it could definitely work.&lt;/p&gt;

&lt;p&gt;Since we decouple the data from the actual Google Sheets, it doesn't really matter performance-wise where you edit your data.&lt;/p&gt;

&lt;p&gt;Also, Enterspeed allows you to transform and combine multiple data sources, which means you could use Google Sheets only for a small section of your site - e.g. your team section.&lt;/p&gt;

&lt;p&gt;So the moral of the story is - once you have decoupled your data, it doesn't really matter how/where you edit it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6829d4nkq5b11fxu5lpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6829d4nkq5b11fxu5lpf.png" alt="This is madness!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you enjoyed the article. You can find all the code used here: &lt;a href="https://github.com/enterspeedhq/enterspeed-demos/tree/master/vanilla-js-google-sheets" rel="noopener noreferrer"&gt;https://github.com/enterspeedhq/enterspeed-demos/tree/master/vanilla-js-google-sheets&lt;/a&gt;&lt;/p&gt;

</description>
      <category>headlesss</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Enabling Preview mode in your Next.js application</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Wed, 08 Jun 2022 09:41:12 +0000</pubDate>
      <link>https://forem.com/enterspeed/enabling-preview-mode-in-your-nextjs-application-30do</link>
      <guid>https://forem.com/enterspeed/enabling-preview-mode-in-your-nextjs-application-30do</guid>
      <description>&lt;p&gt;There's no doubt about it - &lt;a href="https://jamstack.org/" rel="noopener noreferrer"&gt;Jamstack&lt;/a&gt; is here to stay. Since &lt;a href="https://www.netlify.com/blog/authors/matt-biilmann/" rel="noopener noreferrer"&gt;Matt Biilmann&lt;/a&gt; took the concept mainstream back in 2016, a whole ecosystem based upon this architecture has sprung up (including our very own Speed layer, Enterspeed - but more on that later).&lt;/p&gt;

&lt;p&gt;Developers love the benefits a Jamstack architecture can bring to both the site itself and the workflow of the project. We won't dive into the many benefits in this article since books could be (&lt;a href="https://www.manning.com/books/the-jamstack-book" rel="noopener noreferrer"&gt;and have been&lt;/a&gt;) written about this subject. &lt;/p&gt;

&lt;p&gt;However, a site is rarely run by developers alone, but by a wide range of people, e.g., content creators like copywriters, marketers, supporters, etc. All of these people have to be taken into account when developing a new site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev8h6etuz2m75zzpu8ya.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev8h6etuz2m75zzpu8ya.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most content creators have worked with some sort of CMS in the past, for instance, WordPress, and have gotten accustomed to the editor experience.&lt;/p&gt;

&lt;p&gt;One of the key functionalities, that content creators often use, is the option to preview their content before publishing.&lt;/p&gt;

&lt;p&gt;This can be kind of tricky when using SSG (Static Site Generation) since we want to render these pages at request time instead of build time.&lt;/p&gt;

&lt;p&gt;And even though we developers consider our IDE our second home, most content creators aren't that keen on booting up a local instance of the project in order to preview their content (I know, right?).&lt;/p&gt;

&lt;p&gt;Luckily, the awesome team behind &lt;a href="https://github.com/vercel/next.js" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; came up with a brilliant solution to this problem - &lt;a href="https://nextjs.org/docs/advanced-features/preview-mode" rel="noopener noreferrer"&gt;Preview mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Setting up Preview mode enables you to bypass SSG and render the page at request time. So how does it work and how do we enable it? Let's look into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Preview mode in Next.js
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;👉 Note:&lt;/strong&gt; This article is based on the &lt;a href="https://nextjs.org/docs/advanced-features/preview-mode" rel="noopener noreferrer"&gt;Next.js' fantastic documentation&lt;/a&gt;, therefor similar phrasing and code examples will occur. &lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://github.com/vercel/next.js" rel="noopener noreferrer"&gt;Next.js repo&lt;/a&gt;, you'll find plenty of examples of how to set up your headless CMS with Next.js' Preview mode. In this tutorial, we will base the examples on our own solution, Enterspeed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;What is Enterspeed?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Enterspeed is a Speed layer that lets you connect and combine all your services into a single API endpoint. Our low-editor makes it easy to transform your data, to get exactly what you need – all stored in our blazing-fast edge network.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This makes it ideal for speeding up legacy systems, e.g. Umbraco solutions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Well, enough self-promotion - this was simply to give you some context for the examples below. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Creating a Preview API route
&lt;/h3&gt;

&lt;p&gt;Next.js provides a solution to build your API. This is done by creating a folder called &lt;strong&gt;api&lt;/strong&gt; inside the pages folder (pages/api). Any file inside this folder will be treated as an API endpoint instead of a page. &lt;/p&gt;

&lt;p&gt;Create a file called &lt;strong&gt;preview.js&lt;/strong&gt; (or .ts if you're using TypeScript) inside the pages/api folder.&lt;/p&gt;

&lt;p&gt;On the response object (res), we will call &lt;strong&gt;setPreviewData&lt;/strong&gt;, which will turn on preview mode by setting some cookies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkac45ubefw3g5dyyu4p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkac45ubefw3g5dyyu4p.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you've accessed the API route, you can open your browser's developer tools to see that the &lt;strong&gt;__prerender_bypass&lt;/strong&gt; and &lt;strong&gt;__next_preview_data&lt;/strong&gt; cookie have been set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Tip:&lt;/strong&gt; You can specify the preview mode duration. &lt;strong&gt;setPreviewData&lt;/strong&gt; takes an optional second parameter (the options object). Inside the object, set the key to &lt;strong&gt;maxAge&lt;/strong&gt; and the value for how long the preview session should last (in seconds).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F25ajjhvut46lq4pu4u9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F25ajjhvut46lq4pu4u9j.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All requests to Next.js which have these cookies will now be seen as preview mode, which will modify how statically generated pages behave.&lt;/p&gt;

&lt;p&gt;Since you don't want anybody accessing your preview content, a good idea is to create a secret token string for your preview URL. You can use one of the many token generators out there, for instance, &lt;a href="https://randomkeygen.com/" rel="noopener noreferrer"&gt;RandomKeygen.com&lt;/a&gt; to generate your secret token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Tip:&lt;/strong&gt; Store your secret in an &lt;a href="https://nextjs.org/docs/api-reference/next.config.js/environment-variables" rel="noopener noreferrer"&gt;environment variable&lt;/a&gt; and have your team save the preview URL (including the secret token) in a password manager like 1Password.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56e8vxz4kuq3iag7tabv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56e8vxz4kuq3iag7tabv.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 Note:&lt;/strong&gt; If your headless CMS supports setting custom preview URLs, you can implement a fetching logic that redirects to the fetched post. In this example, we simply redirect to root.&lt;/p&gt;

&lt;p&gt;Next, let's implement a way to disable preview mode again, by clearing the Preview mode cookies.&lt;/p&gt;

&lt;p&gt;Create a new file in the api-folder (pages/api) called clear-preview.js. Here you need to call &lt;strong&gt;clearPreviewData&lt;/strong&gt; on the response object.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq42895q2423g7sft2jw7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq42895q2423g7sft2jw7.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you've accessed this API route, you can open your browser's developer tools to see that the &lt;strong&gt;__prerender_bypass&lt;/strong&gt; and &lt;strong&gt;__next_preview_data&lt;/strong&gt; cookie have been removed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Tip:&lt;/strong&gt; For a cleaner URL structure, you can create a folder inside the api-folder called preview and rename preview.js to index.js. Then you can simply name clear-preview.js to clear.js, which will be available via /api/preview/clear.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Updating getStaticProps
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;getStaticProps&lt;/strong&gt; function is used in SSG to pre-render a page at build time using the props returned by getStaticProps.&lt;/p&gt;

&lt;p&gt;However, when Preview mode is enabled, getStaticProps will be called at request time instead of at build time.&lt;/p&gt;

&lt;p&gt;It will also have a &lt;strong&gt;context&lt;/strong&gt; object where &lt;strong&gt;context.preview&lt;/strong&gt; will be &lt;strong&gt;true&lt;/strong&gt;, which is the one we will use. To make the code easier to read, we can destruct the &lt;strong&gt;context&lt;/strong&gt; object like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfw03w5dhxj4drealb1u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfw03w5dhxj4drealb1u.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we will fetch different data based on whether &lt;strong&gt;preview&lt;/strong&gt; is true or not. How this is done can vary based on your CMS.&lt;/p&gt;

&lt;p&gt;In our case, using Enterspeed, we have an API call function that takes the preview context as a parameter. Based on its value (true/false), it will either fetch content from our preview data source or production data source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 Note:&lt;/strong&gt; Enterspeed stores preview data in a separate data source, this may not be the case for your solution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1yycbmqmf84pni9xqli2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1yycbmqmf84pni9xqli2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside our &lt;strong&gt;getStaticProps&lt;/strong&gt; function, we pass the Preview context to our &lt;strong&gt;getByUrl&lt;/strong&gt; function, which passes it to our API call function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7de0947j4tlgk0rr2it.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7de0947j4tlgk0rr2it.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus tip: Implement a preview bar
&lt;/h3&gt;

&lt;p&gt;To help our content creators know if preview mode is enabled or not, we have implemented a "preview bar" in our project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5el3asmmm00o8r3cds4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5el3asmmm00o8r3cds4.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are multiple ways of doing this, each with its own drawbacks.&lt;/p&gt;

&lt;p&gt;Since we wanted the preview bar to be visible across our entire site (to make sure the content creator knew they had preview mode enabled), we decided to set a session cookie once Preview mode had been enabled. &lt;/p&gt;

&lt;p&gt;We could have used getInitialProps, to check for the Preview context in our _app.js, unfortunately, this disables Automatic Static Optimization, which we are not interested in.&lt;/p&gt;

&lt;p&gt;Since we can't set a session cookie via our API route, we passed the query &lt;strong&gt;/?preview=true&amp;amp;secret=${process.env.ENTERSPEED_PREVIEW_SECRET}&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;on our &lt;strong&gt;res.redirect&lt;/strong&gt; in api/preview.js. The secret isn't strictly necessary, but simply to help ensure that the preview parameter is set via the API route.&lt;/p&gt;

&lt;p&gt;Next, we created two helper functions, one of which checks to see if the previewMode session cookie is set to enabled, and one which manages the previewMode session cookie. It checks to see if the preview parameter is true and if the secret parameter matches our secret, by using the next/router.&lt;/p&gt;

&lt;p&gt;If so, we set the session cookie &lt;strong&gt;previewMode&lt;/strong&gt; to enabled. If the preview parameter is equal to "clear", we then remove the cookie again.&lt;/p&gt;

&lt;p&gt;In both functions, we also check to see if we have access to the window object, so we can set the cookie.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxeh004xr628xod84apnw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxeh004xr628xod84apnw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside our _app.js we then call our managePreviewSessionStorage() function. We also check to see if checkPreviewSessionStorage returns true, if so we render our &lt;a href="https://github.com/enterspeedhq/enterspeed-demo-nextjs/blob/master/next/components/PreviewBar.js" rel="noopener noreferrer"&gt;PreviewBar component&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And that's it. Now both developers and content creators can be happy 🙌&lt;/p&gt;

&lt;p&gt;👋 &lt;strong&gt;I hope you enjoyed the article if you are interested in knowing more about Enterspeed you can check us out at &lt;a href="https://www.enterspeed.com/" rel="noopener noreferrer"&gt;enterspeed.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What is "White Screen of Death 💀" (WSoD), and how do you detect it in time?</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Wed, 21 Apr 2021 09:27:52 +0000</pubDate>
      <link>https://forem.com/kaspera/what-is-white-screen-of-death-wsod-and-how-do-you-detect-it-in-time-28hi</link>
      <guid>https://forem.com/kaspera/what-is-white-screen-of-death-wsod-and-how-do-you-detect-it-in-time-28hi</guid>
      <description>&lt;p&gt;&lt;strong&gt;⚠️ NOTE: This article and its solution are based on our own product - Alertdesk, which is a paid product. If you have an alternative solution to the problem, feel free to post it in the comment below.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Website errors. They come in all shades and are every website owner's fear.&lt;/p&gt;

&lt;p&gt;What if I told you that there was an error so cunning that it was named after Death itself? Then you would be scared. Very scared 😱&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/BkchoMJYKvU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If we turn down the drama a bit, then "White Screen of Death" (WSoD) is an extremely irritating error. Why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It makes your page inaccessible to visitors.&lt;/li&gt;
&lt;li&gt;It can shut you out of your backend (e.g., the WordPress admin area).&lt;/li&gt;
&lt;li&gt;It can occur suddenly and without you making any changes yourself.&lt;/li&gt;
&lt;li&gt;Most "Uptime Monitoring" services will not necessarily catch the error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To put it mildly, it's annoying as hell.&lt;/p&gt;

&lt;p&gt;But what exactly is the "White Screen of Death," and how does it occur?&lt;/p&gt;

&lt;h2&gt;
  
  
  What is White Screen of Death?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuuimni596p8ileesirw5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuuimni596p8ileesirw5.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"White Screen of Death" is an error that gives the visitor a white/blank screen. There is often no information about the error, just the color white as far as the eye can see.&lt;/p&gt;

&lt;p&gt;It may occur as a result of a PHP and database error. Therefore, it also affects many CMS', including Magento and PrestaShop.&lt;/p&gt;

&lt;p&gt;But where the error is most prevalent is probably at the world's most popular CMS, WordPress, where it has even found a place in the &lt;a href="https://wordpress.org/support/article/common-wordpress-errors/#the-white-screen-of-death" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;. Therefore, this article will also be based on WordPress.&lt;/p&gt;

&lt;p&gt;The most common cause of a WSoD-error is a faulty plugin or theme. Other reasons can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A syntax error in the code - e.g., if you have made edits yourself.&lt;/li&gt;
&lt;li&gt;The Memory Limit is set to low in either your wp-config.php file, .htaccess file, or php.ini file.&lt;/li&gt;
&lt;li&gt;A failed auto-update of WordPress - e.g., due to a server timeout.&lt;/li&gt;
&lt;li&gt;Problems with your &lt;a href="https://wordpress.org/support/article/changing-file-permissions/" rel="noopener noreferrer"&gt;File Permissions&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How do you fix it?
&lt;/h2&gt;

&lt;p&gt;You start by finding out what is causing the error. It may sound easier said than done when all you have to work with is a blank screen. But fear not.&lt;/p&gt;

&lt;p&gt;WordPress has a built-in &lt;a href="https://wordpress.org/support/article/debugging-in-wordpress/" rel="noopener noreferrer"&gt;debugging mode&lt;/a&gt; that makes errors visible on your page.&lt;/p&gt;

&lt;p&gt;To enable it, open your &lt;a href="https://wordpress.org/support/article/editing-wp-config-php/" rel="noopener noreferrer"&gt;wp-config.php file&lt;/a&gt; and find the following line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;define( 'WP_DEBUG', false);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then change &lt;strong&gt;false&lt;/strong&gt; to &lt;strong&gt;true&lt;/strong&gt;, so it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;define( 'WP_DEBUG', true);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Error messages will now be visible on your page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Tip:&lt;/strong&gt; If you are not that much into messing with files, then the &lt;a href="https://wordpress.org/plugins/wp-debugging/" rel="noopener noreferrer"&gt;WP Debugging plugin&lt;/a&gt; does the same (obviously requires you to be able to get into the WordPress admin area). However, this is Dev.to so this properly isn't the case.&lt;/p&gt;

&lt;p&gt;In the vast majority of cases, it will be a plugin that is causing the error. You will be able to see the name of the plugin in the error message.&lt;/p&gt;

&lt;p&gt;If you have access to your WordPress admin area, simply deactivate the plugin on the "Installed plugins"-page. &lt;/p&gt;

&lt;p&gt;If you can't access the admin area, you can deactivate the plugin manually via an FTP program or your web host's "File Manager"-tool. &lt;/p&gt;

&lt;p&gt;Go to your plugin folder (wp-content -&amp;gt; plugins) and find the plugin that throws the error. Then rename the folder to something else, e.g., from "hello-dolly" to "hello-dolly-1". The plugin will now be disabled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Tip:&lt;/strong&gt; Plugins can also be disabled via phpMyAdmin and WP-CLI - I just find this solution the easiest.&lt;/p&gt;

&lt;p&gt;Reload your page and see if the issue is resolved.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can be done to detect it in time?
&lt;/h2&gt;

&lt;p&gt;One of the most annoying things about the WSoD error is that it can occur suddenly - even without you actively changing anything on your website.&lt;/p&gt;

&lt;p&gt;What's worse, if you have traditional "uptime monitoring" enabled, it will not necessarily catch the error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F919s9e8fh1q3lugdk8qt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F919s9e8fh1q3lugdk8qt.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, you read that right. Most services, including Alertdesk, look at your site’s &lt;a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes" rel="noopener noreferrer"&gt;HTTP status code&lt;/a&gt;. If the page gives a status code 2XX or 3XX, it gets interpreted as up, where status code 4XX and 5XX gets interpreted as down.&lt;/p&gt;

&lt;p&gt;"&lt;a href="https://www.youtube.com/watch?v=8uYuxd3c7ns" rel="noopener noreferrer"&gt;There's got to be a better way!&lt;/a&gt;" you may think - and for fear of sounding like a classic American infomercial: Yes, there is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscahqcmtmgzxvgukn8b0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscahqcmtmgzxvgukn8b0.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you set up an Uptime-check in Alertdesk, you have the option to set up specific "rules" for that check. We call this &lt;a href="https://help.alertdesk.com/docs/uptime-check/#5-assertions-rules" rel="noopener noreferrer"&gt;Assertions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the rules you can set up is that a piece of text must be present on the page in order for it to be "up".&lt;/p&gt;

&lt;p&gt;This is a super powerful tool when it comes to checking for WSoD errors.&lt;/p&gt;

&lt;p&gt;If the text is not visible (which it won't be in the event of a WSoD error), your check will get marked as "down". You will then get notified immediately by E-mail, Push, Slack, or the channel you have selected.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to set up an Uptime-check with Assertions in Alertdesk
&lt;/h3&gt;

&lt;p&gt;Start by creating an account on Alertdesk. Go to Monitoring, click the &lt;strong&gt;"Add check"&lt;/strong&gt;-button and choose &lt;strong&gt;"Uptime check"&lt;/strong&gt;. Enter your URL and click the &lt;em&gt;"Advanced settings"&lt;/em&gt;-toggle in the right corner.&lt;/p&gt;

&lt;p&gt;Scroll down to &lt;strong&gt;Assertions&lt;/strong&gt; and select &lt;strong&gt;"Text body"&lt;/strong&gt; under "Source", &lt;strong&gt;Contains&lt;/strong&gt; under "Comparison," and then paste the piece of text that should be present on your page under "Target" (is case sensitive). Then click the &lt;strong&gt;Add&lt;/strong&gt;-button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8r6huw9tkr9q6d7w2geh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8r6huw9tkr9q6d7w2geh.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;NOTE:&lt;/strong&gt; There may be issues with special characters, as they may be converted to HTML characters instead, which will not match. Therefore use text without special characters.&lt;/p&gt;

&lt;p&gt;Then click the &lt;strong&gt;Next&lt;/strong&gt;-button. Select who to notify and then click the &lt;strong&gt;Next&lt;/strong&gt;-button again. Give your check a name and make sure everything looks fine. Finally, click &lt;strong&gt;Save check&lt;/strong&gt;, and you are done.&lt;/p&gt;

&lt;p&gt;Easy peasy lemon squeezy 🍋&lt;/p&gt;

&lt;p&gt;You will now get notified immediately if your website should get a “White Screen of Death” error.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>wordpress</category>
      <category>frontend</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Make your pages look good on social media 🤳</title>
      <dc:creator>Kasper Andreassen</dc:creator>
      <pubDate>Mon, 19 Apr 2021 07:16:30 +0000</pubDate>
      <link>https://forem.com/kaspera/make-your-pages-look-good-on-social-media-561g</link>
      <guid>https://forem.com/kaspera/make-your-pages-look-good-on-social-media-561g</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pgA-gVkh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ptgdyi4yq6elv67vg93a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pgA-gVkh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ptgdyi4yq6elv67vg93a.png" alt="Alt Text" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ve made sure your pages look good and attract customers. But how do your pages look “offsite”? &lt;/p&gt;

&lt;p&gt;More specifically, how do your pages look when they get shared on social media sites like Facebook, LinkedIn, Twitter, and Pinterest?&lt;/p&gt;

&lt;p&gt;This is how our site, Alertdesk.com, looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LPFiIkwk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k825j5hu9alyu1ynnf3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LPFiIkwk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k825j5hu9alyu1ynnf3p.png" alt="Alt Text" width="672" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How Alertdesk.com looks when shared on Facebook.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;How did we achieve that? The answer is social tags.&lt;/p&gt;

&lt;p&gt;Social tags refer to Open Graph and the Twitter Cards, which are meta tags you place in the &lt;/p&gt;-section of your pages.

&lt;p&gt;Setting up social tags can make your pages more eye-catching when shared on social media. Properly set up tags can also help sites like Facebook to better understand what your content is about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of social tags
&lt;/h2&gt;

&lt;p&gt;There are essentially two types of social meta tags – those who work on Twitter and those who work on every other site, roughly speaking.&lt;/p&gt;

&lt;p&gt;These two types are called Twitter Card and Open Graph. &lt;/p&gt;

&lt;p&gt;Twitter Card, developed by Twitter (who would have guessed?), mainly works on Twitter. By adding it to your pages, users who share your pages on Twitter will have a “card” added to their tweet. &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards"&gt;You can read more about Twitter cards here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Open Graph, developed by Facebook, works on a few more sites than just Facebook. LinkedIn and Pinterest, to name a few, also support the Open Graph-format. &lt;a href="https://ogp.me/"&gt;You can read more about the Open Graph protocol here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which tags should you use?
&lt;/h2&gt;

&lt;p&gt;There are many kinds of Open Graph- and Twitter Card tags and attributes. We’re not going to cover them all in this article. &lt;/p&gt;

&lt;p&gt;The tags below are the ones you should have to help sites like Facebook and Twitter better understand your content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open Graph tags
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;og:title – The title of your page (doesn’t have to match your page title).&lt;/li&gt;
&lt;li&gt;og:url – The canonical URL of your page (all shares will go to this URL).&lt;/li&gt;
&lt;li&gt;og:image – The URL of the image you want to appear in your social snippet (recommended resolution 1200×627 px).&lt;/li&gt;
&lt;li&gt;og:description – A description of your page (doesn’t have to match your page’s meta description).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It can also be a good idea to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;og:type – The type of content (Article, website, video, etc.).&lt;/li&gt;
&lt;li&gt;og:locale – Your page’s language (Facebook will assume this is “en_US” if no other is set).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Twitter Card tags
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;twitter:card – The type of card you want – &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards"&gt;choose between 4 types&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;twitter:title – The title of your page (doesn’t have to match your page title).&lt;/li&gt;
&lt;li&gt;twitter:description – A description of your page (doesn’t have to match your page’s meta description.&lt;/li&gt;
&lt;li&gt;twitter:image – The URL of the image you want to appear in your social snippet (1:1 aspect ratio – minimum 144×144 px and maximum 4096×4096 px).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: For Pinterest, there are a few more Open Graph tags available. &lt;a href="https://developers.pinterest.com/docs/rich-pins/reference/"&gt;You can view Pinterest’s documentation here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set up your tags
&lt;/h2&gt;

&lt;p&gt;If you are using WordPress, several plugins can get the job done. SEO plugins like &lt;a href="https://wordpress.org/plugins/wordpress-seo/"&gt;Yoast SEO&lt;/a&gt; and &lt;a href="https://wordpress.org/plugins/seo-by-rank-math/"&gt;RankMath&lt;/a&gt; both have a social feature, where you can customize your Open Graph and Twitter Card data.&lt;/p&gt;

&lt;p&gt;Almost all CMS’ either have this functionality natively, like &lt;a href="https://support.squarespace.com/hc/en-us/articles/115009564587"&gt;Squarespace&lt;/a&gt;, or has some kind of plugin or add-on for it like &lt;a href="https://marketplace.magento.com/mageworx-module-seomarkupmeta.html"&gt;Magento&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to view and debug your tags
&lt;/h2&gt;

&lt;p&gt;Sometimes you may need to test and debug your tags. For that, you can use these tools to get the job done:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.facebook.com/tools/debug/sharing/"&gt;Facebook Sharing Debugger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cards-dev.twitter.com/validator"&gt;Twitter Card Validator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/post-inspector/inspect/"&gt;LinkedIn Post Inspector&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🤳 Use Alertdesk to check your social tags
&lt;/h2&gt;

&lt;p&gt;In our social &amp;amp; content reports, you can see how your page will look on Facebook and Twitter when shared. &lt;/p&gt;

&lt;p&gt;We also check to see if you are missing any of the recommended social tags (the ones mentioned above). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.alertdesk.com/?utm_source=devto&amp;amp;utm_medium=organic&amp;amp;utm_campaign=article&amp;amp;utm_content=make-your-pages-look-good-on-social-media"&gt;Check your social tags today with Alertdesk. Try us free for 14 days.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>html</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
