<?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: Gert Glükmann</title>
    <description>The latest articles on Forem by Gert Glükmann (@glukmann).</description>
    <link>https://forem.com/glukmann</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F288075%2Fd8e2dcd6-c87f-428e-aa4b-330fc8d88f61.jpg</url>
      <title>Forem: Gert Glükmann</title>
      <link>https://forem.com/glukmann</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/glukmann"/>
    <language>en</language>
    <item>
      <title>How to Get Videos to Work in Safari With Gatsby and Service Workers</title>
      <dc:creator>Gert Glükmann</dc:creator>
      <pubDate>Tue, 18 Feb 2020 08:59:48 +0000</pubDate>
      <link>https://forem.com/glukmann/how-to-get-videos-to-work-in-safari-with-gatsby-and-service-workers-4edc</link>
      <guid>https://forem.com/glukmann/how-to-get-videos-to-work-in-safari-with-gatsby-and-service-workers-4edc</guid>
      <description>&lt;p&gt;&lt;em&gt;Adding service workers to a site breaks videos in Safari and iOS devices&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1h9G4nI7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/10808/0%2AhXI8dA6cR4Q34Wr8" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1h9G4nI7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/10808/0%2AhXI8dA6cR4Q34Wr8" alt="Photo by [Thomas Russell](https://unsplash.com/@truss?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)"&gt;&lt;/a&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@truss?utm_source=medium&amp;amp;utm_medium=referral"&gt;Thomas Russell&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I was making a website where &lt;a href="https://en.wikipedia.org/wiki/Hero_image"&gt;hero components&lt;/a&gt; have videos, and before adding &lt;a href="https://www.gatsbyjs.org/packages/gatsby-plugin-offline/"&gt;gatsby-plugin-offline&lt;/a&gt;, everything was working fine. I did some browser testing and all browsers seemed fined, videos were fine. Then I decided that it’s time to add the service worker and to make the website installable and work offline. I added the plug-in and tested everything with Chrome and Android. Everything as it should be! But then I opened it with my iPad and saw that videos are not playing at all, not even loading.&lt;/p&gt;

&lt;p&gt;That seemed odd as videos were implemented with the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; HTML tag and they were standard MP4 files. Luckily, I had only added the service worker, so I started suspecting it had something to do with that.&lt;/p&gt;

&lt;p&gt;I came across &lt;a href="https://adactio.com/journal/14452"&gt;Jeremy Keiths’ article&lt;/a&gt; where he describes how he had the same problem. He refers to a solution and a more in-depth explanation in a &lt;a href="https://philna.sh/blog/2018/10/23/service-workers-beware-safaris-range-request/"&gt;post by Phil Nash&lt;/a&gt;. It looks like Safari needs service workers to support &lt;em&gt;byte-range requests&lt;/em&gt; to play media. As the &lt;a href="https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/CreatingVideoforSafarioniPhone/CreatingVideoforSafarioniPhone.html#//apple_ref/doc/uid/TP40006514-SW6"&gt;documentation for Safari&lt;/a&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“HTTP servers hosting media files for iOS must support byte-range requests, which iOS uses to perform random access in media playback.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They both went with different approaches to a solution. Phil fixed the service worker to cache video files, but Jeremy opted for always loading videos from the network and never caching them.&lt;/p&gt;

&lt;p&gt;I’m going to show you how to implement both solutions with &lt;code&gt;gatsby-plugin-offline&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Videos From the Cache
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gatsby-plugin-offline&lt;/code&gt; uses &lt;a href="https://developers.google.com/web/tools/workbox"&gt;Workbox&lt;/a&gt; for generating service workers. Luckily, Workbox already has an &lt;a href="https://developers.google.com/web/tools/workbox/guides/advanced-recipes?authuser=0#cached-av"&gt;advanced recipe&lt;/a&gt; for how to serve videos and audio. This is exactly how we’ll implement it. We’ll just have to append it to the service worker generated by Gatsby.&lt;/p&gt;

&lt;p&gt;First, we need to add attribute &lt;code&gt;crossOrigin="anonymous"&lt;/code&gt; to our HTML &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;movie.mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;crossOrigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anonymous&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/video&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Second, we create a file that will be appended to the generated service worker file. Let’s name it &lt;code&gt;sw-range-request-handler.js&lt;/code&gt;. We’ll put that into the root folder of our project.&lt;/p&gt;

&lt;p&gt;Content of this file will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Add Range Request support to fetching videos from cache&lt;/span&gt;
&lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="sr"&gt;/.*&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;mp4/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strategies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CacheFirst&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cacheableResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rangeRequests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Plugin&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;GET&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;p&gt;We match all requested MP4 files and use &lt;code&gt;CacheFirst&lt;/code&gt; strategy to search for video files from the cache. If there isn’t a cache match, then the file will be served from the network.&lt;/p&gt;

