<?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: Vue Storefront</title>
    <description>The latest articles on Forem by Vue Storefront (@vue-storefront).</description>
    <link>https://forem.com/vue-storefront</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%2Forganization%2Fprofile_image%2F622%2F06eff8ee-9da9-4551-ae73-1ba5a427c067.png</url>
      <title>Forem: Vue Storefront</title>
      <link>https://forem.com/vue-storefront</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vue-storefront"/>
    <language>en</language>
    <item>
      <title>Everyday Challenges of Responsive Web Design</title>
      <dc:creator>Rohrig</dc:creator>
      <pubDate>Tue, 21 Nov 2023 16:44:02 +0000</pubDate>
      <link>https://forem.com/vue-storefront/everyday-challenges-of-responsive-web-design-5e3j</link>
      <guid>https://forem.com/vue-storefront/everyday-challenges-of-responsive-web-design-5e3j</guid>
      <description>&lt;p&gt;Part 1: The Right Image for the Device&lt;/p&gt;

&lt;p&gt;Responsive Web Design (RWD) is essential in today's multi-device world. A key challenge in RWD is choosing and loading the right image size based on the device's screen size. This ensures both the quality of the image and the performance of the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element example
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VRm-yc6x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/rohrig/carosel-multisize-image-example/assets/45824492/5617d5d2-e37d-454c-8331-207b556bcdda" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VRm-yc6x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/rohrig/carosel-multisize-image-example/assets/45824492/5617d5d2-e37d-454c-8331-207b556bcdda" alt="Optimize" width="600" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Role of the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; Element in RWD
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Understanding the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; Tag
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element in HTML allows for more control over image resources in different scenarios.&lt;/li&gt;
&lt;li&gt;It contains one or more &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements and a single &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element.&lt;/li&gt;
&lt;li&gt;The browser evaluates each &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; to find the best match for the current display and if none matches, or if the browser doesn't support &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;, it falls back to the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element's &lt;code&gt;src&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The Code in Action
&lt;/h4&gt;

&lt;p&gt;Consider this Vue.js code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"(min-width: 768px)"&lt;/span&gt; &lt;span class="na"&gt;:srcset=&lt;/span&gt;&lt;span class="s"&gt;"item.largeImage"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 767px)"&lt;/span&gt; &lt;span class="na"&gt;:srcset=&lt;/span&gt;&lt;span class="s"&gt;"item.smallImage"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;:src=&lt;/span&gt;&lt;span class="s"&gt;"item.defaultImage"&lt;/span&gt; &lt;span class="na"&gt;:alt=&lt;/span&gt;&lt;span class="s"&gt;"item.altText"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Image Selection&lt;/strong&gt;: Different images are loaded depending on the screen width. Larger images for screens wider than 768 pixels, and smaller ones for narrower screens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficiency and Speed&lt;/strong&gt;: This technique optimizes bandwidth usage and improves page load times by loading images best suited to the viewer’s display.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback Mechanism&lt;/strong&gt;: The &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element is a backup if no &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; elements match.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best Practices for Using the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; Element
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Art Direction&lt;/strong&gt;: Use &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; to modify images for different conditions, like loading a simpler image with fewer details on smaller screens.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Format Flexibility&lt;/strong&gt;: Offer alternative image formats, such as AVIF or WEBP, which may not be supported by all browsers, alongside more universal formats.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimized Bandwidth Use&lt;/strong&gt;: Save bandwidth and improve page load times by loading the most appropriate image for the viewer's display.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;High-DPI Displays&lt;/strong&gt;: Use &lt;code&gt;srcset&lt;/code&gt; on the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element for high-DPI displays. This allows browsers to choose lower-density versions in data-saving modes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Positioning and Sizing&lt;/strong&gt;: Utilize &lt;code&gt;object-fit&lt;/code&gt; and &lt;code&gt;object-position&lt;/code&gt; properties on the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element to adjust the positioning and sizing of the image within the frame.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The why
&lt;/h3&gt;

&lt;p&gt;In responsive web design, effectively managing images is crucial. The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element is a powerful tool that helps in loading the right image for the right device, enhancing both the user experience and the website’s performance. As we continue to navigate the challenges of RWD, understanding and utilizing these elements will be key to creating versatile and efficient web designs. Stay tuned for more insights in this series on responsive web design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considering alternative approaches
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why not just load images based on device detection?
&lt;/h3&gt;

&lt;p&gt;Choosing the right method to load images on a website, especially in a responsive design context, involves considering both efficiency and user experience. While detecting the user's device server-side and then serving appropriate images based on that information is a possible approach, there are several reasons why using HTML's &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element or CSS media queries might be more advantageous:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Device Diversity&lt;/strong&gt;: The range of devices used to access the web is vast and constantly evolving. It's not just about differentiating between a desktop and a mobile device; there are various screen sizes, resolutions, and even connection speeds to consider. Using the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element or CSS media queries allows the browser to make real-time decisions based on the actual device characteristics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client-Side Flexibility&lt;/strong&gt;: By using the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element or CSS, the decision about which image to load is made client-side, based on the actual conditions at the moment the page is rendered. This includes not only the screen size but also the type of connection (e.g., Wi-Fi or cellular data), which can change even during a single session.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance and Bandwidth Optimization&lt;/strong&gt;: Detecting the device server-side and serving different images based on this can lead to unnecessary overhead. The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element allows for more efficient loading of images, as it can select the most appropriate image based on the current viewport and pixel density, potentially saving bandwidth and improving load times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scalability and Maintenance&lt;/strong&gt;: Maintaining a server-side device detection system can be complex and require constant updates as new devices enter the market. In contrast, using the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element or CSS media queries offloads this responsibility to the browser, which is regularly updated to handle new devices and screen sizes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Future-Proofing&lt;/strong&gt;: The web is continuously evolving, and so are web standards and browsers. Relying on current standards like the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element ensures that your website is more likely to remain compatible with future devices and browsers without requiring significant rework.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SEO and Accessibility&lt;/strong&gt;: Search engines prefer websites that use standard, semantic HTML. Using the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element is in line with HTML standards and can be more easily interpreted by search engines, potentially improving SEO. It's also more straightforward for screen readers and other assistive technologies to interpret, improving accessibility.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While server-side device detection has its uses, for image loading in a responsive design, client-side methods like the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element or CSS media queries generally offer greater flexibility, efficiency, and forward compatibility. These methods align better with the principles of responsive web design, focusing on the actual capabilities and conditions of the user's device at the time of access.&lt;/p&gt;

&lt;h2&gt;
  
  
  A more complex example
&lt;/h2&gt;

&lt;p&gt;For this example, I'm using a Nuxt 3 project along with my favorite eCommerce UI library, &lt;a href="https://docs.storefrontui.io/v2/"&gt;Storefront UI&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Challenge
&lt;/h3&gt;

&lt;p&gt;What if I need to create something more than just a static image, what about something as complex as a carousel? And to boot, what if we want to load different images into that carousel based on the size of the browser window?&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; approach works quite well in this scenario. Let's use a Nuxt 3 App for example. We can create a component that will load the images based on the size of the browser window. There are many edge cases we're ignoring here so we can focus on the topic at hand. &lt;/p&gt;

&lt;p&gt;Let's focus on the carousel: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/components/Carousel.vue&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"carousel"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"carousel-track"&lt;/span&gt; &lt;span class="na"&gt;:style=&lt;/span&gt;&lt;span class="s"&gt;"{ transform: `translateX(-${currentIndex * 100}%)` }"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"carousel-item"&lt;/span&gt; &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"(item, index) in items"&lt;/span&gt; &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"index"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"(min-width: 768px)"&lt;/span&gt; &lt;span class="na"&gt;:srcset=&lt;/span&gt;&lt;span class="s"&gt;"item.largeImage"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 767px)"&lt;/span&gt; &lt;span class="na"&gt;:srcset=&lt;/span&gt;&lt;span class="s"&gt;"item.smallImage"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-full h-auto md:w-auto"&lt;/span&gt; &lt;span class="na"&gt;:src=&lt;/span&gt;&lt;span class="s"&gt;"item.defaultImage"&lt;/span&gt; &lt;span class="na"&gt;:alt=&lt;/span&gt;&lt;span class="s"&gt;"item.altText"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"carousel-dots"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dot"&lt;/span&gt; &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"(item, index) in items"&lt;/span&gt; &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"index"&lt;/span&gt; &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"{ 'active': index === currentIndex }"&lt;/span&gt;
        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"goToItem(index)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&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="s1"&gt;vue&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;currentIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;largeImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/images/large1.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;smallImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/images/small1.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;defaultImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/images/large1.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;altText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image 1&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="na"&gt;largeImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/images/large2.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;smallImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/images/small2.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;defaultImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/images/large2.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;altText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image 2&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="na"&gt;largeImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/images/large3.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;smallImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/images/small3.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;defaultImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/images/large3.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;altText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image 3&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="nx"&gt;autoSlideInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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;startAutoSlide&lt;/span&gt; &lt;span class="o"&gt;=&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="nf"&gt;stopAutoSlide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;autoSlideInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&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="nf"&gt;nextItem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&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;stopAutoSlide&lt;/span&gt; &lt;span class="o"&gt;=&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;autoSlideInterval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;autoSlideInterval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;autoSlideInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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="nf"&gt;onMounted&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="nf"&gt;startAutoSlide&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;nextItem&lt;/span&gt; &lt;span class="o"&gt;=&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="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;goToItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&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="nf"&gt;stopAutoSlide&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;currentIndex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.carousel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.carousel-track&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;0.5s&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;/* Smooth transition for sliding */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.carousel-item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;/* Each item takes full width of the carousel */&lt;/span&gt;
  &lt;span class="c"&gt;/* Other styles for the item */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Enter and leave transitions */&lt;/span&gt;
&lt;span class="nc"&gt;.slide-enter-active&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nc"&gt;.slide-leave-active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;0.5s&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.slide-enter&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nc"&gt;.slide-leave-to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c"&gt;/* Start and end state for sliding */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.slide-enter-to&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nc"&gt;.slide-leave&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c"&gt;/* End and start state for sliding */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.carousel-dots&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-50%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.dot&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;opacity&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.dot.active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This carousel will fit nicely with the rest of my UI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/pages/index.vue&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex-col align-middle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Carousel&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;NewsLetterForm&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;" flex justify-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Banner&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can get your hands on the code here: &lt;a href="https://github.com/rohrig/carosel-multisize-image-example"&gt;https://github.com/rohrig/carosel-multisize-image-example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While it might be tempting to create components based on the device, it's more efficient and future-proof to design them responsively, allowing for seamless adaptation to different screen sizes and resolutions. This approach ensures a consistent user experience across all devices and reduces the need for device-specific code maintenance.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ecomm</category>
      <category>frontend</category>
    </item>
    <item>
      <title>How to build accessible button component</title>
      <dc:creator>Anna Musial</dc:creator>
      <pubDate>Fri, 11 Aug 2023 07:58:17 +0000</pubDate>
      <link>https://forem.com/vue-storefront/how-to-build-accessible-button-component-18el</link>
      <guid>https://forem.com/vue-storefront/how-to-build-accessible-button-component-18el</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;It is hard to imagine a website app without a single button. It is a key element of user interaction, providing a means for users to perform actions and navigate through the interface. However, not all users may be able to easily interact with buttons, particularly those with disabilities or impairments that affect their motor skills or vision. Together with the Storefront UI team we decided to provide the best accessibility possible for our components, starting with something as simple as a button. &lt;/p&gt;

&lt;p&gt;In this article, we will explore the importance of button accessibility, the challenges that users with disabilities may face, and best practices for designing buttons that are accessible to all users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantics as a starting point
&lt;/h2&gt;

&lt;p&gt;We can’t have a conversation about accessibility without providing proper semantics. It all starts with HTML and if you would ask developers around the globe how to create a button, you may get completely different answers. So let’s review our options. &lt;/p&gt;

&lt;h3&gt;
  
  
  Using the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; tag
&lt;/h3&gt;

&lt;p&gt;The most recommended one is using &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; tag. It provides all behaviors and functionalities by default. Funny thing is, it is not the only accessible way to achieve our goals. For many years developers lacked awareness on why the quality of code is so important. As long as the solution works and the client is happy, why should we care about semantics? Well, in recent years accessibility has become a legal requirement in the US. It can also affect SEO, as Google is ranking the websites based on their HTML structure. So yes, it does matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Legacy code can be a real pain, because it is not always so easy to change the code without causing breaking change. Unfortunately, using things like &lt;code&gt;&amp;lt;div role=”button” /&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;input role=”button /&amp;gt;&lt;/code&gt; is still common among many web pages.  However, it is a perfectly accessible element as long as you actually provide all the interactions to make it work exactly like a native &lt;code&gt;&amp;lt;button /&amp;gt;&lt;/code&gt;. You must treat the role as a promise, not a solution. This requirement and the human factor is the real reason why so many webpages are still not accessible enough. That is why I highly recommend riding on the coattails of others and using &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; wherever the element should trigger a click action. And that’s it. Accessibility is all about making your code smarter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design vs. Development
&lt;/h3&gt;

&lt;p&gt;I cannot stress this enough - cooperation between developers and designers is essential for creating a product with good accessibility. &lt;strong&gt;I would say that two key focus areas for designers should be proper sizing and color contrast.&lt;/strong&gt; This can really affect your project's accessibility. Buttons must have a sufficient (minimum of 37 px according to MIT Touch Lab) size to be easily clickable, especially on touch devices. Larger buttons are easier to target accurately, reducing the risk of accidental clicks. &lt;/p&gt;

&lt;p&gt;Color contrast between the button's foreground (text or icon) and background colors is particularly important for users with low vision or color blindness. Ratio of at least 4.5:1 should be used for normal-sized text and 3:1 for large text. There are many online tools that allow developers and designers to find a perfect match. &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%2Fsy5ywwh5r6k0frbmsa6u.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%2Fsy5ywwh5r6k0frbmsa6u.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, it is essential to prioritize accessibility over design preferences and invest the necessary effort to ensure an inclusive user experience for all users, including those who rely on keyboard input or assistive technologies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility properties
&lt;/h2&gt;

&lt;p&gt;Sometimes a little help from HTML attributes is needed to make the button truly accessible. &lt;/p&gt;

&lt;h3&gt;
  
  
  Role vs. type
&lt;/h3&gt;

&lt;p&gt;In the previous section I’ve briefly presented the usage of &lt;code&gt;role&lt;/code&gt; attribute. Let’s dive deeper into this. From my personal experience, I can tell that &lt;code&gt;role&lt;/code&gt; is commonly confused with another HTML attribute -  &lt;code&gt;type&lt;/code&gt;. The &lt;code&gt;button&lt;/code&gt; role identifies an element as a button to assistive technologies. Simply speaking, it does not matter to screen readers whether you use &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with &lt;code&gt;role=”button”&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Whereas &lt;code&gt;type&lt;/code&gt;is specifying the context of usage. By using the proper type, you can create a button that behaves in a specific way, depending on the needs of your web page. &lt;/p&gt;

&lt;p&gt;Here are the most commonly used button types in HTML:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;type="button"&lt;/code&gt; - a simple button that does not perform any specific action.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;type="submit"&lt;/code&gt; - commonly used in forms. It submits the form data for processing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;type="reset"&lt;/code&gt; -  this type of button is also used in forms. It resets all form fields to their default values.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most browsers, the default button type is &lt;code&gt;submit&lt;/code&gt;, as in the early days of modern websites buttons were used mostly for building forms. Today they are used in many different contexts and it isn’t always clear at first glance at the markup. Therefore, it is a good practice to always declare the type of a button explicitly.&lt;/p&gt;

&lt;h4&gt;
  
  
  Aria Labels
&lt;/h4&gt;

&lt;p&gt;ARIA attributes, such as &lt;code&gt;aria-label&lt;/code&gt;, &lt;code&gt;aria-labelledby&lt;/code&gt;, &lt;code&gt;aria-describedby&lt;/code&gt; and &lt;code&gt;aria-hidden&lt;/code&gt;, among others, allow developers to specify additional information about the purpose, state, and behavior of elements. Button is just one of them. &lt;strong&gt;No ARIA is better than bad ARIA&lt;/strong&gt; - this is a quote taken from WCAG best practices and I couldn't agree more. While ARIA labels can be a powerful tool for improving web accessibility, it's important to use them judiciously and appropriately. Overuse of ARIA labels or incorrect usage can actually make web content more confusing or difficult to navigate for users with disabilities. And the truth is, for most cases, you don’t need it. ARIA can even act as a cloak and override original semantics of element i.e. &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;But in some cases aria-label is a must have to provide good accessibility. ARIA labels should be used for buttons &lt;strong&gt;when the text on the button itself doesn't fully describe the action that the button performs&lt;/strong&gt;, or when additional information is needed to clarify the purpose of the button for users with disabilities. A great example of such an element is a button with an icon, but without any label inside. These can be found in menus, product cards, wishlists etc., as seen on the picture below:&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%2F8j6bklpyorb5dfs3pgrt.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%2F8j6bklpyorb5dfs3pgrt.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;SfButton&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;:square=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Add to cart"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;SfIconAdd&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/SfButton&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Icon itself is not representative enough, at least not for someone with vision disability. When screen readers come across such a button without  aria, it will not be read to the user. So how can anyone understand what the purpose of these buttons are? Therefore we need to provide a description using the aria label property. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here are a few examples of BAD aria usage:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 html
&amp;lt;SfButton aria-label="button"&amp;gt;&amp;lt;MyCoolIcon /&amp;gt;&amp;lt;/SfButton&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 html 
&amp;lt;SfButton aria-label="Add to cart"&amp;gt;Add to cart&amp;lt;/SfButton&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In StorefrontUI we took the full flexibility approach when it comes to arias. There is no specific prop for aria labels or disabled attributes. Instead, the template allows users to assign those properties as simply as binding them to the component. All of our copy-pasteable examples has &lt;code&gt;aria-labels&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preventing button click
&lt;/h3&gt;

&lt;p&gt;Web Content Accessibility Guidelines (WCAG) requires that websites provide clear and consistent feedback to users, and that they do not present information or functionality in a way that is confusing or misleading. Using the &lt;code&gt;disabled&lt;/code&gt; attribute helps meet these standards by clearly indicating the availability of functionality. When a button is disabled, it can also provide visual feedback that helps users understand the status of the associated functionality. For example, if a "Submit" button is disabled until all required form fields have been completed, this can help users understand that their form is not yet complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Button as a link or link as a button?
&lt;/h2&gt;

&lt;p&gt;While both buttons and links serve interactive purposes, there are specific scenarios where choosing the appropriate element can enhance the user experience for individuals with disabilities who rely on assistive technologies. &lt;/p&gt;

&lt;p&gt;When the interactive element primarily triggers an action or performs a function, it is generally more appropriate to use a button instead of a link. Buttons are typically associated with actions such as submitting a form, confirming a selection, or initiating a process. &lt;/p&gt;

&lt;p&gt;So what can we do when we have a link that should look like a button? A real life example can be a “Continue to payment” button in checkout. Well, here is how we handled it at Storefront UI. Vue offers a tremendous feature that is dynamic components.&lt;/p&gt;

&lt;p&gt;The SfButton component is built using the &lt;code&gt;component is=”tag”&lt;/code&gt; and it is up to the user to decide what should be passed via tag prop. Therefore, you can render your component as a link by passing &lt;code&gt;a&lt;/code&gt;, or as a button by passing &lt;code&gt;button&lt;/code&gt;. It all depends on the context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyboard a11y
&lt;/h2&gt;

&lt;p&gt;Keyboard accessibility in button components is essential for achieving universal accessibility. While many users rely on a mouse or touch input for interaction, some individuals with disabilities may not have access to these input methods or may find it challenging to use them effectively. By ensuring that buttons can be fully operated using the keyboard, we can accommodate users who rely on alternative input devices, such as screen readers or assistive technologies, to navigate and interact with digital interfaces.&lt;/p&gt;

&lt;p&gt;Such an important feature also happens to be the most “painful” one when it comes to implementation practices. So what is so tricky in the matter?&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Focus outlines are ugly.
&lt;/h3&gt;

&lt;p&gt;One common challenge in implementing keyboard accessibility is the perception that focus outlines are aesthetically unappealing or disrupt the visual design of a website or application. Some designers and developers may opt to remove or modify the default focus &lt;code&gt;outline&lt;/code&gt; styles to achieve a specific look or to match the overall design aesthetics. However, it's important to remember that the focus outline serves a &lt;strong&gt;crucial accessibility purpose&lt;/strong&gt;, providing a visual indicator for keyboard users. While it may not align perfectly with certain design preferences, &lt;strong&gt;it is essential to prioritize accessibility over purely aesthetic considerations&lt;/strong&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%2Fef1p2sajurf37nuxrk8p.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%2Fef1p2sajurf37nuxrk8p.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Keyboard accessibility is more than just tabbing around the elements.
&lt;/h3&gt;

&lt;p&gt;​​Keyboard accessibility involves more than just enabling users to navigate through elements using the &lt;code&gt;Tab&lt;/code&gt; key. It’s much more, depending on the context. For menus and dropdowns you should also use arrows and optionally - &lt;code&gt;Home/End&lt;/code&gt; keys.&lt;/p&gt;

&lt;p&gt;Also, there is a difference in how we need to navigate through elements. Imagine using just a keyboard to navigate through the page. The problem is that tabbing relies on the DOM tree. Therefore opening fe. a modal, won’t even trigger the focus on its elements. We’ll keep on tabbing under the opened window which may cause confusion. Therefore it should not be surprising that there are so many open source solutions just for handling the keyboard navigation. In the case of the &lt;code&gt;Button&lt;/code&gt; component, the solution is as simple as using &lt;code&gt;focus-visible&lt;/code&gt; Tailwind class.&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%2Fra8fdv4g0o93fvqil2u2.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%2Fra8fdv4g0o93fvqil2u2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For more advanced components, like custom Input, we decided we decided to implement a custom solution to have full flexibility of how the outlines behave: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useFocusVisible&lt;/code&gt; - sometimes the CSS :focus-visible is not enough. In some of our components we wanted to style the wrapper element based on the nested input. That’s why we implemented a custom solution, as &lt;code&gt;:has()&lt;/code&gt; pseudo class is not fully supported yet.&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%2Fkl4u6lyypyad11ga2bup.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%2Fkl4u6lyypyad11ga2bup.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following composable/hook is showing the focus outline only for the keyboard events. The user does not have to worry about the ugly outlines ever again. This simple yet powerful solution allows us to have both - great accessibility and nice design.&lt;/p&gt;