&lt;p&gt;If you look at the example from &lt;a href="https://developers.google.com/web/tools/workbox/guides/advanced-recipes?authuser=0#cached-av"&gt;Workbox advanced recipes&lt;/a&gt;, you’ll see that the usage of plugin functions are a bit different. That’s because as of now, &lt;code&gt;gatsby-plugin-offline&lt;/code&gt; uses Workbox v.4, but the example is for v.5.&lt;/p&gt;

&lt;p&gt;You’ll see that we didn’t import any functions from Workbox, either. That’s because we only append our file content to the generated service worker file, where all those plugins are already added to the &lt;code&gt;workbox&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;gatsby-plugin-offline&lt;/code&gt; updates to v.5 of Workbox, we’ll need to update how we use the plugins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Add Range Request support to fetching videos from cache&lt;/span&gt;
&lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="sr"&gt;/.*&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;mp4/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strategies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CacheFirst&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cacheableResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CacheableResponsePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rangeRequests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RangeRequestsPlugin&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;GET&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;p&gt;Now we need to use the &lt;a href="https://www.gatsbyjs.org/packages/gatsby-plugin-offline/#available-options"&gt;appendScript&lt;/a&gt; option in &lt;code&gt;gatsby-plugin-offline&lt;/code&gt; config to append our file to the service worker. Add the &lt;code&gt;options&lt;/code&gt; object to &lt;code&gt;gatsby-config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gatsby-plugin-offline`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;appendScript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&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="s2"&gt;`./sw-range-request-handler.js`&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;Now running &lt;code&gt;gatsby build&lt;/code&gt; command and looking into &lt;code&gt;public/sw.js&lt;/code&gt; file, you’ll see that at the very end is our code. Showing videos in Safari and iOS devices will work again after the service worker is updated.&lt;/p&gt;

&lt;p&gt;But this caches all our videos to Cache API storage, and when you have a lot them or they’re big files, then it’s probably not a very good idea to take that much space from the user’s device. And are the videos really that important when the user is offline?&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Videos From the Network
&lt;/h2&gt;

&lt;p&gt;To never cache videos to storage and only get them from the network, you need to overwrite the Workbox config. That can be done by overwriting the &lt;a href="https://www.gatsbyjs.org/packages/gatsby-plugin-offline/#overriding-workbox-configuration"&gt;workboxConfig&lt;/a&gt; object inside the &lt;code&gt;options&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;First, we should still add the &lt;code&gt;crossOrigin="anonymous"&lt;/code&gt; attribute to our HTML &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;movie.mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;crossOrigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anonymous&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/video&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Second, we modify the &lt;code&gt;gatsby-config.js&lt;/code&gt; file to overwrite the existing &lt;code&gt;workboxConfig&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gatsby-plugin-offline`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;workboxConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;runtimeCaching&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="na"&gt;urlPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/.*&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;mp4/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`NetworkOnly`&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;We use &lt;code&gt;NetworkOnly&lt;/code&gt; strategy to get our MP4 file. It will never be searched from the cache.&lt;/p&gt;

&lt;p&gt;But be aware that our code now overwrites the default caching for &lt;code&gt;gatsby-plugin-offline&lt;/code&gt;. It would be better to add it to the &lt;a href="https://www.gatsbyjs.org/packages/gatsby-plugin-offline/#overriding-workbox-configuration"&gt;existing list of caching options&lt;/a&gt; so that everything else will still be cached, and with the right strategies.&lt;/p&gt;

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

&lt;p&gt;At first, it can be very confusing to understand why videos are not playing when the page has service workers, but these two solutions should fix that. This problem doesn’t occur only when using Gatsby, and there are solutions for those other situations, too.&lt;/p&gt;

&lt;p&gt;If you’re using Workbox, then look at their &lt;a href="https://developers.google.com/web/tools/workbox/guides/advanced-recipes?authuser=0#cached-av"&gt;advanced recipes&lt;/a&gt;. If you’re using pure service worker implementation, then look at Jeremy Keith’s article “&lt;a href="https://adactio.com/journal/14452"&gt;Service Workers and Videos in Safari&lt;/a&gt;” or the post by Phil Nash, “&lt;a href="https://philna.sh/blog/2018/10/23/service-workers-beware-safaris-range-request/"&gt;Service Workers: Beware Safari’s Range Request&lt;/a&gt;.” They give a more in-depth explanation about that problem.&lt;/p&gt;