&lt;p&gt;You can check the source code in our repo &lt;a href="https://github.com/vuestorefront/storefront-ui/tree/v2-develop/packages/sfui/frameworks/vue/composables/useFocusVisible" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Button a11y Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add descriptive text or aria label: Ensure the button has clear and descriptive text that conveys its purpose or action. Avoid using vague labels like "Click here". If button contains only icon add an aria-label with proper description.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use proper HTML structure: Ensure the  element is used instead of &lt;a&gt; (anchor) or other elements for actions and interactions. This ensures semantic correctness.&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ensure keyboard accessibility:&lt;/strong&gt; Test the button's functionality using only the keyboard. Ensure it can be focused on using the "Tab" key. Make sure the focus indicator is visible and adequately styled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implement proper focus management:&lt;/strong&gt; Ensure that the focus is moved to the button when it becomes visible or activated, and that it is not trapped or lost inappropriately.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Provide visual cues on interaction:&lt;/strong&gt; Make sure the button visibly changes its appearance when hovered over or clicked, using CSS styles such as background color, border, or shadow. This helps users understand the button's interactive nature.&lt;/p&gt;&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%2Fxrspqyilo37mrftzpjs2.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%2Fxrspqyilo37mrftzpjs2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consider color contrast:&lt;/strong&gt; Ensure that the button's text color and background color have sufficient contrast to be easily readable, especially for users with visual impairments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use ARIA attributes if necessary:&lt;/strong&gt; If the button performs a complex action or has dynamic behavior, consider using ARIA attributes like aria-expanded, aria-controls, or aria-pressed to provide additional context to assistive technologies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test with assistive technologies:&lt;/strong&gt; Use screen readers or other assistive technologies to test the button's accessibility. Ensure the button's purpose, state changes, and any associated information are communicated effectively.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Prioritizing accessibility in button design is an essential step towards building inclusive websites and applications. It may seem like a lot at first but in the end it leads toward better code and better experience for all users. &lt;/p&gt;




&lt;p&gt;Have some suggestions regarding accessibility? Feel free to &lt;a href="https://www.linkedin.com/in/anna-musial-1a8741121/" rel="noopener noreferrer"&gt;contact me&lt;/a&gt;, follow on &lt;a href="https://twitter.com/musialania" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or contribute to StorefrontUI and help make the web a more friendly place for everyone.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Framework-Agnostic Magento 2 Integration: The Next Step in eCommerce Frontend Development:</title>
      <dc:creator>Rohrig</dc:creator>
      <pubDate>Mon, 12 Jun 2023 11:50:00 +0000</pubDate>
      <link>https://forem.com/vue-storefront/the-next-step-in-ecommerce-frontend-development-a-framework-agnostic-magento-2-integration-5g5e</link>
      <guid>https://forem.com/vue-storefront/the-next-step-in-ecommerce-frontend-development-a-framework-agnostic-magento-2-integration-5g5e</guid>
      <description>&lt;p&gt;We're happy to introduce the latest Magento 2 Integration from Vue Storefront. This integration connects your frontend application smoothly with a Magento 2 backend, ensuring a seamless bridge between the two.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Framework-Agnostic Design
&lt;/h2&gt;

&lt;p&gt;A highlight of this Magento 2 Integration is its framework-agnostic design. This design includes an SDK and middleware that are framework-agnostic. What does that mean for you? It means you have the flexibility to use popular frameworks such as Nuxt and Next.js. If you choose, you can work without any framework at all. The SDK, built using TypeScript, is adaptable and can be installed in any application developed in JavaScript or TypeScript.&lt;/p&gt;

&lt;p&gt;If your project could benefit from a UI library made with eCommerce in mind, Storefront UI may be a good fit. You can find more details via this link: Storefront UI &lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;The Journey to Build the Next Storefront Boilerplate&lt;br&gt;
We're proud of the SDK and see it as a significant step forward. It is particularly useful if you're aiming to create a new frontend application that pairs with your existing Magento 2 headless store. However, we're always looking for ways to push our products further, and that's why our next move is the development of a fully functional open-source boilerplate application.&lt;/p&gt;

&lt;p&gt;This application will incorporate the new Magento 2 integration, the SDK and middleware, and of course, Storefront UI 2. We've chosen to use Nuxt 3 for this initial boilerplate due to its excellent performance, flexibility, and advanced features. As we move forward with this project, we'll document the process and share updates so you can follow along with our progress. Here's how you can stay informed:&lt;/p&gt;

&lt;p&gt;Checko out the &lt;a href="https://github.com/vuestorefront/magento2"&gt;magento-integration repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://www.youtube.com/channel/UCkm1F3Cglty3CE1QwKQUhhg"&gt;Youtube Channel&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Sign up for our &lt;a href="https://vuestorefront.io/developer-newsletter"&gt;Developer Newsletter&lt;/a&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  Inviting Collaborators
&lt;/h2&gt;

&lt;p&gt;If you're interested, you're more than welcome to join us in developing this open-source storefront using the new Magento 2 SDK and Storefront UI 2. Whether you're an eCommerce expert, a beginner in open-source contributions, or simply curious to be part of an exciting project, we'd be delighted to have you on board. Everyone has something valuable to contribute, regardless of experience level. So, let's code together and see where this project takes us. We're excited about this journey and we hope you are too. Ready to start coding?&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/vuestorefront/magento-sdk-storefront"&gt;magento-sdk-storefront Repo&lt;/a&gt;, (it's brand new.)&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/-w0U95mOb7I"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>magento2</category>
      <category>nextjs</category>
      <category>nuxt</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Investing in Developer Experience Could Be The Best Growth Hack for Your Business</title>
      <dc:creator>Filip Rakowski</dc:creator>
      <pubDate>Wed, 19 Apr 2023 11:17:52 +0000</pubDate>
      <link>https://forem.com/vue-storefront/investing-in-developer-experience-could-be-the-best-growth-hack-for-your-business-fnn</link>
      <guid>https://forem.com/vue-storefront/investing-in-developer-experience-could-be-the-best-growth-hack-for-your-business-fnn</guid>
      <description>&lt;p&gt;&lt;strong&gt;It was November 2017 when we pushed the very first version of Vue Storefront to GitHub. At that time, there were only two of us working on it after hours. One year later, we had been invited to almost every Magento event and podcast. Vue Storefront had almost 100 agencies around the world implementing it, and more than 4000 people were on its Slack. New live stores were popping up every week, and SAP had even approached us, asking for help to build its own Enterprise product.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As crazy as it sounded, this was a real story. What was our secret ingredient to success? Developer Experience.&lt;/strong&gt; Despite having no investment or marketing budget, we managed to leverage Developer Experience to our advantage. The mechanisms that led to our success can be used by other IT companies, both building and consuming developer tools to greatly increase their chances of success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience is User Experience
&lt;/h2&gt;

&lt;p&gt;For a company building tools for developers, developers are their users, &lt;strong&gt;so Developer Experience is just User Experience&lt;/strong&gt;. It’s not a secret that improving the experience of your users can be a great way to increase revenue. A &lt;a href="https://www.usertesting.com/blog/customer-experience-roi"&gt;survey by UserTesting&lt;/a&gt; found that &lt;strong&gt;86% of buyers are willing to pay more for a better customer experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Some companies, like Amazon, have become completely obsessed with their customers and their experience. It works the same way with developers - we have companies like Vercel, Netlify that are known from their obsession with great DX! By the way - &lt;a href="https://vuestorefront.io/blog/developer-experience-obsession-as-new-cdxo-heres-why-"&gt;we're obsessed about it in Vue Storefront&lt;/a&gt; too!&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience drives web tools adoption
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BuHToCDn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ip5o7d0x5zq4u98hn6bc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BuHToCDn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ip5o7d0x5zq4u98hn6bc.png" alt="Image description" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Developers are constantly looking for new and better tools that will allow them to perform tasks more efficiently or decrease the chances of failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The reason we became so successful with Vue Storefront was quite simple - we identified a pain point of a huge amount of developers and shipped something that improved their experience.&lt;/strong&gt; They lifted it up because our success was in their interest.&lt;/p&gt;

&lt;p&gt;Developers often look for tools that are easy to use, provide the required functionality, have good documentation, and have good community support. If a tool lacks these qualities, developers are more likely to switch to an alternative tool that provides a better experience. &lt;/p&gt;

&lt;p&gt;In our case, we gave Magento 2 frontend developers a way to use a modern frontend framework like Vue, and decouple their work from the PHP world. &lt;strong&gt;It was in their interest to advocate for Vue Storefront and convince the companies they worked for to use it.&lt;/strong&gt; This way, they could learn a new, exciting technology that is much more enjoyable and efficient to work with than what they were used to. &lt;/p&gt;

&lt;p&gt;Let’s take another example from the web dev tooling courtyard. Webpack, a tool that was fueling almost all modern JavaScript projects with 6 000 000 daily downloads was almost completely replaced by Vite in most of the modern frameworks and starters last year. Of course, Webpack won’t die any time soon, with its massive adoption, but Vite is now a go-to for the new projects on Vue, React and Nuxt.&lt;/p&gt;

&lt;p&gt;Speaking of which - Nuxt and Vue also became a market standards because of their amazing DX. They had no marketing budget, but happy developers advocating for their products were all they needed to get where they are now.&lt;/p&gt;

&lt;p&gt;I know what you’re thinking right now - is it really worth investing time in a migration just to make my developers happier? As a leader, or a business owner, should I even listen to their whims?&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience improves the efficiency of your team
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TggwxKZ4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k03rfvntmqw1fu8uhz0i.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TggwxKZ4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k03rfvntmqw1fu8uhz0i.jpeg" alt="Image description" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using a developer tool with great developer experience is like driving on a well-maintained highway, where you can smoothly and efficiently cruise at high speeds towards your destination. &lt;/p&gt;

&lt;p&gt;On the other hand, using a tool with a poor developer experience is like driving on a bumpy and pothole-filled road, where you constantly have to slow down and maneuver around obstacles, causing delays and frustration. Ultimately, the former allows you to get to your destination faster and with less stress, while the latter can slow you down and impede your progress.&lt;/p&gt;

&lt;p&gt;Developers spend most of their time coding, designing, and testing software applications. The experience they have while doing these tasks can have a significant impact on their performance. Good developer experience can make them more productive and more efficient, leading to timely delivery of high-quality software.&lt;/p&gt;

&lt;p&gt;While a bad developer experience can lead to inefficiencies, delays, and ultimately, higher costs. This can also impact the end-user experience, leading to unhappy customers, bad reviews, and lost business opportunities.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://www.mckinsey.com/industries/technology-media-and-telecommunications/our-insights/developer-velocity-how-software-excellence-fuels-business-performance"&gt;report from McKinsey&lt;/a&gt; shows that &lt;strong&gt;companies prioritizing developer velocity have four to five times the revenue growth of their counterparts&lt;/strong&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  It’s not always worth migrating to the next big thing
&lt;/h2&gt;