&lt;p&gt;Thanks.&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>react</category>
      <category>javascript</category>
      <category>serviceworkers</category>
    </item>
    <item>
      <title>Create Multilingual Web Pages With Gatsby, WordPress, WPML, and ACF</title>
      <dc:creator>Gert Glükmann</dc:creator>
      <pubDate>Tue, 28 Jan 2020 18:26:57 +0000</pubDate>
      <link>https://forem.com/glukmann/create-multilingual-web-pages-with-gatsby-wordpress-wpml-and-acf-4i9f</link>
      <guid>https://forem.com/glukmann/create-multilingual-web-pages-with-gatsby-wordpress-wpml-and-acf-4i9f</guid>
      <description>&lt;p&gt;&lt;em&gt;Bring your site to the world&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3gqy5klo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AvVTDgiAoliRU_lI93lpLWQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3gqy5klo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AvVTDgiAoliRU_lI93lpLWQ.jpeg" alt="Gatsby page with English and Estonian content from WordPress"&gt;&lt;/a&gt;&lt;em&gt;Gatsby page with English and Estonian content from WordPress&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Gatsby is a great static-site generator to use today. Its ecosystem is really large, and you get a lot out of the box. Getting Lighthouse maximum scores is almost default with Gatsby. Anyone who is working with WordPress and wants to separate the CMS and the website itself should at least try to create something with Gatsby. It is really easy to use, and the documentation is very straightforward.&lt;/p&gt;

&lt;p&gt;Gatsby is using GraphQL for getting data from local files or from external endpoints. If you want use it with WordPress for getting pages, posts, media, ACF fields, etc., you don’t have to manually figure it out. There’s a library that creates schema from the WordPress REST API to GraphQL and it’s backed by Gatsby. It’s &lt;a href="https://github.com/wp-graphql/wp-graphql"&gt;WPGraphQL&lt;/a&gt;, and there’s a Gatsby plugin, &lt;a href="https://www.gatsbyjs.org/packages/gatsby-source-wordpress/"&gt;gatsby-source-wordpress&lt;/a&gt;, to connect your WordPress site to. It’s using that connector library underneath.&lt;/p&gt;

&lt;p&gt;This piece expects you have WordPress set up with WPML and ACF plugins activated. It also expects you have the gatsby-source-wordpress plugin set up in &lt;code&gt;gatsby-config.js&lt;/code&gt;. In the &lt;a href="https://github.com/gglukmann/multilingual-wp-gatsby"&gt;example repository&lt;/a&gt;, you can see how I connected to WordPress from Gatsby.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Changing the Language in GraphQL
&lt;/h2&gt;

&lt;p&gt;There’s only one problem. Lets say you’re creating a page with only one view, and this will be on the root URL &lt;code&gt;//your-site.domain/&lt;/code&gt;. Now, you’ll need to create the same page in a different language that’ll sit in the &lt;code&gt;//your-site.domain/et&lt;/code&gt; URL — just like when using standard WordPress. How do you do it using WPML in Wordpress and creating pages with Gatsby?&lt;/p&gt;

&lt;p&gt;The WordPress REST API endpoint gets you content for the default language. Example: &lt;code&gt;//your-site.domain/wp-json/wp/v2/pages&lt;/code&gt; is in your WPML default language. You can switch languages by adding the &lt;code&gt;?lang=et&lt;/code&gt; parameter, but with GraphQL you can’t add parameters like that. You’ll have to add it as a filter to the query. The GraphQL schema in Gatsby doesn’t have that filter for WMPL. We have to add it ourselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Pages in Gatsby
&lt;/h2&gt;

&lt;p&gt;I have created a page in WordPress with the slug &lt;code&gt;homepage&lt;/code&gt; and with the ACF fields &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Dky4EOwc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3032/1%2AkRHiHmk_sOzzTwmNAfDMYg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Dky4EOwc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3032/1%2AkRHiHmk_sOzzTwmNAfDMYg.png" alt="ACF fields in WordPress"&gt;&lt;/a&gt;&lt;em&gt;ACF fields in WordPress&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Make sure every page with a different language has the same slug because WordPress creates new slugs for different languages. When I created a new page in the Estonian language, WordPress created the slug &lt;code&gt;homepage-2&lt;/code&gt;. You could, of course, query it with its ID, too, but it’ll be easier to query data for that page with a known slug. You’ll see later where we’re going to use it.&lt;/p&gt;

&lt;p&gt;Creating pages in Gatsby is usually done by adding new JavaScript files to the &lt;code&gt;src/pages&lt;/code&gt; folder with the name that’ll be the route. Like the &lt;code&gt;about.js&lt;/code&gt; file would have &lt;code&gt;/about&lt;/code&gt; as its URL. When you’re creating pages from WordPress, you’ll have to create them during the build time. You’ll need to open &lt;code&gt;gatsby-node.js&lt;/code&gt; and use the &lt;a href="https://www.gatsbyjs.org/docs/node-apis/#createPages"&gt;&lt;code&gt;createPages&lt;/code&gt;&lt;/a&gt; function that Gatsby provides.&lt;/p&gt;

&lt;p&gt;For our case, we’ll need to create a separate page for all languages. Our index page will have the URLS &lt;code&gt;/&lt;/code&gt; for English and &lt;code&gt;/et&lt;/code&gt; for the Estonian language.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`path`&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;languages&lt;/span&gt; &lt;span class="o"&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en_US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nx"&gt;taken&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;WPML&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt; &lt;span class="nx"&gt;codes&lt;/span&gt;
  &lt;span class="p"&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="s2"&gt;/et&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;code&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&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="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createPage&lt;/span&gt; &lt;span class="p"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HomepageTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/templates/Homepage.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lang&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;createPage&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="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;HomepageTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&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;We’ve created an array with languages that match our WordPress WPML setup. This will be looped over, and one page will be created for each language with a given path.&lt;/p&gt;

&lt;p&gt;You can see there’s a template file from &lt;code&gt;./src/templates/Homepage.js&lt;/code&gt;. This will be the template that has our index-page components inside — just like when you’d add a new page inside the &lt;code&gt;src/pages&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Next, as you’d think, we’ll have to create that template file. Create the folder &lt;code&gt;templates&lt;/code&gt; inside &lt;code&gt;./src&lt;/code&gt;, and inside it, create a file named &lt;code&gt;Homepage.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&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;Link&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gatsby&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/layout&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;HomepageTemplate&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;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;English&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/et&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Estonian&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Layout&lt;/span&gt;&lt;span class="err"&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;HomepageTemplate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The hardcoded texts &lt;code&gt;Title&lt;/code&gt; and &lt;code&gt;Description&lt;/code&gt; will be replaced with the texts from WordPress a bit later.&lt;/p&gt;

&lt;p&gt;If you run &lt;code&gt;gatsby develop&lt;/code&gt;, then you can switch between those two views. But right now, the content is exactly the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Data from WordPress
&lt;/h2&gt;

&lt;p&gt;In your &lt;code&gt;Homepage.js&lt;/code&gt; file, add the following GraphQL query before &lt;code&gt;export default HomepageTemplate&lt;/code&gt;. Make sure to add &lt;code&gt;graphql&lt;/code&gt; to import from &lt;code&gt;gatsby&lt;/code&gt; as the named import.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;graphql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gatsby&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;graphql&lt;/span&gt;&lt;span class="s2"&gt;`
  query {
    wordpressPage(
      slug: { eq: "homepage" }
    ) {
      acf {
        title
        description
      }
    }
  }
`&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;HomepageTemplate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here you can see we’re querying a WordPress page with slug that equals &lt;code&gt;"homepage"&lt;/code&gt; and two ACF fields — &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; — that we had set up in WordPress. The query result is added to your &lt;code&gt;HomepageTemplate&lt;/code&gt; component as the prop &lt;code&gt;data&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HomepageTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;wordpressPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;acf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&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="o"&gt;=&amp;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;With object destructuring, we have &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; ready to use inside our React component. We can change our HTML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&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;description&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now if you run it in your browser, it shows text in the default language and switching between those pages will still not change anything yet again. We’ll get to that now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Content in Other Languages to the WordPress REST API So GraphQL Can Create Schema
&lt;/h2&gt;

&lt;p&gt;Switching pages doesn’t change the language because the WordPress REST API is only giving out data in one language, and we’ll have to change that.&lt;/p&gt;

&lt;p&gt;First, look at the WordPress REST API &lt;code&gt;//your-site.domain/wp-json/wp/v2/pages&lt;/code&gt;, and you can see only one object with content in the default language there. But we’ll need to have both languages there in different objects.&lt;/p&gt;

&lt;p&gt;For that, you’ll need to open your currently active theme code, located at &lt;code&gt;./wp-content/themes/example-theme/&lt;/code&gt;. Open the file &lt;code&gt;functions.php&lt;/code&gt;, and add the following lines there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rest_api_init&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REST_REQUEST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;REST_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Add all WPML language translations to rest api when type is page&lt;/span&gt;
    &lt;span class="nx"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;parse_query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$q&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;query_vars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post_type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page&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="nx"&gt;$q&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;query_vars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;suppress_filters&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;This trick is taken from &lt;a href="https://wpml.org/forums/topic/how-can-i-get-all-pages-original-and-translated-in-the-api-response/#post-3072937"&gt;wmpl.org forum&lt;/a&gt;. Now if you look at the WordPress REST API, &lt;code&gt;//your-site.domain/wp-json/wp/v2/pages&lt;/code&gt;, you can see there are two objects with different languages.&lt;/p&gt;

&lt;p&gt;That means GraphQL can now create schema for both languages.&lt;/p&gt;

&lt;p&gt;Before we can start using it inside our React component, we need to be able to get the current language code, too. If you look closely into the REST API response, you’ll see that &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; are in different languages inside different objects, but there’s no way to get the language code.&lt;/p&gt;