&lt;p&gt;Investing in tools that can provide better developer experience is an excellent decision, especially for large teams. However, it's essential to consider that not all migrations are worthwhile. If your current tool is already delivering good developer experience, then it's highly unlikely that migrating to a new tool will bring significant benefits that justify the costs.&lt;/p&gt;

&lt;p&gt;For example, people migrated from webpack to Vite in large numbers because webpack was incredibly slow in larger projects, leading to a significant waste of time.&lt;/p&gt;

&lt;p&gt;However, Vite has addressed this problem by being fast enough, so replacing Vite with an even faster build tool may not provide the same benefits as migrating from webpack to Vite.&lt;/p&gt;

&lt;p&gt;Therefore, it's crucial to make a well-informed decision before making any changes to your current toolset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience attracts talent
&lt;/h2&gt;

&lt;p&gt;Good developer experience can also impact employee retention and recruitment. Developers want to work for companies that provide the best tools, technologies, and working environments. Often they switch jobs just to be able to work with the new technology they’re excited about.&lt;/p&gt;

&lt;p&gt;According to a survey by Hired, a job search platform for tech talent, outdated technology is one of the top reasons why developers look for new jobs. In their &lt;a href="https://pages.hired.email/rs/289-SIY-439/images/2021%20State%20of%20Software%20Engineering%20Report.pdf"&gt;2021 State of Software Engineers report&lt;/a&gt;, they found that &lt;strong&gt;39% of software engineers surveyed cited outdated technology as a reason for leaving their job&lt;/strong&gt;, making it the second most common reason behind compensation.&lt;/p&gt;

&lt;p&gt;By providing good developer experience, businesses can retain and attract top talent, leading to better results and growth opportunities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Investing in Developer Experience (DX) can be a game-changer for IT companies building and consuming developer tools. *&lt;em&gt;DX is simply User Experience (UX) for developers, and improving it can increase revenue, drive adoption, and increase team efficiency. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;However, not all migrations are worthwhile, and it's crucial to make a well-informed decision before making any changes to your current toolset. Good DX can also impact employee retention and recruitment, leading to better results and growth opportunities. Overall, businesses should consider DX as a growth hack that can drive success and take them to the next level.&lt;/p&gt;




&lt;p&gt;I share how we build best Developer Experience on the market in Vue Storefront every month in my &lt;a href="https://vuestorefront.io/developer-newsletter"&gt;newsletter&lt;/a&gt;! Follow me on &lt;a href="https://twitter.com/filrakowski"&gt;Twitter&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/filip-rakowski-a43671129/"&gt;Linkedin&lt;/a&gt; to stay in the loop and learn something new!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>vue</category>
    </item>
    <item>
      <title>Using Custom Queries in Vue Storefront 2</title>
      <dc:creator>Jakub Andrzejewski</dc:creator>
      <pubDate>Mon, 14 Nov 2022 07:18:59 +0000</pubDate>
      <link>https://forem.com/vue-storefront/using-custom-queries-in-vue-storefront-2-2d01</link>
      <guid>https://forem.com/vue-storefront/using-custom-queries-in-vue-storefront-2-2d01</guid>
      <description>&lt;p&gt;Vue Storefront uses default GraphQL queries under the hood to fetch and modify the data in a certain e-commerce platform (or any other third party service). This is a very useful feature that can speed up your development but what if you would like to customize it to make it work a bit different?&lt;/p&gt;

&lt;p&gt;You can do so by utilizing the concept of Custom Queries. These custom values will replace the default ones to achieve completely new result. If you would like to see this on a real example, I have recorded a video some time ago, where I am showing how to use these Custom Queries in a real Vue Storefront 2 application.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/5nCGb-RCkQU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Apart from this video, I would highly recommend you to check out following links to learn more about composables and how to extend Vue Storefront 2 application to match your needs best:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.vuestorefront.io/v2/composition/composables.html" rel="noopener noreferrer"&gt;https://docs.vuestorefront.io/v2/composition/composables.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.vuestorefront.io/v2/composition/extending-graphql-queries.html" rel="noopener noreferrer"&gt;https://docs.vuestorefront.io/v2/composition/extending-graphql-queries.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>What is URL Resolving?</title>
      <dc:creator>Jakub Andrzejewski</dc:creator>
      <pubDate>Mon, 07 Nov 2022 11:27:00 +0000</pubDate>
      <link>https://forem.com/vue-storefront/what-is-url-resolving-22cb</link>
      <guid>https://forem.com/vue-storefront/what-is-url-resolving-22cb</guid>
      <description>&lt;p&gt;When building a custom storefront for your e-commerce, you eventually will run into a situation where you will need to map the products in your e-commerce platform to certain pages in the storefront. So you may ask yourself: “How should I map them then?”. This is a common problem in all e-commerce websites, especially considering the growing amount of possible attributes a product can have like sizes, colors, fabric, etc. The combination of attributes is often referred as variants. To help with that problem, you can utilize the concept of URL Resolving. &lt;/p&gt;

&lt;p&gt;As a start, we should first get a better understanding of what actually is a URL. Knowledge about this concept will allow us to dive deeper into the second section where we will be discussing the pros and cons of certain URL Resolving approaches so that you can choose and implement the one that best suits your business needs. Finally, an example of implementing Dynamic Routing in Vue Storefront 2 will be shown and discussed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an URL?
&lt;/h2&gt;

&lt;p&gt;A Uniform Resource Locator (URL) is the web address that we enter into a browser to access a web page. Web URLs are also called links and users will use them to access your website directly.&lt;/p&gt;

&lt;p&gt;URLs can have a different structure, but the most popular one can be seen 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%2Ftnfctwfusspac2jfoak5.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%2Ftnfctwfusspac2jfoak5.png" alt="URL Structure" width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s discuss each individual part of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protocol&lt;/strong&gt; - thanks to it, the browser will be able to get information by using the appropriate HTTP protocol (HTTP or HTTPS which stands for secure).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sub Domain&lt;/strong&gt; - you can have multiple subdomains like docs, prod, stage, test, etc. They can be useful for creating organized content that is related to your root domain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Root Domain&lt;/strong&gt; - it is a unique address where a website is located.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top Level Domain (TLD)&lt;/strong&gt; - usually .com, .org, .io&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path&lt;/strong&gt; - exact location of a certain page, subpage, or a file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The structure of an URL can depend on several aspects and unique needs and I will explain some of them later in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a URL Resolving?
&lt;/h2&gt;

&lt;p&gt;URL Resolving concept allows you to connect certain products in your e-commerce platform with a certain page and URL in your storefront. This means that whenever a customer on your online store will click on the product card to see more details (and possibly add this product to the cart) he/she will be redirected to an appropriate URL. &lt;/p&gt;

&lt;p&gt;Let’s take a look at the example from our Vue Storefront playground.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Category Page&lt;/strong&gt; - &lt;a href="https://demo.vuestorefront.io/c/household-items" rel="noopener noreferrer"&gt;https://demo.vuestorefront.io/c/household-items&lt;/a&gt; - the path consists of &lt;code&gt;/c&lt;/code&gt;, which stands for the category, and &lt;code&gt;/household-items&lt;/code&gt; which is a category slug or category name.&lt;/li&gt;
&lt;/ul&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%2F0z1pxp212qjm5re09zxp.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%2F0z1pxp212qjm5re09zxp.png" alt="Vue Storefront Category URL" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product Page&lt;/strong&gt; - &lt;a href="https://demo.vuestorefront.io/p/77/fog-linen-chambray-towel-beige-stripe" rel="noopener noreferrer"&gt;https://demo.vuestorefront.io/p/77/fog-linen-chambray-towel-beige-stripe&lt;/a&gt; - the path consists of &lt;code&gt;/p&lt;/code&gt;, which stands for product, &lt;code&gt;/77&lt;/code&gt;, which is a product ID, and &lt;code&gt;/fog-linen-chambray-towel-beige-stripe&lt;/code&gt; which is a product slug or name.&lt;/li&gt;
&lt;/ul&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%2Fsvlb5q0bttizjkp42x4x.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%2Fsvlb5q0bttizjkp42x4x.png" alt="Vue Storefront Product URL" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vue Storefront also supports international e-commerce so when we switch the language of our website, it will automatically update the URLs to include the correct locale, i.e. &lt;a href="https://demo.vuestorefront.io/de/c/household-items" rel="noopener noreferrer"&gt;https://demo.vuestorefront.io/de/c/household-items&lt;/a&gt;, where &lt;code&gt;/de&lt;/code&gt; is a language/region/locale.&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%2Fvo8xzz7vnorv0c6yglea.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%2Fvo8xzz7vnorv0c6yglea.png" alt="Vue Storefront Multi Language URLs" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Implementing custom dynamic routing in both Vue Storefront 2 and Nuxt 2 is pretty straightforward and it is explained really well in the following articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vue Storefront 2 - &lt;a href="https://docs.vuestorefront.io/v2/getting-started/layouts-and-routing.html#manually-adding-and-modifying-routes" rel="noopener noreferrer"&gt;https://docs.vuestorefront.io/v2/getting-started/layouts-and-routing.html#manually-adding-and-modifying-routes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nuxt 2 - &lt;a href="https://nuxtjs.org/docs/features/file-system-routing#extendroutes" rel="noopener noreferrer"&gt;https://nuxtjs.org/docs/features/file-system-routing#extendroutes&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Vue Storefront, if you want to manually add your custom routes or modify some already provided, use the &lt;code&gt;extendRoutes&lt;/code&gt; function in the &lt;code&gt;nuxt.config.js&lt;/code&gt;. This function has two properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;routes&lt;/code&gt; — an array of already registered routes. You can &lt;code&gt;push&lt;/code&gt; or delete entries from it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resolve&lt;/code&gt; — helper function for resolving Vue.js components based on their paths in the project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the sake of example, let's assume that we created a &lt;code&gt;pages/AboutUs.vue&lt;/code&gt; component, but we want to use the &lt;code&gt;/company/about-us&lt;/code&gt; route instead of auto-registered &lt;code&gt;/aboutus&lt;/code&gt;. There are two approaches we could take.&lt;/p&gt;

&lt;p&gt;The first approach is to &lt;strong&gt;delete the existing route&lt;/strong&gt; and &lt;strong&gt;register a new route&lt;/strong&gt; with a different path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.js&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;extendRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Delete automatically registered route&lt;/span&gt;
      &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/AboutUs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Re-register the same component but with different path&lt;/span&gt;
      &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AboutUs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/company/about-us&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pages/AboutUs.vue&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="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;h2&gt;
  
  
  Comparison of URL Resolving patterns
&lt;/h2&gt;

&lt;p&gt;URL Resolving can be achieved in many different ways. Let’s compare some of the popular URL Resolving patterns that can be used in e-commerce websites. Depending on your website requirements, you may implement some or all of the following patterns.&lt;/p&gt;