&lt;p&gt;For that, you’ll need the &lt;a href="https://github.com/shawnhooper/wpml-rest-api"&gt;WPML REST API&lt;/a&gt; plugin activated inside WordPress. For us, it adds &lt;code&gt;wpml_current_locale&lt;/code&gt; to the REST API response. This way we can know which language to query from GraphQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Texts in the Right Language From GraphQL
&lt;/h2&gt;

&lt;p&gt;If you look at the &lt;code&gt;gatsby-node.js&lt;/code&gt; file, you can see in our languages array, we have &lt;code&gt;code&lt;/code&gt; defined for each language. This &lt;code&gt;code&lt;/code&gt; is exactly the same as &lt;code&gt;wpml_current_locale&lt;/code&gt;. If you look at where we’re using the &lt;code&gt;createPage&lt;/code&gt; function, you’ll see we’re giving the &lt;code&gt;context&lt;/code&gt; as the property with that &lt;code&gt;code&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;createPage&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="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;HomepageTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nx"&gt;sending&lt;/span&gt; &lt;span class="nx"&gt;language&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;GraphQL&lt;/span&gt; &lt;span class="nx"&gt;query&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;We’ll get this as a GraphQL variable inside &lt;code&gt;Homepage.js&lt;/code&gt;, where we’re going to make the query.&lt;/p&gt;

&lt;p&gt;Update the &lt;code&gt;Homepage.js&lt;/code&gt; GraphQL query with the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;graphql&lt;/span&gt;&lt;span class="s2"&gt;`
  query($lang: String) {
    wordpressPage(
      slug: { eq: "homepage" }
      wpml_current_locale: { eq: $lang }
    ) {
      acf {
        title
        description
      }
    }
  }
`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$lang&lt;/code&gt; is the language code we sent with the context from the &lt;code&gt;createPage&lt;/code&gt; function. We pass it to query the filter as equal to &lt;code&gt;wpml_current_local&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And we’ve done it!&lt;/p&gt;

&lt;p&gt;Now if you run it in a browser, it shows the text in English, and when switching it to a different language, &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; are going to change.&lt;/p&gt;

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

&lt;p&gt;This solution is pretty standard for &lt;a href="https://www.gatsbyjs.org/docs/recipes/sourcing-data#sourcing-from-wordpress"&gt;creating pages with Gatsby and getting data from Wordpress&lt;/a&gt;, but that one little trick inside the WordPress theme &lt;code&gt;functions.php&lt;/code&gt; is what matters to get data for all available WPML languages.&lt;/p&gt;

&lt;p&gt;Thanks.&lt;/p&gt;

&lt;p&gt;Here’s a link to the &lt;a href="https://github.com/gglukmann/multilingual-wp-gatsby"&gt;example repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>wordpress</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Validating Our Idea With Google Design Sprint 2.0</title>
      <dc:creator>Gert Glükmann</dc:creator>
      <pubDate>Tue, 28 Jan 2020 15:24:04 +0000</pubDate>
      <link>https://forem.com/glukmann/validating-our-idea-with-google-design-sprint-2-0-166g</link>
      <guid>https://forem.com/glukmann/validating-our-idea-with-google-design-sprint-2-0-166g</guid>
      <description>&lt;p&gt;&lt;em&gt;When you have an idea for an app but you are not sure how valid it is, how to estimate it quickly and with low costs?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We had an idea for an app, but weren’t sure how to approach it. We had looked at competitors and wanted to do it differently and better. The idea wasn’t something very revolutionary so we needed to be sure our soon-to-be users and service providers actually need it.&lt;/p&gt;

&lt;p&gt;We had heard about Google Design Sprint and thought we should give it a go: validate our idea and come out with a prototype to test on real users before starting to create the real app.&lt;/p&gt;

&lt;p&gt;This article is about how we did it — I suppose it goes a bit differently every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Design Sprint 2.0 (GDS)
&lt;/h2&gt;

&lt;p&gt;GDS is a four day sprint with different roles, a lot of discussions and research. Every day has a specific focus and outcome.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i8X64Pgl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3998/1%2AyLP7II0_h2OGIqL9dMjC4A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i8X64Pgl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/3998/1%2AyLP7II0_h2OGIqL9dMjC4A.png" alt="4 days of Google Design Sprint 2.0. Photo from [invisionapp.com](https://www.invisionapp.com/inside-design/design-sprint-2/)"&gt;&lt;/a&gt;&lt;em&gt;4 days of Google Design Sprint 2.0. Photo from &lt;a href="https://www.invisionapp.com/inside-design/design-sprint-2/"&gt;invisionapp.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is crucial to include every role you have in your group. Every person with a different role and background has a distinctive perspective on how things should work. Roles can vary depending on who you are working with — product manager, product owner, developer, analyst, tester, designer, UX person, sales person, you name it — just one role you really can’t be without is someone who can make a clickable prototype that is used to present on the fourth day to real users.&lt;/p&gt;

&lt;p&gt;Google Design Sprint is usually done from Monday to Thursday, but we needed to pause our everyday work and find a time that suited everyone, so we went with from Saturday to Tuesday.&lt;/p&gt;

&lt;p&gt;Our team consisted of a product manager, developer, marketing person, two business managers and a designer.&lt;/p&gt;

&lt;h2&gt;
  
  
  First day — Saturday
&lt;/h2&gt;

&lt;p&gt;Usually design sprint needs some preliminary analysis to figure out who are your personas (users who would use your app) and create some user flows, but we didn’t have time to do it beforehand. We had only done some discussions about our idea, talked about it with some of our acquaintances that worked in that field and thought about business model. Only thing we did for this design sprint was buying stickers with different colours. 👏&lt;/p&gt;

&lt;p&gt;So we started off with gathering ideas. We mapped out all possible scenarios and user groups. We didn’t limit ourselves with anything, every idea was a possibility. We tried to think about every feature or flow our app should do. For advanced user or one time visitor, didn’t matter. For the scope of MVP or very far fetched and complicated features, didn’t matter. We just wanted to get it all out there — no limits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xXIbly_m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/8000/1%2A8vS81T92cjz5ifsUDc_tqw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xXIbly_m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/8000/1%2A8vS81T92cjz5ifsUDc_tqw.jpeg" alt="Our hub in a meeting room with ideas"&gt;&lt;/a&gt;&lt;em&gt;Our hub in a meeting room with ideas&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To be sure we have thought about our idea from every angle, we had a&lt;a href="https://methodkit.com/shop/methodkit-for-startups/"&gt; MethodKit for Startups&lt;/a&gt; to help us. This kit is a set of cards that have keywords on and we tried to answer them or find a solution. This helped to pay attention to the possible danger zones, legal limitations or even how the marketing should be and how the user should even find us.&lt;/p&gt;

&lt;p&gt;After all these discussions and research, everyone took some time to think it through and we started drawing our ideas as they should look in the app.&lt;/p&gt;

&lt;p&gt;First day ended with a lot of drawings of different flows. Everyone tried to visualize how they saw the user flows or some specific features. However, it was not obligatory to draw all the possible views of the app, just the few one had in their mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second day — Sunday
&lt;/h2&gt;

&lt;p&gt;We had gathered again the next morning and began the day with drawing new ideas everyone had come up with while being away and fixed all the drawings to our glass wall. Usually the next step would be for everyone to look at other ideas and mark the most liked features with dots or stars, but we just looked at drawings to understand what others had thought of.&lt;/p&gt;

&lt;p&gt;Next step was for us to start deciding what we should keep and what not. Especially for our prototype and for MVP. We started cutting parts of drawings out and gluing them together with duct tape to make user flows. This took most of the day, as new ideas came and erupted with ease. Nevertheless, at the end of the day we managed to agree on the main user flows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z-BQ2n4x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/9216/1%2AFwE-RS7UbyonTSeRLMkftA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z-BQ2n4x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/9216/1%2AFwE-RS7UbyonTSeRLMkftA.jpeg" alt="Adding solutions to the glass wall"&gt;&lt;/a&gt;&lt;em&gt;Adding solutions to the glass wall&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After our flows were ready, we had to prepare texts that would be used in the prototype. This is always a challenge, especially as we decided to not use English and use our native Estonian language instead.&lt;/p&gt;

&lt;p&gt;Second day resulted with one mayor flow ready for prototyping in the third day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third day — Monday
&lt;/h2&gt;

&lt;p&gt;The activities of the third day didn’t insist on the whole group being together in one place. Everyone took some up tasks to deal with separately or in a small group. Like figuring out the payment system, making more market research or as for our designer, she started prototyping our user flows into&lt;a href="https://www.figma.com/files/recent"&gt; Figma&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Everyone who had some friends or acquaintances who they could talk to and show our prototype, set up meetings for user testing.&lt;/p&gt;

&lt;p&gt;We also needed to create a questionnaire for user testing, but we didn’t know how much of the prototype will be ready at the end of the day. Luckily Figma is a collaboration tool and we could see almost live how much is done from the shared link. But it was hard to create it and we pretty much abandoned it. Our designer finished at 8pm in the evening, we had enough views to show the next day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fourth day — Tuesday
&lt;/h2&gt;

&lt;p&gt;In the morning we did some updates to prototype and after lunch everyone went their own way to their meetings they had set up the day before. We didn’t have a questionnaire to ask the same questions but everyone did it themselves.&lt;/p&gt;

&lt;p&gt;At the end of the day we should have got together to discuss the feedback and make conclusions, but we did it the next day.&lt;/p&gt;

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

&lt;p&gt;We are going forward with our idea, we got a lot of great feedback and knowledge about what users want. Next step is to put together the scope of MVP.&lt;/p&gt;

&lt;p&gt;All-in-all we highly recommend this approach to validate and confirm whatever idea you have. This 4-day event not only puts your idea to the test, but also strengthens the relations between your teammates. It is guaranteed that you will learn something new about someone or something. Timewise it is definitely a sprint, as its name claims to be, but actionwise more of a marathon.&lt;/p&gt;

&lt;p&gt;Thanks.&lt;/p&gt;

&lt;p&gt;Edited by Mare Hunt, Marilin Moor &amp;amp; Kaur Esnar&lt;/p&gt;

</description>
      <category>startup</category>
      <category>design</category>
      <category>product</category>
      <category>mvp</category>
    </item>
    <item>
      <title>Let Users Know When You Have Updated Your Service Workers in Create React App</title>
      <dc:creator>Gert Glükmann</dc:creator>
      <pubDate>Sat, 04 Jan 2020 14:18:42 +0000</pubDate>
      <link>https://forem.com/glukmann/let-users-know-when-you-have-updated-your-service-worker-in-create-react-app-3il9</link>
      <guid>https://forem.com/glukmann/let-users-know-when-you-have-updated-your-service-worker-in-create-react-app-3il9</guid>
      <description>&lt;p&gt;&lt;em&gt;Show an alert component when you have pushed a new service worker, allowing the user to update their page right away&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cddbLN36--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ao-uGfk9X59ZU1mloaqxnfA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cddbLN36--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ao-uGfk9X59ZU1mloaqxnfA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create React App (CRA) is great for developing progressive web apps (PWAs). It has offline/cache-first behaviour built in. It’s not enabled by default, but you can opt in. It uses service workers and has a lot of pitfalls you can read about from &lt;a href="https://create-react-app.dev/docs/making-a-progressive-web-app/"&gt;official docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This piece is going to show you how to trigger an alert (or toast or actually whatever component you want) when you have updated your service worker. Usually, this will be when your app has some new updates and you want the user to see them right away.&lt;/p&gt;

&lt;p&gt;This piece assumes you have a new project made with CRA. If you don’t, you can do it easily with:&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="nx"&gt;npx&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Registering a Service Worker
&lt;/h2&gt;

&lt;p&gt;If you navigate to &lt;code&gt;src/index.js&lt;/code&gt; you find on the last line:&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="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unregister&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Switch it to:&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="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And registering a service worker is pretty much done. If you deploy your app to an HTTPS-enabled site, it’ll be cached.&lt;/p&gt;

&lt;p&gt;Remember that service-worker implementation in CRA works only in production. You can make sure it works by checking the offline checkbox from the Chrome DevTools Network tab and reloading your page.&lt;/p&gt;

&lt;p&gt;It still shows your app!&lt;/p&gt;
&lt;h2&gt;
  
  
  Is your updated Service Worker not visible?
&lt;/h2&gt;

&lt;p&gt;Now comes the harder part. You add or change code in your app and deploy — but users aren’t seeing your updates. As the docs state:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The default behavior is to conservatively keep the updated service worker in the “&lt;a href="https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting"&gt;waiting&lt;/a&gt;” state. This means that users will end up seeing older content until they close (reloading is not enough) their existing, open tabs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What if you want users to see your new updates without having to close all tabs? CRA is providing that option, too.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;src/serviceWorker.js&lt;/code&gt; is a function named &lt;code&gt;registerValidSW&lt;/code&gt; that provides access to service-worker updates and success events via callbacks and also prints information about these events to the console. This is how you know when to show that the app is cached for offline use or there’s a newer version available.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;registerValidSW&lt;/code&gt; function takes in two arguments — the second one is the one we’re interested in. &lt;code&gt;config&lt;/code&gt; can be an object that has &lt;code&gt;onSuccess&lt;/code&gt; and &lt;code&gt;onUpdate&lt;/code&gt; callbacks in it. You should wonder now how and where could we make such an object?&lt;/p&gt;

&lt;p&gt;If you look where &lt;code&gt;registerValidSW&lt;/code&gt; is called, you see that it comes from &lt;code&gt;export function register(config)&lt;/code&gt;. This is the very same function that we saw on the last line in &lt;code&gt;src/index.js&lt;/code&gt;. Now, we’re back in our own code and we could do something like:&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="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&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="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SW_INIT&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;onUpdate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SW_UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reg&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;When those functions are called, they dispatch a function, and you can do whatever you want with them, like show a message.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;onSuccess&lt;/code&gt; it’s easier — you can just show the alert somewhere on your page. Perhaps it says, “Page has been saved for offline use.”. With onUpdate, you want to let the user know there’s a newer version available, and you can add a button with “Click to get the latest version.”.&lt;/p&gt;
&lt;h2&gt;
  
  
  Showing a user alert when page is first time saved for offline use
&lt;/h2&gt;

&lt;p&gt;In the above example, I used Redux store to dispatch an action, and I have store set up with:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initalState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;serviceWorkerInitialized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;serviceWorkerUpdated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;serviceWorkerRegistration&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now when dispatching &lt;code&gt;SW_INIT&lt;/code&gt; type action, we change &lt;code&gt;serviceWorkerInitialized&lt;/code&gt; state to &lt;code&gt;true&lt;/code&gt; and can use this selector inside any React component.&lt;/p&gt;

&lt;p&gt;In my &lt;code&gt;src/App.js&lt;/code&gt; (or any other component), we get it from store with Redux Hooks:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isServiceWorkerInitialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceWorkerInitialized&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And we can show an Alert when it is &lt;code&gt;true&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="nx"&gt;isServiceWorkerInitialized&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Alert&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page has been saved for offline use&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Tdb6R078--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AdQ-oTIRtgnbd8PQ84cXJVA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Tdb6R078--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AdQ-oTIRtgnbd8PQ84cXJVA.png" alt="Alert when Service Worker is installed"&gt;&lt;/a&gt;&lt;em&gt;&lt;sup&gt;Alert when Service Worker is installed&lt;/sup&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Showing the user an alert and a button when a new version of Service Worker is available
&lt;/h2&gt;

&lt;p&gt;Using the same pattern, we show alert components when service workers have been updated.&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="nx"&gt;isServiceWorkerUpdated&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Alert&lt;/span&gt;
    &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;There is a new version available.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;buttonText&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;updateServiceWorker&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This time we add an onClick function that’ll be triggered when clicking on the “Update” button inside the alert component. Because we want user to click on a button and get a new version of the app.&lt;/p&gt;

&lt;p&gt;All the magic is inside &lt;code&gt;updateServiceWorker&lt;/code&gt; function that we are going to create.&lt;/p&gt;

&lt;p&gt;This example is using CRA v3, which has a little addition generated inside the &lt;code&gt;public/service-worker.js&lt;/code&gt; file. (If you’re using an older version of CRA, I’ve created a solution for that, too — just write to me.)&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;&lt;code&gt;skipWaiting&lt;/code&gt; is a function that forces your new service worker to become the active one, and next time the user opens a browser and comes to your page, they can see the new version without having to do anything.&lt;/p&gt;

&lt;p&gt;You can read more about &lt;code&gt;skipWaiting&lt;/code&gt; from &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/skipWaiting"&gt;MDN&lt;/a&gt;. But this just forces your service worker to be the active one, and you see changes only next time. We want to ensure the user has a new version right now. That’s why we have to call it and then refresh the page ourselves — but only after the new service worker is active.&lt;/p&gt;

&lt;p&gt;To call it, we need an instance of our new service worker. If you scroll back up to where we registered the service worker, you can see the &lt;code&gt;onUpdate&lt;/code&gt; function had an argument called &lt;code&gt;reg&lt;/code&gt;. That’s the registration object, and that’s our instance. This will be sent to the &lt;code&gt;serviceWorkerRegistration&lt;/code&gt; property in the Redux store, and we can get our waiting SW from &lt;code&gt;serviceWorkerRegistration.waiting&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This will be our function that’s called when the user presses the “Update” button inside the alert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateServiceWorker&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registrationWaiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;serviceWorkerRegistration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiting&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;registrationWaiting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;registrationWaiting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SKIP_WAITING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;registrationWaiting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;statechange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;activated&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reload&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;Because the service worker is a worker and thus in another thread, to send any messages to another thread we have to use &lt;code&gt;Worker.postMessage&lt;/code&gt; (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage"&gt;MDN&lt;/a&gt;). Message type is &lt;code&gt;'SKIP_WAITING'&lt;/code&gt; as we saw from generated &lt;code&gt;public/service-worker.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;And we create an eventListener that waits for our new service-worker state change, and when it’s activated, we reload the page ourselves. And that’s pretty much it.&lt;/p&gt;

&lt;p&gt;Now the user can see there’s a newer version available, and if they want, they can update it right away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VlPTC90h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A4k3sIy_o9fICXdJug4wFtA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VlPTC90h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2A4k3sIy_o9fICXdJug4wFtA.png" alt="Alert when new Service Worker is available"&gt;&lt;/a&gt;&lt;em&gt;&lt;sup&gt;Alert when new Service Worker is available&lt;/sup&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I think it’s good to let the user decide if they want a new version right away or not. They have the option to click on the “Update” button and get the new version or just ignore it. Then, the new version of the app will be available when they close their tabs and open your app again.&lt;/p&gt;

&lt;p&gt;Thanks.&lt;/p&gt;

&lt;p&gt;Here’s a link to the &lt;a href="https://github.com/gglukmann/cra-sw"&gt;example repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>serviceworkers</category>
      <category>pwa</category>
    </item>
  </channel>
</rss>