&lt;p&gt;For the comparison, let’s assume that before the slash there is something like &lt;code&gt;my-awesome-store.com&lt;/code&gt; and &lt;code&gt;${product}&lt;/code&gt; is a unique product slug or ID. These are the patterns that I have found being used in several e-commerce applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/[p/c]/${product}&lt;/code&gt; - In this approach, the first part of the URL path is a business domain, for example, a category/collection or a product with only the first letter from the domain. After it, there is a unique product ID or a slug. This approach allows easily distinguish the certain part of the website (i.e. I am currently in the category X and the ID is Y)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/${product}&lt;/code&gt; - In this approach, the only part of the URL path is the unique product ID or slug. This approach may not be the best if you prefer to have meaningful URLs because your URL in this approach might look like this &lt;code&gt;my-awesome-website/123-abc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/p-${product}&lt;/code&gt; - In this approach, the only part of the URL path is the unique product ID or slug and a prefix of a business domain. While this might look good, both creating links and fetching data about certain pages can be more difficult as you would have to respect add and strip a domain prefix to get a product ID. Not a big deal, but you would have to remember about it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/${product}-p&lt;/code&gt; - Similar pattern as the one above but in this case, we are adding an appendix with a business domain. It shares similar issues as the one above.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/any-text-p-${product}&lt;/code&gt; - In this approach, the only part of the URL path is the unique product ID or slug, a prefix of a business domain, and some text. By going with this pattern, you will have even more unique URLs but at the cost of hard-to-read and navigate links, i.e. &lt;code&gt;my-awesome-store/some-text-p-123abc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/any-text-p${product}&lt;/code&gt; - Similar to the one above but there is no hyphen between a product letter and the product ID. It might be even harder to work with from the frontend.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/category/product/${product}&lt;/code&gt; - In this approach, the URLs are created adequately to the actual page path so that we will have a product with a certain ID that is a part of a certain category. This may be too complex for some long names.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/category/subcategory/product/${product}&lt;/code&gt; - Similar approach to the one above but even more nested. This kind of URL paths could work as breadcrumbs but in some cases might be too complex.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are some of the most popular URL resolving / Dynamic Routing patterns I have observed in several e-commerce websites.&lt;/p&gt;

&lt;p&gt;If you need internationalization in your website you can also implement the following pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/en_US/*&lt;/code&gt; - in it, as the first part of the URL path you will be adding an appropriate locale and then category/product and a product id. With this approach, you can configure different languages and locale in the frontend application and you don’t have to worry about domains (i.e. .pl/.de).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, if you do not want to implement routing in the frontend of your website, you can also implement third-party (i.e. Content Management System - CMS) based URL Resolving. It can take the whole routing issue out of the frontend but it will be a single point of failure (if CMS will be down, your whole website will be down as well). Below, you can see a visual representation of this approach:&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%2Fzhv8fg4oswbrkp5aoz2b.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%2Fzhv8fg4oswbrkp5aoz2b.png" alt="CMS acting as URL Resolver" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the right URL structure is important?
&lt;/h2&gt;

&lt;p&gt;URL Structure is important for two main reasons; &lt;strong&gt;User Experience&lt;/strong&gt; and &lt;strong&gt;Google Search Position&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The first aspect is especially important for making your URL’s be logically structured and easy to navigate by the users of your website. URLs such as the one below are easy to understand for the users and include content keywords that are useful for SEO:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.vuestorefront.io/commercetools/guide/ssr-cache.html" rel="noopener noreferrer"&gt;https://docs.vuestorefront.io/commercetools/guide/ssr-cache.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;URLs such as the one below are not descriptive and can be indexed by Search Engines as duplicates:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.my-awesome-website.com/index.php?id_123" rel="noopener noreferrer"&gt;https://www.my-awesome-website.com/index.php?id_123&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The second aspect is even more important for SEO. Good URL structure can put your website higher in Google search results. You can read more about it here → &lt;a href="https://www.semrush.com/blog/pagerank/" rel="noopener noreferrer"&gt;https://www.semrush.com/blog/pagerank/&lt;/a&gt;. Root domains have the most PageRank but it does not mean that you have to add all-important keywords there. Too many keywords will make your website difficult to read and crawl by Google. Instead, make sure to include what is necessary and place it under the path.&lt;/p&gt;

&lt;p&gt;Remember to create and update the sitemap.xml file so that your website will be correctly crawlable so that Google could keep Search Engine Results Pages up to date. If you are interested in how to add sitemap.xml to Vue Storefront 2 project, check out the following module that you can add to your project and control the sitemap easily → &lt;a href="https://sitemap.nuxtjs.org/" rel="noopener noreferrer"&gt;https://sitemap.nuxtjs.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The right URL structure  can help Google crawl and index your website better but also mitigate the following problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Getting the same content multiple times - in some scenarios, two different websites can lead to the same product and it is also how Google will index it. Let’s take a look at the following example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/p/dress&lt;/code&gt; and &lt;code&gt;/p?id=456&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both URLs can lead to the same product page and Google will index it incorrectly.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;For some dynamic parameters or queries (like timestamps) Google Crawler may think that our store contains an infinite number of pages. Let’s take a look at the following example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/checkout?expiresAt=10:20pm&lt;/code&gt; and &lt;code&gt;/checkout?expiresAt=10:30pm&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These URLs can be then treated as different pages even though the only difference is a parameter.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;When using fragment identifiers (#fragment) Google may not see the difference between the two URL and because of that, the content can be missed. Let’s take a look at the following example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;p/dress#red&lt;/code&gt; and &lt;code&gt;p/dress#green&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two URLs even though is meant to display different products, will be indexed by Google as the same.&lt;/p&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Working with product variants has also proven to be quite difficult in terms of correct URLs and Google searching algorithms. Variants can be implemented purely in JavaScript as a selection that will be then passed as a param or query for a request that will fetch the data from the e-commerce platform or can be accessed from the current URL. For the second approach, you can choose either of the following approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A path segment, such as &lt;code&gt;/dress/black&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A query parameter, such as &lt;code&gt;/dress?color=black&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this situation, to show Google which variant is the best to show, you can use canonical URL’s. For example, if the default value of the &lt;code&gt;color&lt;/code&gt; query parameter for a Dress is &lt;code&gt;red&lt;/code&gt;, then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;/dress&lt;/code&gt; as the canonical URL for all Dress variants&lt;/li&gt;
&lt;li&gt;For a red dress, use &lt;code&gt;/dress&lt;/code&gt; (and not &lt;code&gt;/dress?color=red&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;For a black dress, use &lt;code&gt;/dress?color=black&lt;/code&gt; with canonical URL &lt;code&gt;&amp;lt;link rel="canonical" href="https://my-awesome-store/dress" /&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  URL Best Practices
&lt;/h2&gt;

&lt;p&gt;Below, I am listing some URL best practices for you to use in your website. No matter what kind of application you are building, most or all of these recommendations should make your website more crawlable and user-friendly. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Construct a good URL structure&lt;/strong&gt; - as explained in previous section, URL’s that are simple and self-explanatory are best for both the users and the search engines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use hyphens to separate words&lt;/strong&gt; - If your path is constructed out of several words, make sure to divide them by using hyphens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t use stop words&lt;/strong&gt; - Words like and, or, for are not meaningful for the SEO so make sure to avoid them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use lowercase&lt;/strong&gt; - pretty self-explanatory, instead of /About use /about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirect old URLs&lt;/strong&gt; - In the case of out of date URLs, you can add a redirect to them so that users will be redirected to the new and up-to-date page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t use dates in the Path&lt;/strong&gt; - If you are building a blog or your products contain dates, do not put them into the URL path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spell out numbers&lt;/strong&gt; - If your website URL contains the number, i.e. &lt;a href="http://example.com/3-best-tools" rel="noopener noreferrer"&gt;example.com/3-best-tools&lt;/a&gt; you can instead spell out the number like example.com/three-best-tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use HTTPS&lt;/strong&gt; - Using a secure protocol will ensure security for your users and result in better trust. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hide www prefix&lt;/strong&gt; - If your website is using this prefix, you can safely hide it. Nowadays, the majority of the websites are known for being a World Wide Web resource.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use country domain or subdirectory&lt;/strong&gt; - If you are building an international website, you can use a country domain (example.pl) or subdirectory (example.com/pl/)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.designpowers.com/blog/url-best-practices" rel="noopener noreferrer"&gt;https://www.designpowers.com/blog/url-best-practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/docs/advanced/guidelines/url-structure" rel="noopener noreferrer"&gt;https://developers.google.com/search/docs/advanced/guidelines/url-structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.meticulosity.com/blog/best-practices-ecommerce-url-structures" rel="noopener noreferrer"&gt;https://www.meticulosity.com/blog/best-practices-ecommerce-url-structures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ecommerce-platforms.com/glossary/what-is-a-website-url" rel="noopener noreferrer"&gt;https://ecommerce-platforms.com/glossary/what-is-a-website-url&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/docs/specialty/ecommerce/designing-a-url-structure-for-ecommerce-sites" rel="noopener noreferrer"&gt;https://developers.google.com/search/docs/specialty/ecommerce/designing-a-url-structure-for-ecommerce-sites&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vue</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to deal with caching and dynamic content in Nuxt?</title>
      <dc:creator>Filip Rakowski</dc:creator>
      <pubDate>Wed, 05 Oct 2022 13:10:19 +0000</pubDate>
      <link>https://forem.com/vue-storefront/how-to-deal-with-caching-and-dynamic-content-2ilk</link>
      <guid>https://forem.com/vue-storefront/how-to-deal-with-caching-and-dynamic-content-2ilk</guid>
      <description>&lt;p&gt;&lt;strong&gt;The cache is one of the most powerful weapons when you want to make your web application fast. Delivering static, pre-rendered pages from the closest location can result in a great performance, but &lt;a href="https://dev.to/jacobandrewsky/leveraging-cache-in-vuejs-and-nuxtjs-4b26"&gt;setting it up on the server&lt;/a&gt; without making your frontend application ready to be cached can lead to unpleasant outcomes. In the best-case scenario, you will annoy your users by breaking the app. Worst-case scenario, you will violate GDPR rules. This is certainly a situation that none of us want to experience! Don't worry - from this article, you will learn everything you need to know to make your Server-Side or Statically generated Single Page Applications ready to be cached (and hopefully distributed through a CDN)!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Side Note:&lt;/strong&gt; I use Nuxt.js in code examples, but the concepts and solutions are not tied to any framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  We cache HTML templates to improve performance.
&lt;/h2&gt;

&lt;p&gt;Let's learn some theory first and familiarize ourselves with the concept of full-page caching. I found a very good one on Branch CMS documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Full page cache" means that the entire HTML output for the page will be cached. Subsequent requests for the page will return the cached HTML instead of trying to process and re-build the page, thus returning a response to the browser much faster&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you open a Server-Side Rendered application, the JavaScript code first runs on the server to generate the HTML file containing all your components and data rendered. This static file is then sent to the browser. Single-Page Application framework like Vue takes control over it and makes it dynamic. This process is called hydration. At this stage your application is fully interactive.&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%2Fscxm9lm88cn6r25yumyp.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%2Fscxm9lm88cn6r25yumyp.png" alt="How Vue hydration works"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Generating the static HTML file on the server can take a few seconds. A few seconds when your user sees a blank screen and potentially leaves your website. &lt;strong&gt;According to &lt;a href="https://think.storage.googleapis.com/docs/mobile-page-speed-new-industry-benchmarks.pdf" rel="noopener noreferrer"&gt;Google's study&lt;/a&gt;, 1-3 seconds of load time increases the bounce rate probability by 32%!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To deal with this significant performance downside of Server-Side Rendering, developers started caching the first generated response and sending it to others. Thanks to that, we perform the time-consuming rendering step only once and send its outcome immediately to all users requesting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cached template has to be generic.
&lt;/h2&gt;

&lt;p&gt;When we cache an HTML page generated by a SPA SSR framework like Nuxt.js it contains both the generated HTML &lt;strong&gt;and application state as inlined JavaScript object&lt;/strong&gt; after the server-side code execution.&lt;/p&gt;

&lt;p&gt;Because of that &lt;strong&gt;you need to ensure that there is no session-specific content in both template and application state after server-side code execution.&lt;/strong&gt; Otherwise, it will be cached and served to all users.&lt;/p&gt;

&lt;p&gt;A session-specific content contains data and markup that can be different depending on a user or device - for example: displayed user or device name, conditional rendering statement for specific devices 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%2F0eeun5v15n8dsfjccm8m.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%2F0eeun5v15n8dsfjccm8m.png" alt="Example of dynamic content"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don’t remove session-specific content from the cached template, everyone will see a page with the data of the first user.&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%2F75iaube2wvegndc8i37p.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%2F75iaube2wvegndc8i37p.png" alt="Caching dynamic content makes everyone see the content of the first visitor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Execute session-specific content in the browser
&lt;/h2&gt;

&lt;p&gt;Our applications are rarely entirely generic in the real world, though. There is nothing wrong with it - almost every website has some content that is dynamic and depends on a current user session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;it’s important to make the dynamic content rendering happen in an environment that is isolated for each user like their browser&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This way the generic content, generated on the server is distributed immediately to all of your users and then, in their browsers, parts that make the experience tailored specifically for them are injected.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to deal with session-specific templates
&lt;/h2&gt;

&lt;p&gt;The first thing that comes to mind when thinking about session-specific content is the one of the currently authenticated user. We obviously don't want other users to receive a page that is filled with someone else's data.&lt;/p&gt;

&lt;p&gt;Let’s see an example of excluding session-specific parts of &lt;code&gt;AppHeader&lt;/code&gt; component from rendering on the server.&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%2Fwxv1c7mnsj8vxc62fhei.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%2Fwxv1c7mnsj8vxc62fhei.png" alt="Dynamic content in AppHeader"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Nuxt.js you can skip the server-side rendering of components wrapped with a built-in &lt;a href="https://nuxtjs.ir/api/components-client-only" rel="noopener noreferrer"&gt;ClientOnly&lt;/a&gt; component.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="c"&gt;&amp;lt;!-- components/AppHeader.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;AppLogo&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Keep the elements that are session-specific as client-only --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ClientOnly&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"!isLoggedIn"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"LogIn()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log in&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"logOut()"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log out&lt;span class="nt"&gt;&amp;lt;/butt&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AppCart&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AppWishlist&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ClientOnly&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The server-side-rendered code of the above component will look more or less like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./assets/logo.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then in the browser, the rest of the elements are added dynamically.&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%2F7801rl4nonvdqfjl41e5.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%2F7801rl4nonvdqfjl41e5.png" alt="How static and dynamic content are rendered"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Improving the user experience with loading indicators and placeholders
&lt;/h3&gt;

&lt;p&gt;Usually, the hydration happens in milliseconds, but on some devices, it could take even more than 10 seconds. Seeing empty elements on the template can be misleading and deliver a bad user experience. Users can feel that the website is broken, or they could miss some important elements that are loaded later. We should let them know that some elements are not there yet.&lt;/p&gt;

&lt;p&gt;The most common and effective technique of indicating yet-to-be-loaded content are skeletons.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Skeleton screens &lt;em&gt;**are blank pages that are progressively populated with content, such as text and images, as they become available.&lt;/em&gt;* (from ***&lt;em&gt;uxdesign.cc)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://www.lukew.com/ff/entry.asp?1797" rel="noopener noreferrer"&gt;Skeletons are much better option than spinners&lt;/a&gt; because they give user a hint of what content they can expect.&lt;/p&gt;

&lt;p&gt;Let's take a look at a practical example of Linkedin. When you enter the page, not everything is loaded yet. You immediately receive a cached skeleton home screen, and the data is loaded progressively in the browser:&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%2Fnnazxgcx1z1s03o7qyxz.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%2Fnnazxgcx1z1s03o7qyxz.png" alt="Linkedin skeleton screens"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Nuxt.js &lt;code&gt;ClientOnly&lt;/code&gt; component has a placeholder slot we can use to display a skeleton for the content that will gradually load on the client side. I used &lt;a href="https://docs.storefrontui.io/?path=/docs/components-atoms-skeleton--common" rel="noopener noreferrer"&gt;SfSkeleton&lt;/a&gt; component example from &lt;a href="https://vuestorefront.io/storefront-ui" rel="noopener noreferrer"&gt;Storefront UI&lt;/a&gt; - eCommerce UI library we’ve built in Vue Storefront for our users.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="c"&gt;&amp;lt;!-- components/AppHeader.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;AppLogo&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Keep the elements that are session-specific as client-only --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ClientOnly&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Display a placeholder until the page is hydrated --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#placeholder&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;SfSkeleton&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"paragraph"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"!isLoggedIn"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"LogIn()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log in&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"logOut()"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log out&lt;span class="nt"&gt;&amp;lt;/butt&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AppCart&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AppWishlist&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ClientOnly&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fl6f53n7zxz2ls8xwdn54.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%2Fl6f53n7zxz2ls8xwdn54.png" alt="Skeleton in AppHeader"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡 Sometimes it is better to just use the generic state of the component as placeholder&lt;/p&gt;

&lt;h2&gt;
  
  
  What about the data?
&lt;/h2&gt;

&lt;p&gt;Until now we talked only about the session-specific templates but what about data fetched inside components?&lt;/p&gt;

&lt;p&gt;Most of the SSR frameworks like Nuxt or Next send the server-side state at the bottom of &lt;code&gt;index.html&lt;/code&gt; file that comes with the rendered HTML, so you don’t have to fetch the data twice - on the server and then on the client.&lt;/p&gt;

&lt;p&gt;If you inspect your Nuxt or Next SSR response, you will see a similar piece of code injected at the end of the &lt;code&gt;body&lt;/code&gt; tag:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;window.__NUXT__=&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:[{},{&lt;/span&gt;&lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sunt aut facere repellat provident occaecati excepturi optio reprehenderit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quia et suscipit&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;suscipit recusandae consequuntur expedita et cum&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;reprehenderit molestiae ut ut quas totam&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;nostrum rerum est autem sunt rem eveniet architecto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;qui est esse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;est rerum tempore vitae&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;sequi sint nihil reprehenderit dolor beatae ea dolores neque&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;fugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;qui aperiam non debitis possimus qui neque nisi nulla&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ea molestias quasi exercitationem repellat qui ipsa sit aut&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;et iusto sed quo iure&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;voluptatem occaecati omnis eligendi aut [...]&lt;/span&gt;&lt;span class="dl"&gt;"&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;serverRendered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Because of that, we have to ensure that no session-specific data is in the cached HTML just like we did with the templates.&lt;/p&gt;

&lt;p&gt;The data can be fetched from two places:&lt;/p&gt;

&lt;h3&gt;
  
  
  Inside the component
&lt;/h3&gt;

&lt;p&gt;When the data is fetched inside the component, the case is simple. &lt;strong&gt;If the whole component is wrapped with &lt;code&gt;ClientOnly&lt;/code&gt; like in the example above the execution of the component code is completely skipped on the server&lt;/strong&gt;, so the data is fetched for the first time when its executed in the browser.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="c"&gt;&amp;lt;!-- components/AppHeader.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;AppLogo&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- None of the code from components inside ClientOny will execute on the server --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ClientOnly&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AppCart&lt;/span&gt; &lt;span class="na"&gt;:items=&lt;/span&gt;&lt;span class="s"&gt;"user.cart.items"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ClientOnly&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;


&lt;span class="c"&gt;&amp;lt;!-- components/AppCart.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- cart template --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&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="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchCart&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchCart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you’re REST APIs and have multiple requests on your page, executing the fetching logic inside the components is best. It’s much harder to miss the session-specific data if it’s not all around your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  In the parent component
&lt;/h3&gt;

&lt;p&gt;If you follow a smart/dumb components pattern aka. presentational/container (explained well by Dan Abramov &lt;a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0" rel="noopener noreferrer"&gt;here&lt;/a&gt;,  and no longer a go-to approach since the introduction of React Hooks/Composition API) and fetch all the logic in the parent component, you have to take care of the session-specific data separately in the parent component.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="c"&gt;&amp;lt;!-- components/AppHeader.vue --&amp;gt;&lt;/span&gt;&lt;br&gt;
&lt;span class="nt"&gt;&amp;lt;AppHeader&amp;gt;&lt;/span&gt;&lt;br&gt;
  &lt;span class="nt"&gt;&amp;lt;AppLogo&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;&lt;br&gt;
    &lt;span class="c"&gt;&amp;lt;!-- None of the code from components inside ClientOny will execute on the server --&amp;gt;&lt;/span&gt;&lt;br&gt;
  &lt;span class="nt"&gt;&amp;lt;ClientOnly&amp;gt;&lt;/span&gt;&lt;br&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;&lt;br&gt;
    &lt;span class="nt"&gt;&amp;lt;AppCart&lt;/span&gt; &lt;span class="na"&gt;:items=&lt;/span&gt;&lt;span class="s"&gt;"user.cart.items"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;&lt;br&gt;
  &lt;span class="nt"&gt;&amp;lt;/ClientOnly&amp;gt;&lt;/span&gt;&lt;br&gt;
&lt;span class="nt"&gt;&amp;lt;/AppHeader&amp;gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;br&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&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="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchCart&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="c1"&gt;// onMounted runs only in the browser (client context)&lt;/span&gt;&lt;br&gt;
&lt;span class="nf"&gt;onMounted&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;br&gt;
 &lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchCart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="c1"&gt;// ...other logic&lt;/span&gt;&lt;br&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Summary&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;To make your app cacheable and CDN-ready you have to avoid session-specific content in the cached content rendered on the server.&lt;/p&gt;

&lt;p&gt;You have to render your application in a few steps - a generic one that renders most of the page and a session-specific one that injects dynamic parts.&lt;/p&gt;




&lt;p&gt;Liked the article? Follow me on &lt;a href="https://twitter.com/filrakowski" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; to get daily tips about web development.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>vue</category>
      <category>react</category>
    </item>
    <item>
      <title>Welcome to the new Vue Storefront Developer Portal</title>
      <dc:creator>Heitor Ramon Ribeiro</dc:creator>
      <pubDate>Mon, 05 Sep 2022 02:13:22 +0000</pubDate>
      <link>https://forem.com/vue-storefront/welcome-to-the-new-vue-storefront-developer-portal-1glc</link>
      <guid>https://forem.com/vue-storefront/welcome-to-the-new-vue-storefront-developer-portal-1glc</guid>
      <description>&lt;p&gt;Have you ever wondered why educational material and documentation cannot have a nice DX?&lt;/p&gt;

&lt;p&gt;Why can't companies create a concise experience throughout their material and make it easy to understand what is needed to start developing or simple tutorials to help your daily tasks?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I also feel the same, and dang; this sucks 😟 - Heitor Ramon&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's why we, the community team at Vue Storefront, decided to create a developer portal to combine all the information you need to start developing with Vue Storefront and help you with those pesky questions, which sometimes require a lot of research and Googling.&lt;br&gt;
We’d like to introduce the new Vue Storefront Developer portal, &lt;a href="https://developer.vuestorefront.io"&gt;https://developer.vuestorefront.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our portal concept is a unique place for all the Vue Storefront developers to find answers, acquire new knowledge, get the latest news related to the Vue Storefront community and develop even faster with Vue Storefront.&lt;/p&gt;

&lt;p&gt;Do you like open-source projects? So do we. We made the portal open-source and chose the best future-proof tech stack for a Vue ecosystem project. Using &lt;strong&gt;Nuxt 3&lt;/strong&gt;, &lt;strong&gt;nuxt-content&lt;/strong&gt;, and more.&lt;/p&gt;

&lt;p&gt;As an open-source project, you are more than welcome to participate in the decisions regarding the roadmap, features, enhancements, translation, and much more. We want to create a friendly and welcoming place for our community members.&lt;/p&gt;

&lt;p&gt;Right now, our portal has a documentation hub , an easy link to our community, and a video section. All to help our developers find the information they need, quickly.There is more to come, full cross documentation search, certifications and badges, an integrated community area, user area, events area, a single stop for all the docs (&lt;em&gt;yes, all the docs will be available in the portal)&lt;/em&gt;, and much more.&lt;/p&gt;

&lt;p&gt;As an open-source project, we are happy to have your participation in the decisions and coding of the portal. The portal being open-source is a way for our community to empower it and choose what is best for it.&lt;/p&gt;

&lt;p&gt;There will be an open roadmap with the features planned by the community team, but we are always available for new features, enhancements, fixes, and more. Check the code of the portal at &lt;a href="https://github.com/vuestorefront/developer.vuestorefront.io"&gt;https://github.com/vuestorefront/developer.vuestorefront.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Come visit our portal, and let's share this amazing news with all of your developer friends and community.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>portal</category>
      <category>dx</category>
      <category>education</category>
    </item>
    <item>
      <title>How to add custom API methods to Vue Storefront 2</title>
      <dc:creator>Jakub Andrzejewski</dc:creator>
      <pubDate>Mon, 29 Aug 2022 05:50:08 +0000</pubDate>
      <link>https://forem.com/vue-storefront/how-to-add-custom-api-methods-to-vue-storefront-2-4gjj</link>
      <guid>https://forem.com/vue-storefront/how-to-add-custom-api-methods-to-vue-storefront-2-4gjj</guid>
      <description>&lt;p&gt;Vue Storefront allows you to scaffold your next E-Commerce website in minutues. You can choose from a variety of platforms like Magento, Shopify, Commercetools, and many more!&lt;/p&gt;

&lt;p&gt;In this tutorial, I would like to guide you through the process of adding a custom API method to create a new function for your E-Commerce. It will allow you to have a completely new endpoint extension that could send a new request, a GraphQL query/mutation, or something completely different as well.&lt;/p&gt;

&lt;p&gt;For this tutorial, I will be using Vendure integration, but you are free to choose any E-Commerce integration you want. The process is the same for all these integration with a small difference depending on the API client that the integration is using (either Apollo GraphQL or Axios REST).&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;First of all, you would need to register a new &lt;code&gt;extension&lt;/code&gt; in a &lt;code&gt;middleware.config.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;vendure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vue-storefront/vendure-api/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;uri&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;GRAPHQL_API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="c1"&gt;// to be used later with authentication&lt;/span&gt;
          &lt;span class="na"&gt;tokenMethod&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;TOKEN_METHOD&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;extensions&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;extendApiMethods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;testApiMethod&lt;/span&gt;&lt;span class="p"&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;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&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;result&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`
              query products {
                products {
                  items {
                    id
                    customFields
                  }
                }
              }
              `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fetchPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's focus on the part from the &lt;code&gt;extensions&lt;/code&gt; as everything else is just a default value for the certain integration to work properly. &lt;/p&gt;

&lt;p&gt;Extensions will accept a parameter called extensions, and it is important to return a spread extensions in the final return. Otherwise, the default extensions in the integration may stop working so just please remember to return them as well. Next, we can see the structure of a new extension. Let's take a look at it with more details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;extendApiMethods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;testApiMethod&lt;/span&gt;&lt;span class="p"&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;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&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;result&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`
      query products {
        products {
          items {
            id
            customFields
          }
        }
      }
      `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fetchPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First of all, we need to name our new extension. It is advised to give it some unique name (i.e. &lt;code&gt;fetch-multiple-products&lt;/code&gt;) but in this case, I am just showing a test example. Next, we will have an &lt;code&gt;extendApiMethods&lt;/code&gt; object where each property can represent its own new or extended API method. Each API method have access to the destructured client parameter. This parameter can then be used to call certain requests, queries, or mutations really easily from the frontend. This example shows how to fetch the multiple products from the GraphQL API by using the custom API method (the new one as Vendure does not have this query implemented - products are being fetched by using different query)&lt;/p&gt;

&lt;p&gt;Then, you can use the newly created API method in your component or a page like following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$vendure&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useVSFContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;onSSR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &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;await&lt;/span&gt; &lt;span class="nx"&gt;$vendure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;testApiMethod&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;If everything worked correctly, you should see a result of a products query in the console where the project is running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Well done! You have just implemented a completely new API method in your Vue Storefront 2 project. This knowledge will allow you to customize it even more to suit your business needs better.&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>vue</category>
      <category>typescript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Everything you need to know about Web Performance (in 5 Minutes)</title>
      <dc:creator>Filip Rakowski</dc:creator>
      <pubDate>Thu, 23 Jun 2022 15:00:53 +0000</pubDate>
      <link>https://forem.com/vue-storefront/everything-you-need-to-know-about-web-performance-as-a-dev-in-5-minutes-450l</link>
      <guid>https://forem.com/vue-storefront/everything-you-need-to-know-about-web-performance-as-a-dev-in-5-minutes-450l</guid>
      <description>&lt;p&gt;&lt;strong&gt;I hear a lot of people saying that web performance is hard. Honestly, I don't think that's true. It could feel complex and intimidating at first glance because there is a lot of domain-specific naming, metrics, etc but to build a fast website you don't need to know them. You only need a basic understanding of what influences the speed of your website the most and make sure you have it under control. Believe me or not, you can learn this in about 5 minutes. Let's see if I'm right!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What influences your app performance?
&lt;/h2&gt;

&lt;p&gt;Let's start with identifying all the aspects that influence your app performance. I find this mental model most useful when thinking about web performance:&lt;/p&gt;

&lt;p&gt;There are essentially three "steps" that sum up the overall loading performance of your app&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Server-side execution&lt;/strong&gt; - First the HTML document has to be generated on the server. In some cases, this step costs us nothing because it's already generated (static sites).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network&lt;/strong&gt; - The generated HTML document has to travel through wires and routers to arrive in the user's browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-side execution&lt;/strong&gt; - The document needs to be parsed, and dependencies (CSS, JavaScript) have to be downloaded and executed. Once it's all done our page is fully loaded.&lt;/li&gt;
&lt;/ol&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%2Fzejj3z1nnu6rrxwg9i7y.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%2Fzejj3z1nnu6rrxwg9i7y.png" alt="Response Journey"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimizing server-side execution
&lt;/h2&gt;

&lt;p&gt;If you're building a SPA (&lt;strong&gt;Single Page Application&lt;/strong&gt;) there is a high chance you're also adopting SSR (&lt;strong&gt;Server-Side Rendering&lt;/strong&gt;). In that case, the same code will run both on the server and the client sides.&lt;/p&gt;

&lt;p&gt;The best code is the one that never has to run so you should first consider SSG (Static Site Generation). If it's not an option and you're sticking to SSR, make heavy use of full-page caching and distribute cached content through CDN.&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%2Fgk250d9agmxwpd6g6cbs.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%2Fgk250d9agmxwpd6g6cbs.png" alt="The fastest code is the one you don't need to run"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some pages will have to be generated on the server during runtime and just cannot be cached. Of those, make sure to fetch only fast, essential data on the server and make less important, and slower API calls on the client-side. This way you will significantly improve your Time to First Byte.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimizing Network
&lt;/h2&gt;

&lt;p&gt;Optimizing the networking part boils down to 4 main rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ship the smallest possible assets.&lt;/strong&gt; The bigger they are, the longer it takes to download them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid chaining network requests&lt;/strong&gt; (making one request depending on another) and try to download them in parallel.Avoid using multiple external domains in the critical path. Establishing a connection with all of them will take more time than downloading everything from one source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache static assets&lt;/strong&gt; (HTML, CSS JS) through a Service Worker.&lt;/li&gt;
&lt;/ol&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%2F9va5rci6lghh7iy2hlzd.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%2F9va5rci6lghh7iy2hlzd.png"&gt;&lt;/a&gt;&lt;br&gt;Source: &lt;a href="https://developer.chrome.com/blog/app-shell/" rel="noopener noreferrer"&gt;https://developer.chrome.com/blog/app-shell/&lt;/a&gt;
  &lt;/p&gt;

&lt;p&gt;If you take care of that there is a much smaller chance you will run into performance bottlenecks on the network part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimizing client execution
&lt;/h2&gt;

&lt;p&gt;This is where we, frontend developers, have the most power and where we also make a lot of mistakes! From my experience, 90% of frontend performance bottlenecks are around &lt;strong&gt;rendering time&lt;/strong&gt; (that one can be easily solved with cached SSR and SSG output) and &lt;strong&gt;interactivity&lt;/strong&gt; (that one can be solved by reducing amount of JavaScript in so-called critical rendering path through code splitting, lazy loading and being cautious with adding new libs to the clients).&lt;/p&gt;

&lt;p&gt;The thing that usually leads to the biggest number of performance bottlenecks is JavaScript. In SPAs it's very easy to lose control over your JS bundle size. Here's what you can do to prevent it from growing into a Brontosaurus:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you're using SSR/SSG it means that many of your components are already rendered on the server and they don't need interactivity on the frontend. You can drastically increase the speed of your hydration by hydrating only the components that need to be interactive and only when they need to become ones. You can use Astro.build or vue-lazy-hydration plugin if you're using Nuxt to control the hydration process and exclude the components that don't need it.
&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%2Fw1c8yucbd4mdr8dgjlgx.png" alt="Hydrate on Scroll"&gt;
&lt;/li&gt;
&lt;li&gt;Split your app into multiple lazy-loaded chunks (start with routes!). Every sidebar, modal or expensive widget can be loaded lazily on interaction.
&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%2F7yim098yr3gxy8npaa1p.png" alt="Code Split"&gt;
&lt;/li&gt;
&lt;li&gt;Your website could seem fast when you're building it but once the marketing team puts all the analytics there I guarantee it will slow down. You can use web workers to run the non-critical code asynchronously. I strongly recommend &lt;a href="https://github.com/nuxt-community/partytown-module" rel="noopener noreferrer"&gt;Partytown&lt;/a&gt; - it's integrated with all major frameworks from the Vue ecosystem.

&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%2Fvz29r5xd99k9sqdyub00.png"&gt;Source: &lt;a href="https://partytown.builder.io/" rel="noopener noreferrer"&gt;https://partytown.builder.io/&lt;/a&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Optimizing images
&lt;/h2&gt;

&lt;p&gt;Surprising amount of developers does a rookie mistake of not optimizing their images. To make sure images aren't the bottleneck simply adjust their size to the screen and use next-gen formats like webp. You can automatically resize and optimize your images using &lt;a href="https://image.nuxtjs.org/" rel="noopener noreferrer"&gt;&lt;/a&gt; and/or &lt;a href="https://cloudinary.com/" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt;. Also, load your below-the-fold images last. You can use native &lt;code&gt;&amp;lt;img loading="lazy" /&amp;gt;&lt;/code&gt; property for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring performance
&lt;/h2&gt;

&lt;p&gt;If you can't measure – you can't say if there was any improvement. Measuring your performance constantly is as important as optimizing it regularly.&lt;/p&gt;

&lt;p&gt;The performance metrics that have the biggest impact on user experience are called &lt;strong&gt;Core Web Vitals (CVV)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Largest Contentful Paint (LCP)&lt;/strong&gt;: measures loading performance. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First Input Delay (FID)&lt;/strong&gt;: measures interactivity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cumulative Layout Shift (CLS)&lt;/strong&gt;: measures visual stability. &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%2Fs74t6f06szzlr2qcncgw.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%2Fs74t6f06szzlr2qcncgw.png"&gt;&lt;/a&gt;&lt;br&gt;Source: &lt;a href="https://web.dev/vitals/?gclid=Cj0KCQjw2MWVBhCQARIsAIjbwoN567MYvlge9gXipQmZGvQG-9WZddKuQXRto_NiuIaHaTXStrjNFx0aApY9EALw_wcB" rel="noopener noreferrer"&gt;https://web.dev/vitals/?gclid=Cj0KCQjw2MWVBhCQARIsAIjbwoN567MYvlge9gXipQmZGvQG-9WZddKuQXRto_NiuIaHaTXStrjNFx0aApY9EALw_wcB&lt;/a&gt;
  &lt;/p&gt;

&lt;p&gt;If you want to quickly check how your website is performing, try &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;Page Speed Insights&lt;/a&gt;. It will run a Lighthouse audit on your website using the closest Google Data Center.&lt;/p&gt;

&lt;p&gt;You should also incorporate performance checks into your CI/CD pipeline. Use &lt;a href="https://github.com/GoogleChrome/lighthouse-ci" rel="noopener noreferrer"&gt;Lighthouse CI&lt;/a&gt; to run a synthetic Lighthouse test on each PR (PS: &lt;a href="https://dev.to/vue-storefront/youre-probably-using-lighthouse-wrong-how-we-got-tricked-by-a-single-magic-number-1laj"&gt;Learn why&lt;/a&gt; you shouldn't believe the Lighthouse score alone) and &lt;a href="https://www.npmjs.com/package/bundlesize" rel="noopener noreferrer"&gt;bundlesize&lt;/a&gt; package to raise alerts if your bundle size exceeds a certain threshold. For more nuanced data you should use &lt;a href="https://www.webpagetest.org/" rel="noopener noreferrer"&gt;WebPageTest&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%2F7x1ss0t9uynul0gr7f9a.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%2F7x1ss0t9uynul0gr7f9a.png" alt="Bundle Analysis"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Believe me or not but that's all you need to know to have your performance under control!&lt;/p&gt;




&lt;p&gt;If you liked the article and want to learn more about web performance through articles and tips you can follow Vue Storefront profile here or &lt;a href="https://twitter.com/filrakowski" rel="noopener noreferrer"&gt;follow me on Twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>How to enable Cache in Vue Storefront 2</title>
      <dc:creator>Jakub Andrzejewski</dc:creator>
      <pubDate>Mon, 06 Jun 2022 06:24:06 +0000</pubDate>
      <link>https://forem.com/vue-storefront/how-to-enable-cache-in-vue-storefront-2-1757</link>
      <guid>https://forem.com/vue-storefront/how-to-enable-cache-in-vue-storefront-2-1757</guid>
      <description>&lt;p&gt;Cache is a really important concept in modern web development that allows to greatly improve the second load of the certain page and in general, improve the User Experience. If you are not yet familiar with it, I have published an article about it some time ago that you can read &lt;a href="https://dev.to/jacobandrewsky/leveraging-cache-in-vuejs-and-nuxtjs-4b26"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In Vue Storefront cache can be enabled on both browser and the server. The first one will be using a &lt;code&gt;Cache-Control&lt;/code&gt; response header to cache the response on the browser, while the second, will be using a cache driver like &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; to cache all pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Cache
&lt;/h2&gt;

&lt;p&gt;To enable Cache on the browser level, we will be using a &lt;code&gt;http-cache&lt;/code&gt; package from Vue Storefront that you can check out &lt;a href="https://github.com/vuestorefront/vue-storefront/tree/main/packages/http-cache" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;This package handles adding http-cache header to document after render for caching capabilities&lt;/p&gt;

&lt;p&gt;First, install the dependency&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add @vue-storefront/http-cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add it to the &lt;code&gt;modules&lt;/code&gt; array in your &lt;code&gt;nuxt.config.js&lt;/code&gt;:&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vue-storefront/http-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it. Thanks to this module, homepage, category page, and product page will be automatically returning a response header &lt;code&gt;Cache-Control&lt;/code&gt; with a certain default value that will enable your browser to cache it properly. Check out the following section to see some configuration options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;The package allows you to configure certain properties of it to make it work differently and suit your needs best.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;default&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This property allows you do override default value of &lt;code&gt;http-cache&lt;/code&gt; header which is initially set to &lt;code&gt;max-age=60&lt;/code&gt;&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vue-storefront/http-cache&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="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max-age=120&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;matchRoute&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Customize &lt;code&gt;http-cache&lt;/code&gt; values for selected routes. you can use &lt;code&gt;*&lt;/code&gt; for a wildcard. To remove &lt;code&gt;http-cache&lt;/code&gt; header use &lt;code&gt;none&lt;/code&gt; value.&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vue-storefront/http-cache&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="na"&gt;matchRoute&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="s1"&gt;/&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="s1"&gt;max-age=240&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="s1"&gt;/p/*&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="s1"&gt;max-age=360&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="s1"&gt;/c/*&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="s1"&gt;none&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Server Cache
&lt;/h2&gt;

&lt;p&gt;To enable Cache on the server side, we can also use the packages provided by Vue Storefront, namely &lt;code&gt;@vue-storefront/cache&lt;/code&gt; and &lt;code&gt;@vue-storefront/redis-cache&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, let's install the required dependencies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add @vue-storefront/cache
yarn add @vue-storefront/redis-cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's add required configuration for the packages to work correctly&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.js&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;modules&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vue-storefront/cache/nuxt&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="na"&gt;invalidation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Invalidation options&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;driver&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="s1"&gt;@vue-storefront/redis-cache&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="na"&gt;defaultTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the following docs to understand better the process of using server cache with Vue Storefront:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.vuestorefront.io/v2/integrate/cache-driver.html#integrating-cache-driver" rel="noopener noreferrer"&gt;Integrating Cache Driver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.vuestorefront.io/v2/performance/ssr-cache.html" rel="noopener noreferrer"&gt;SSR Cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.vuestorefront.io/v2/integrations/redis-cache.html" rel="noopener noreferrer"&gt;Redis Cache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Well done! You have just enabled the Cache on both browser and server environments of your Vue Storefront application. This should improve the performance of you e-commerce website by a mile!&lt;/p&gt;

</description>
      <category>performance</category>
      <category>nuxt</category>
      <category>vue</category>
      <category>cache</category>
    </item>
    <item>
      <title>How to add TailwindCSS to Vue Storefront 2</title>
      <dc:creator>Jakub Andrzejewski</dc:creator>
      <pubDate>Mon, 30 May 2022 06:37:27 +0000</pubDate>
      <link>https://forem.com/vue-storefront/how-to-add-tailwindcss-to-vue-storefront-2-1i7j</link>
      <guid>https://forem.com/vue-storefront/how-to-add-tailwindcss-to-vue-storefront-2-1i7j</guid>
      <description>&lt;p&gt;TailwindCSS is becoming a number one CSS framework on the market. Whether you like it or not, it is being used in more and more applications and it is also coming as a default for some popular projects. Due to its simplicity and many Developer Experience improvements, Tailwind joined a DX Gang and has a safe position there.&lt;/p&gt;

&lt;p&gt;In this article, I would like to guide you through the process of adding TailwindCSS to your Vue Storefront project. It can be used as an alternative to Storefront UI styles or be used with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vue Storefront
&lt;/h2&gt;

&lt;p&gt;If you are not yet familiar with Vue Storefront, it is Lightning-Fast Frontend Platform for Headless Commerce. Boost your site performance, shape the customer journey and free your developer's creativity with Vue Storefront, the last frontend you will ever need. &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%2F1adt9yw9byndirxn6ymt.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%2F1adt9yw9byndirxn6ymt.png" alt="Vue Storefront" width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can check out more about it here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.vuestorefront.io/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.vuestorefront.io/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Storefront UI
&lt;/h2&gt;

&lt;p&gt;Vue Storefront uses by default a really useful UI Library called Storefront UI. It was designed based on Google for Retail report and specifically for E-Commerce. It is really customizable and allows to accelerate the development of your e-commerce application by a mile!&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%2F36sbfljy4cqxdcouvsz4.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%2F36sbfljy4cqxdcouvsz4.png" alt="Storefront UI" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can read more about it here: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.vuestorefront.io/storefront-ui" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.storefrontui.io/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding TailwindCSS to VSF
&lt;/h2&gt;

&lt;p&gt;As Vue Storefront uses Nuxt.js under the hood, the process of adding it to your project is relatively simple. Especially with the recent release of a new version of &lt;a href="https://tailwindcss.nuxtjs.org/" rel="noopener noreferrer"&gt;Tailwind Module for Nuxt&lt;/a&gt; that you can check the code &lt;a href="https://github.com/nuxt-community/tailwindcss-module" rel="noopener noreferrer"&gt;here&lt;/a&gt;. In this version, you do not need to register tailwind config nor postcss config in your nuxt application in order to make it work. How briliant is that?&lt;/p&gt;

&lt;p&gt;In order to install the Tailwind module in Vue Storefront application we need to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add @nuxtjs/tailwindcss &lt;span class="c"&gt;# npm install @nuxtjs/tailwindcss&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we need to register it in the &lt;code&gt;modules&lt;/code&gt; array of our &lt;code&gt;nuxt.config.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Other modules&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nuxtjs/tailwindcss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, to test if it works, we can add some Tailwind styles to our wrapper component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"home"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-blue-300"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this should be a result:&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%2Flsz43mslhkl3zexs2muw.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%2Flsz43mslhkl3zexs2muw.png" alt="Demo" width="800" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Well done! You have successfuly added TailwindCSS to your Vue Storefront project. Keep in mind however, that Storefront UI wasn't designed to work with TailwindCSS out of the box so in order to replace the styles you would have to adjust several CSS variables and this process is described &lt;a href="https://docs.storefrontui.io/?path=/docs/getting-started-integrations-tailwindcss--page#customizing-variables" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>tailwindcss</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
