<?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>DEV Community: Manuel Sommerhalder</title>
    <description>The latest articles on DEV Community by Manuel Sommerhalder (@oncode).</description>
    <link>https://dev.to/oncode</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%2F24761%2F18160134-87c6-44f6-b813-95afeab42ab4.png</url>
      <title>DEV Community: Manuel Sommerhalder</title>
      <link>https://dev.to/oncode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oncode"/>
    <language>en</language>
    <item>
      <title>Display your PWA / website fullscreen</title>
      <dc:creator>Manuel Sommerhalder</dc:creator>
      <pubDate>Thu, 07 Jan 2021 09:43:11 +0000</pubDate>
      <link>https://dev.to/oncode/display-your-pwa-website-fullscreen-4776</link>
      <guid>https://dev.to/oncode/display-your-pwa-website-fullscreen-4776</guid>
      <description>&lt;p&gt;In this short article I will show you how to expand your installed PWA to the full edge of the screen of a mobile device and how to take care of design issues that might occur.&lt;/p&gt;

&lt;h1&gt;
  
  
  Standalone mode
&lt;/h1&gt;

&lt;p&gt;As soon as the user has added your PWA to the home screen and opened it, it runs in a standalone mode, where e.g. the navigation bar of the browser will disappear and we can produce more app-like designs.&lt;/p&gt;

&lt;p&gt;We can detect if the PWA is running in standalone mode inside our application like this:&lt;/p&gt;

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

&lt;span class="c1"&gt;// on iOS Safari&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;standalone&lt;/span&gt;

&lt;span class="c1"&gt;// on Android Chrome&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(display-mode: standalone)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Because of the platform difference, a more popular approach is to define a &lt;code&gt;start_url&lt;/code&gt; inside your &lt;code&gt;manifest.json&lt;/code&gt; file, where we can add a parameter that will be added, when the app is started in standalone mode. Then we can check for the parameter inside our application to make changes to our UI.&lt;/p&gt;

&lt;p&gt;To display it fullscreen and remove native control elements, we also have to add the &lt;code&gt;display&lt;/code&gt; property with the value &lt;code&gt;standalone&lt;/code&gt; (&lt;code&gt;fullscreen&lt;/code&gt; won't work). Here's our current manifest:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;manifest.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"standalone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/?standalone=true"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


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

&lt;/div&gt;
&lt;h1&gt;
  
  
  Meta Tags
&lt;/h1&gt;

&lt;p&gt;To display your app fullscreen, we will also have to add a few meta tags.&lt;/p&gt;

&lt;p&gt;You might already have a &lt;code&gt;viewport&lt;/code&gt; meta tag, but make sure &lt;code&gt;viewport-fit=cover&lt;/code&gt; is inside it (separated by &lt;code&gt;;&lt;/code&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;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt;
    &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width; initial-scale=1; viewport-fit=cover"&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;Then we have to add the &lt;code&gt;mobile-web-app-capable&lt;/code&gt; and &lt;code&gt;status-bar-style&lt;/code&gt; meta tags:&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;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"mobile-web-app-capable"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"yes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"apple-mobile-web-app-capable"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"yes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- possible content values: default, black or black-translucent --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"apple-mobile-web-app-status-bar-style"&lt;/span&gt;
    &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"black-translucent"&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;Note that we can decide, whether the status bar should have a white (default), black or transparent (with white color) background. Take into account that this meta tag is only used when the app starts, so it's app-wide and we can't modify the meta tag later to change the color dynamically.&lt;/p&gt;

&lt;p&gt;Because we also want to be able to display content underneath the status bar, we'll use the transparent background (&lt;code&gt;black-translucent&lt;/code&gt;). This will also shift our app up to the top of the screen.&lt;/p&gt;

&lt;p&gt;Now we can produce fancy headers that look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F16ndalhtlp5zp00ikk7f.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%2Fi%2F16ndalhtlp5zp00ikk7f.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Safe Area
&lt;/h1&gt;

&lt;p&gt;Since we can display content underneath the status bar now, we'll have to make sure that the white text will always be readable (e.g. with a decorative shadow or ensuring dark background colors) and that there will be no interactive elements underneath. Also we might have to take the notch into account, which some newer iOS versions have:&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%2Fi%2Fofha1w80q6vpy3uqi4x7.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%2Fi%2Fofha1w80q6vpy3uqi4x7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To solve those problems we can utilize the &lt;code&gt;safe-area&lt;/code&gt; CSS env variables (supported since iOS &amp;gt;= 11.1 and Chrome &amp;gt;= 69):&lt;/p&gt;

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

&lt;span class="nc"&gt;.element&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="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe-area-inset-top&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="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe-area-inset-left&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="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe-area-inset-right&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;you&lt;/span&gt; &lt;span class="err"&gt;can&lt;/span&gt; &lt;span class="err"&gt;also&lt;/span&gt; &lt;span class="err"&gt;use&lt;/span&gt; &lt;span class="err"&gt;fallback&lt;/span&gt; &lt;span class="err"&gt;values&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe-area-inset-bottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;20px&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;As an example, I implemented a sticky navigation bar vue component for my app, which has an element with the top safe-area applied as height. It will be transparent as long as the &lt;code&gt;transparent&lt;/code&gt; property is &lt;code&gt;true&lt;/code&gt; and the user has not scrolled down. Here's the crucial part of the 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;!-- NavigationBar.vue --&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;"sticky top-0"&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;"h-safe-area-inset-top"&lt;/span&gt;
        &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"{
            'bg-black': !transparent
                        || (transparent &amp;amp;&amp;amp; scrollTop &amp;gt; 0)
        }"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;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;style&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.h-safe-area-inset-top&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="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe-area-inset-top&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.bg-black&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;#444&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;span class="nt"&gt;&amp;lt;script&amp;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="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;scrollTop&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="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;:&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="nb"&gt;Boolean&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="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;scrollListener&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="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&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;scrollTop&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;mounted&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="nf"&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;scroll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollListener&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;If you have a question, don't hesitate to leave a comment. Happy coding and designing! 👨🏼‍💻&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>javascript</category>
      <category>html</category>
      <category>css</category>
    </item>
    <item>
      <title>How to embed a PWA into a (existing) native iOS / Android App</title>
      <dc:creator>Manuel Sommerhalder</dc:creator>
      <pubDate>Tue, 05 Jan 2021 19:40:57 +0000</pubDate>
      <link>https://dev.to/oncode/how-to-embed-a-pwa-into-a-existing-native-ios-android-app-2p4l</link>
      <guid>https://dev.to/oncode/how-to-embed-a-pwa-into-a-existing-native-ios-android-app-2p4l</guid>
      <description>&lt;p&gt;In this article I will show you, how to embed a progressive web app (PWA) or any website into a (existing) native app from a frontend perspective. How they can communicate with each other and navigation handling.&lt;/p&gt;

&lt;p&gt;The PWA was built with Nuxt, so the example code will be Nuxt specific, but the principles are applicable to every PWA. &lt;/p&gt;

&lt;h1&gt;
  
  
  Table of contents 📖
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;🤔 Why?&lt;/li&gt;
&lt;li&gt;🍪 Storage &amp;amp; Login Session&lt;/li&gt;
&lt;li&gt;📨 Communication&lt;/li&gt;
&lt;li&gt;🧭 Navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Why? 🤔
&lt;/h1&gt;

&lt;p&gt;A good question. You might consider following points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🏗 You may want to replace an existing iOS/Android app    with a PWA step by step or build some new feature that should also work on the web&lt;/li&gt;
&lt;li&gt;🤑 It reduces developer effort, you only need to develop once on one platform&lt;/li&gt;
&lt;li&gt;🤖 The new feature should be indexable by search engines&lt;/li&gt;
&lt;li&gt;🏃‍♂️ New features / fixes can be shipped faster, because you don't have to go through the publishing process&lt;/li&gt;
&lt;li&gt;🏠 App will still be available at the App/Play Store, so users can find and install it over different channels&lt;/li&gt;
&lt;li&gt;🦄 Maybe you want to do something that a PWA can't do (yet) like accessing the calendar, contacts or intercepting SMS/calls. Or do something iOS Safari can't do, because the API still is missing (e.g. background sync).&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Storage &amp;amp; Login Session 🍪
&lt;/h1&gt;

&lt;p&gt;The PWA will be displayed inside a &lt;a href="https://developer.apple.com/documentation/webkit/wkwebview" rel="noopener noreferrer"&gt;WKWebView&lt;/a&gt; (&amp;gt;= iOS 8). You can think of it as an iframe for native apps. Every WKWebView has its own storage data (cookies, localStorage, IndexedDB, etc.) and it will be restored when closed and reopened again. But the native App doesn't share its own cookies with the WebView.&lt;/p&gt;

&lt;p&gt;So if you need a logged in user, you should manually reuse the login session, to prevent the user from having to login in a second time, after opening the PWA in the WebView.&lt;/p&gt;

&lt;p&gt;To achieve this, the app developer can set a cookie with the already established session/token that he got e.g. from the initial app login screen or from a fingerprint. To set a cookie for the WebView, he can use the &lt;a href="https://developer.apple.com/documentation/webkit/wkhttpcookiestore" rel="noopener noreferrer"&gt;WKHTTPCookieStore&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%2Fi%2Fkd82iwmm0tjuv9vy1cfk.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%2Fi%2Fkd82iwmm0tjuv9vy1cfk.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Communication 📨
&lt;/h1&gt;

&lt;p&gt;You might want your PWA and native app to be able to talk to each other and tell them to execute actions. So we are going to build a bridge, where they can talk with each other.&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%2Fi%2Fej3ij7nj3kmqc3hkut1h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fej3ij7nj3kmqc3hkut1h.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this we will add a global object (&lt;code&gt;window.bridge&lt;/code&gt;) with two methods. One for invoking actions on the native app from inside the PWA (&lt;code&gt;invokeNative&lt;/code&gt;) and a second one for receiving messages and commands (&lt;code&gt;invokedByNative&lt;/code&gt;), which will be executed from within the native app. Inside this method, we can put the messages into our Vuex store, so that we can observe them.&lt;/p&gt;

&lt;p&gt;The method names, the data structure you pass to the iOS/Android developer and data you receive are up to you and the app developer.&lt;/p&gt;

&lt;p&gt;The app developer can inject JS Code into the WebView. For our example, he would have to define a global method &lt;code&gt;window.invokeCSharpAction&lt;/code&gt;, which receives a JSON string. We can check for that function to detect whether we are inside the app or just in a normal browser.&lt;/p&gt;

&lt;p&gt;Below, the code for the bridge, which was put into a Nuxt plugin:&lt;/p&gt;

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

&lt;span class="c1"&gt;// bridge.client.js nuxt plugin&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="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inject&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="c1"&gt;// force app mode with ?app param to be able to test&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// determine whether the app is opened inside native app&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&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;invokeCSharpAction&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// inject global $inApp variable and &lt;/span&gt;
    &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inApp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inApp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;setInApp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inApp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// the bridge object in the global namespace&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;bridge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// invoke native function via PWA&lt;/span&gt;
        &lt;span class="nf"&gt;invokeNative&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="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invokeCSharpAction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="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="nf"&gt;invokeCSharpAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="c1"&gt;// called by native app&lt;/span&gt;
        &lt;span class="nf"&gt;invokedByNative&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="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// write passed data to the store&lt;/span&gt;
            &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;addAppMessage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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="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;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bridge&lt;/span&gt;&lt;span class="dl"&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;bridge&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;After setting up the bridge, we are able to invoke native actions inside our PWA like this:&lt;/p&gt;

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

&lt;span class="c1"&gt;// callable in stores, components &amp;amp; plugins&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$bridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invokeNative&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Close&lt;/span&gt;&lt;span class="dl"&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;SaveSomething&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&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;title&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;And the native app developer can call PWA actions by executing JS code like this:&lt;/p&gt;

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

&lt;span class="c1"&gt;// callable in native app&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$bridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invokedByNative&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GoBack&lt;/span&gt;&lt;span class="dl"&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;HasSavedSomething&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;success&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;A save action inside a Vuex store could look like this:&lt;/p&gt;

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

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;saveSomething&lt;/span&gt;&lt;span class="p"&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;commit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rootGetters&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// prevent saving while it's already saving &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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isSaving&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;setIsSaving&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// data we want to pass to the app or to our API&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&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="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// call the bridge method inside app&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$inApp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$bridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invokeNative&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SaveSomething&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;payload&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// otherwise we will call the API like we're used to&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// await POST or PUT request response ...&lt;/span&gt;

        &lt;span class="c1"&gt;// finish saving and set response id&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;setId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Failed, inform user 😢&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;setIsSaving&lt;/span&gt;&lt;span class="dl"&gt;'&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="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;You might noticed that we don't get a direct response from the bridge method like we would get from an ordinary API call. To be able to know when the app has finished the action and whether is was successful, the native app has to inform us by calling the &lt;code&gt;invokedByNative&lt;/code&gt; method. In the PWA, we can listen to the received message like this:&lt;/p&gt;

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

&lt;span class="c1"&gt;// inside app / layout component&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;mapState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mapMutations&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;vuex&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="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// map messages and isSaving to show a loader e.g.&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;mapState&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appMessages&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;isSaving&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;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// map necessary mutations&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;mapMutations&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;setId&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;setIsSaving&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;watch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// watch the messages from the store for any changes&lt;/span&gt;
        &lt;span class="nf"&gt;appMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// get last received message&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastMsg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mgs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastMsg&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastMsg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

            &lt;span class="c1"&gt;// check if last message belongs to function we expect a response from&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;appFunction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HasSavedSomething&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// Failed, inform user 😢&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setIsSaving&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="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;Now we're done setting up the bridge and can send each other commands!&lt;/p&gt;

&lt;h1&gt;
  
  
  Navigation 🧭
&lt;/h1&gt;

&lt;p&gt;When your PWA is running inside a WebView just as part of a native app, make sure that the user is always able to get back to the app, without having to close the entire app.&lt;/p&gt;

&lt;p&gt;You can utilize the global &lt;code&gt;$inApp&lt;/code&gt; variable we have set before in the Nuxt plugin to change your templates and components. You may want to display a close button in a menubar when the PWA is opened in the WebView or make other design adjustments:&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%2Fi%2Ffblr3fz34sn37abug0r4.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%2Fi%2Ffblr3fz34sn37abug0r4.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Furthermore, the app developer should make sure to catch HTTP errors like 404 or 500, close the WebView and possibly show a message to inform the user.&lt;/p&gt;

&lt;p&gt;Another challenge is implementing the back button/navigation. On Android we usually have a back button at the bottom. On iOS we don't have one, but could use a swipe gesture instead.&lt;/p&gt;

&lt;p&gt;When the user navigates back, &lt;code&gt;history.back&lt;/code&gt; should be called inside the PWA as long as there are previous visited sites, otherwise the WebView should be closed.&lt;/p&gt;

&lt;p&gt;Unfortunately, the &lt;code&gt;window.history&lt;/code&gt; API doesn't offer a possibility to detect on which position you are currently in your history entries or accessing them. Also the &lt;a href="https://developer.apple.com/documentation/webkit/wkwebview/1414966-cangoback#declaration" rel="noopener noreferrer"&gt;canGoBack&lt;/a&gt; property doesn't seem to work, when &lt;code&gt;pushState&lt;/code&gt; is used inside the PWA, to update URLs. &lt;/p&gt;

&lt;p&gt;We can solve this inside the PWA, by implementing our own history / back-forward list:&lt;/p&gt;

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

&lt;span class="c1"&gt;// inside app / layout component&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="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;history&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="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// watch route which updates when URL has changed&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$route&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// find if target page has been visited&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appRouteHistory&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;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;to&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="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&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="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// back navigation:&lt;/span&gt;
                &lt;span class="c1"&gt;// remove every entry that is&lt;/span&gt;
                &lt;span class="c1"&gt;// after target page in history&lt;/span&gt;
                &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appRouteHistory&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;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// forward navigation&lt;/span&gt;
                &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appRouteHistory&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="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;from&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="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;goBack&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;lastAppHistoryEntry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appRouteHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appRouteHistory&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appRouteHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastAppHistoryEntry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// go back to last entry&lt;/span&gt;
                &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$router&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="nx"&gt;lastAppHistoryEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// tell the app it should close the WebView&lt;/span&gt;
                &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$bridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invokeNative&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Close&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;p&gt;Inside the app, the developer can overwrite the back button functionality to call this JS code:&lt;/p&gt;

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

&lt;span class="c1"&gt;// callable in native app to go back in PWA or close WebView&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$bridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invokedByNative&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GoBack&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, make also sure listen to the &lt;code&gt;GoBack&lt;/code&gt; message inside the &lt;code&gt;watch: { appMessages() }&lt;/code&gt;method (see implementation in the communication section above) and call the &lt;code&gt;goBack&lt;/code&gt; method.&lt;/p&gt;


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

&lt;p&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appFunction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GoBack&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;br&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goBack&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  The End 🔚&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;I hope this article gave you an quick overview of establishing a connection between your PWA and (existing) native app. Leave a comment, if you have any questions!&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>javascript</category>
      <category>ios</category>
      <category>android</category>
    </item>
    <item>
      <title>Setup a Vendure E-Commerce API on a Dokku Droplet</title>
      <dc:creator>Manuel Sommerhalder</dc:creator>
      <pubDate>Sun, 28 Jun 2020 12:10:44 +0000</pubDate>
      <link>https://dev.to/oncode/how-to-setup-a-vendure-e-commerce-api-on-a-dokku-droplet-3enc</link>
      <guid>https://dev.to/oncode/how-to-setup-a-vendure-e-commerce-api-on-a-dokku-droplet-3enc</guid>
      <description>&lt;p&gt;If you haven't heard of Vendure, it is a "modern, headless GraphQL-based e-commerce framework built with TypeScript &amp;amp; Nodejs". It's still in beta but already used in production and a major release is planned until end of this year. Check it out on &lt;a href="https://www.vendure.io/"&gt;vendure.io&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;This guide assumes that you have already installed a local Vendure project via &lt;a href="https://www.vendure.io/docs/getting-started/"&gt;@vendure/create&lt;/a&gt;. It will lead you through the setup of Vendure on a Dokku droplet, hosted by DigitalOcean with some tips for your production system. If you have no DigitalOcean account yet, you can use this &lt;a href="https://m.do.co/c/6f1dc5cbfd78"&gt;referral link&lt;/a&gt; if you want, to get $100 over 60 days. &lt;/p&gt;

&lt;h1&gt;
  
  
  Create Droplet
&lt;/h1&gt;

&lt;p&gt;First you can create the Dokku droplet with the one-click installer here: &lt;a href="https://marketplace.digitalocean.com/apps/dokku"&gt;https://marketplace.digitalocean.com/apps/dokku&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When creating the droplet, you will see three setting fields: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public Key&lt;/strong&gt;: For adding an SSH key to login into your server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hostname&lt;/strong&gt;: For setting the hostname (e.g. &lt;code&gt;example.com&lt;/code&gt;). You an also just use the IP address of your droplet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use virtualhost naming for apps&lt;/strong&gt;: Enable this if you want the app URLs by default to be &lt;code&gt;APP_NAME.example.com&lt;/code&gt; instead of &lt;code&gt;example.com:APP_PORT_NUMBER&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make sure your droplet has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enough disk space&lt;/strong&gt;: I'm currently using 9.5GB including OS, Docker containers and around 200 product images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enough memory&lt;/strong&gt;: Especially if you are going use the &lt;a href="https://www.vendure.io/docs/typescript-api/elasticsearch-plugin/"&gt;ElasticsearchPlugin&lt;/a&gt; to search through products. I would recommend &lt;strong&gt;at least 3GB of memory and a swapfile of 3GB&lt;/strong&gt; (we will create one later). This should be enough in the beginning and the swapfile can cover possible memory peaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A firewall&lt;/strong&gt;: To secure your droplet, make sure you restrict inbound rules to &lt;strong&gt;only HTTP(S)&lt;/strong&gt; and also SSH if you want to login on your server via SSH. This will prevent outsiders from accessing your Elasticsearch instance on port 9200/9300. On your droplet overview click on &lt;code&gt;Secure your Droplets&lt;/code&gt; and add a new firewall. Set the inbound rules to HTTPS and SSH and save. You firewall should look like this:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r_GFd8LY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gx4gq1oly0nkc680tonw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r_GFd8LY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gx4gq1oly0nkc680tonw.png" alt="Alt Text" width="880" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It might also make sense for you to enable backups for weekly snapshots, after the shop is up and running.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup Dokku Environment
&lt;/h1&gt;

&lt;p&gt;When the droplet is ready and you are able to connect with your previously added SSH key (&lt;code&gt;ssh -i SSH_KEY_NAME root@IP_OF_YOUR_DROPLET&lt;/code&gt;), we can start setting up Dokku and its services. First we will create the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku apps:create myshopapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So our API is later going to be available on &lt;code&gt;myshopapi.example.com/shop-api&lt;/code&gt; and the admin area on &lt;code&gt;myshopapi.example.com/admin&lt;/code&gt;. Dokku will provide the ENV variable &lt;code&gt;PORT&lt;/code&gt;, which we will use later in our config file.&lt;/p&gt;

&lt;h1&gt;
  
  
  Create storage folder
&lt;/h1&gt;

&lt;p&gt;Then we will create a persistent storage folder that will get mounted to the &lt;code&gt;/storage&lt;/code&gt; folder of the app when the application starts. It stores product assets, mail templates and test mails. On your droplet run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# create folder and set correct ownership
mkdir -p  /var/lib/dokku/data/storage/myshopapi
chown -R dokku:dokku /var/lib/dokku/data/storage/myshopapi

# mount it to your app container to /storage
dokku storage:mount myshopapi /var/lib/dokku/data/storage/myshopapi:/app/storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then zip and upload the contents of the &lt;code&gt;/static&lt;/code&gt; folder from your local computer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# create zip file
cd ~/YOURLOCALPROJECTFOLDER/static
zip -r ../storage.zip . *

# upload it to your droplet
scp ~/YOURLOCALPROJECTFOLDER/storage.zip root@IP_OF_YOUR_DROPLET:/var/lib/dokku/data/storage/myshopapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back at your droplet unzip it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# unzip folders
unzip /var/lib/dokku/data/storage/myshopapi/storage.zip
# remove the zip
rm /var/lib/dokku/data/storage/myshopapi/storage.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you should have your &lt;code&gt;assets&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; folders inside the &lt;code&gt;/var/lib/dokku/data/storage/myshopapi&lt;/code&gt; folder.&lt;/p&gt;

&lt;h1&gt;
  
  
  Install &lt;a href="https://github.com/dokku/dokku-mysql"&gt;MySQL Dokku Plugin&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;I choose MySQL but you can also use Postgres, MariaDB or SQLite if you like. Let's call the service &lt;code&gt;myshopapi-mysql&lt;/code&gt; and link it to the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo dokku plugin:install https://github.com/dokku/dokku-mysql.git mysql
dokku mysql:create myshopapi-mysql
dokku mysql:link myshopapi-mysql myshopapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the installation is complete you should get some data/config directories and the ENV variable &lt;code&gt;DATABASE_URL&lt;/code&gt;. The value should look like this: &lt;code&gt;mysql://mysql:YOUR_MYSQL_PASSWORT@dokku-mysql-myshopapi-mysql:3306/myshopapi_mysql&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For easier usage of the login data in our config file later, we set our own custom ENV variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku config:set --no-restart myshopapi MYSQL_PORT=3306
dokku config:set --no-restart myshopapi MYSQL_USER=mysql
dokku config:set --no-restart myshopapi MYSQL_PASSWORD=YOUR_MYSQL_PASSWORD
dokku config:set --no-restart myshopapi MYSQL_HOST=dokku-mysql-myshopapi-mysql
dokku config:set --no-restart myshopapi MYSQL_DB=myshopapi_mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Install &lt;a href="https://github.com/dokku/dokku-elasticsearch"&gt;Elasticsearch Dokku Plugin&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;First we install the plugin and create the service. Vendure should work with v7.0 or higher. I'm currently using v7.5.2. Then we increase the &lt;code&gt;max_map_count&lt;/code&gt; option of the virtual machine to &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html"&gt;prevent out of memory exceptions&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# install plugin
sudo dokku plugin:install https://github.com/dokku/dokku-elasticsearch.git elasticsearch
# set version you want to use
export ELASTICSEARCH_IMAGE_VERSION="7.5.2"
# create service
dokku elasticsearch:create myshopapi-elasticsearch
# expose the service to ports
dokku elasticsearch:expose myshopapi-elasticsearch 9200 9300
# link the service to your app
dokku elasticsearch:link myshopapi-elasticsearch myshopapi
# increase max_map_count 
echo 'vm.max_map_count=262144' | sudo tee -a /etc/sysctl.conf; sudo sysctl -p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since Dokku seems to have &lt;a href="https://github.com/dokku/dokku-elasticsearch/issues/72#issuecomment-510771763"&gt;an issue&lt;/a&gt; connecting with Elasticsearch &lt;strong&gt;v7.&lt;/strong&gt;*, you will get an &lt;code&gt;unable to connect&lt;/code&gt; error after creating the service. We also have to paste in following into the &lt;code&gt;/var/lib/dokku/services/elasticsearch/myshopapi-elasticsearch/config/elasticsearch.yml&lt;/code&gt; file, to be able to connect to the instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node.name: node-1
cluster.name: docker-cluster
network.host: 0.0.0.0
cluster.initial_master_nodes:
  - node-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also get an ENV variable during this process named &lt;code&gt;ELASTICSEARCH_URL&lt;/code&gt; which looks like this: &lt;code&gt;http://dokku-elasticsearch-myshopapi-elasticsearch:9200&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We will also split it in our own variables, to use it later in our config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku config:set --no-restart myshopapi ELASTICSEARCH_HOST=http://dokku-elasticsearch-myshopapi-elasticsearch
dokku config:set --no-restart myshopapi ELASTICSEARCH_PORT=9200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Create a &lt;a href="https://linuxize.com/post/create-a-linux-swap-file/"&gt;Swapfile&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;I still experienced memory overflows sometimes on production when Elasticsearch was busy. We can create a 3GB swapfile to help cover those like previously mentioned. You can also create a larger one, the recommendations vary. Changing it or adding another file later is possible too.&lt;/p&gt;

&lt;p&gt;Further, we will set the &lt;code&gt;swappiness&lt;/code&gt; variable to &lt;code&gt;10&lt;/code&gt;, so the virtual machine is less likely going to use the swapfile instead of the memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# create 3GB swapfile
fallocate -l 3G /swapfile
# set correct permissions
chmod 600 /swapfile
# set up swap area
mkswap /swapfile
# turn swap one
swapon /swapfile
# save swap file in config to use after restart
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# check if the swap is on
swapon --show
# set the swappiness
sysctl vm.swappiness=10
# save config to use after restart
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Install &lt;a href="https://github.com/dokku/dokku-letsencrypt"&gt;LetsEncrypt Dokku Plugin&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;What would be a good shop without SSL? So now we add the domain for the vendure instance and install the LetsEncrypt plugin and add a cronjob to renew the certificate automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# add domain to dokku app
dokku domains:set myshopapi API.YOURDOMAIN.COM

# install letsencrypt
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git

# set your email
dokku config:set --no-restart myshopapi DOKKU_LETSENCRYPT_EMAIL=YOUREMAIL@example.com

# install certificate for domain and add renewal cron job
dokku letsencrypt:enable myshopapi
dokku letsencrypt:cron-job --add
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Set further Environment Variables
&lt;/h1&gt;

&lt;p&gt;Since sensitive data should not be in your source code when checking into git, we will add some more ENV variables for the SMTP connection, which will be used to send emails and also one for the session secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dokku config:set --no-restart myshopapi SESSION_SECRET=YOUR_SESSION_SECRET_KEY

dokku config:set --no-restart myshopapi SMTP_HOST=YOUR_SMTP_HOST
dokku config:set --no-restart myshopapi SMTP_PORT=YOUR_SMTP_PORT
dokku config:set --no-restart myshopapi SMTP_USER=YOUR_SMTP_USER
dokku config:set --no-restart myshopapi SMTP_PASSWORD=YOUR_SMTP_PASSWORD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Change your &lt;code&gt;vendure-config.ts&lt;/code&gt; File
&lt;/h1&gt;

&lt;p&gt;Now that we have everything ready, we can update our config file with all the ENV variables. We will also add &lt;code&gt;cors.origin&lt;/code&gt; setting to be able to query the API &lt;code&gt;myshopapi.example.com&lt;/code&gt; from &lt;code&gt;example.com&lt;/code&gt; and set the correct &lt;code&gt;assetUrlPrefix&lt;/code&gt;. Here's how your config file could look:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&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;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;VendureConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;DefaultJobQueuePlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;examplePaymentHandler&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;@vendure/core&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;Transport&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;@nestjs/microservices&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;AssetServerPlugin&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;@vendure/asset-server-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;AdminUiPlugin&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;@vendure/admin-ui-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;ElasticsearchPlugin&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;@vendure/elasticsearch-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;EmailPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultEmailHandlers&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;@vendure/email-plugin&lt;/span&gt;&lt;span class="dl"&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;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VendureConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;workerOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TCP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;options&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;3020&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;apiOptions&lt;/span&gt;&lt;span class="p"&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="nb"&gt;Number&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;PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;adminApiPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;shopApiPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shop-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/example&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;com$/&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sessionSecret&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;SESSION_SECRET&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;dbConnectionOptions&lt;/span&gt;&lt;span class="p"&gt;:&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;mysql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;synchronize&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;logging&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;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&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;MYSQL_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3306&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;database&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;MYSQL_DB&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="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;MYSQL_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;username&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;MYSQL_USER&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="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;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;join&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;../migrations/*.ts&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;paymentOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;paymentMethodHandlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;examplePaymentHandler&lt;/span&gt;&lt;span class="p"&gt;]&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="nx"&gt;DefaultJobQueuePlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;AssetServerPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&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;3001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;assetUploadDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/storage/assets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;assetUrlPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://myshopapi.example.com/assets/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nx"&gt;ElasticsearchPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&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="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;ELASTICSEARCH_HOST&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="nb"&gt;Number&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;ELASTICSEARCH_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;9200&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nx"&gt;EmailPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;handlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;defaultEmailHandlers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;templatePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/storage/email/templates&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;:&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;smtp&lt;/span&gt;&lt;span class="dl"&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="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;SMTP_HOST&lt;/span&gt; &lt;span class="o"&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;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&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;SMTP_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;user&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;SMTP_USER&lt;/span&gt; &lt;span class="o"&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;pass&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;SMTP_PASSWORD&lt;/span&gt; &lt;span class="o"&gt;||&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;globalTemplateVars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;fromAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"Example" &amp;lt;info@example.ch&amp;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;verifyEmailAddressUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/verify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;passwordResetUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/password-reset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;changeEmailAddressUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/verify-email-address-change&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;AdminUiPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&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;3002&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="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="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Setup Git
&lt;/h1&gt;

&lt;p&gt;Finally we can add the droplet as remote in our git repostory and push our code to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git remote add dokku dokku@IP_OF_YOUR_DROPLET:myshopapi
git push dokku master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Some useful Dokku commands
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# output app logs
dokku logs myshopapi
# output Elasticsearch logs
dokku elasticsearch:logs myshopapi-elasticsearch
# restart the app
dokku ps:restart myshopapi
# connect to MySQL database
dokku mysql:connect myshopapi-mysql
USE myshopapi_mysql;
# export/import an SQL file from/into database
dokku mysql:export myshopapi-mysql &amp;gt; backup.sql
dokku mysql:import myshopapi-mysql &amp;lt; backup.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this guide will help you setting up your shop API. Please comment if something is not working or if you have other tips, that you would like to share. You can also join the &lt;a href="https://join.slack.com/t/vendure-ecommerce/shared_invite/enQtNzA1NTcyMDY3NTg0LTMzZGQzNDczOWJiMTU2YjAyNWJlMzdmZGE3ZDY5Y2RjMGYxZWNlYTI4NmU4Y2Q1MDNlYzE4MzQ5ODcyYTdmMGU"&gt;Slack channel&lt;/a&gt; or look into the &lt;a href="https://github.com/vendure-ecommerce/real-world-vendure"&gt;real world Vendure project&lt;/a&gt; on Github, which might help you too.&lt;/p&gt;

</description>
      <category>vendure</category>
      <category>headless</category>
      <category>graphql</category>
      <category>node</category>
    </item>
    <item>
      <title>Accessibility Developer Guide: Setup and use a blind user's environment</title>
      <dc:creator>Manuel Sommerhalder</dc:creator>
      <pubDate>Sun, 23 Jun 2019 00:24:09 +0000</pubDate>
      <link>https://dev.to/oncode/accessibility-developer-guide-setup-and-use-a-blind-user-s-environment-199n</link>
      <guid>https://dev.to/oncode/accessibility-developer-guide-setup-and-use-a-blind-user-s-environment-199n</guid>
      <description>&lt;p&gt;The awareness for accessibility in website development gladly seems to have increased a lot in the past few years. A great resource for how to build and test accessible websites and widgets is the &lt;a href="https://www.accessibility-developer-guide.com/"&gt;Accessibility Developer Guide&lt;/a&gt; created by the &lt;a href="https://www.access-for-all.ch/en/accessibility.html"&gt;Access4all&lt;/a&gt; foundation and its partner agencies. Check it out if you haven't done it yet!&lt;/p&gt;

&lt;p&gt;In this article I want to get you started on how to setup an environment to test your websites from a blind user's perspective with a screen reader.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup Environment
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Used software&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt; with &lt;a href="https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/"&gt;Windows&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nvaccess.org/"&gt;NVDA screen reader&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pqrs.org/osx/karabiner/"&gt;karabiner-elements&lt;/a&gt; (Mac Users)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mozilla.org/en-US/firefox/organizations/all/"&gt;Firefox ESR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.google.com/chrome/"&gt;Chrome&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create &lt;code&gt;Insert&lt;/code&gt; Key
&lt;/h2&gt;

&lt;p&gt;When using a screen reader, you sometimes need a modifier key for special key combinations. &lt;code&gt;Caps Lock&lt;/code&gt; and &lt;code&gt;Insert&lt;/code&gt; are offered but &lt;code&gt;Caps Lock&lt;/code&gt; doesn't seem to work with VirtualBox.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For Mac Users&lt;/em&gt;: There is no native &lt;code&gt;Insert&lt;/code&gt; key on a MacBook, but you can remap an unused key as &lt;code&gt;Insert&lt;/code&gt; (e.g. right &lt;code&gt;Command&lt;/code&gt; ⌘, &lt;code&gt;Option&lt;/code&gt; ⌥ or &lt;code&gt;Shift&lt;/code&gt; ⇧) like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download, install and run &lt;a href="https://pqrs.org/osx/karabiner/"&gt;karabiner-elements&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Simple Modifications&lt;/code&gt; -&amp;gt; &lt;code&gt;Add item&lt;/code&gt; and choose a desired &lt;code&gt;From key&lt;/code&gt; and set &lt;code&gt;To key&lt;/code&gt; to &lt;code&gt;Insert&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install Virtual Machine
&lt;/h2&gt;

&lt;p&gt;To install the screen reader software a Windows OS is required. It makes sense to install it as a virtual machine to prevent polluting your system and have a ready environment for testing. Microsoft gives away a free 90 days Windows trial for browser testing that you can use. I went for the &lt;code&gt;MSEdge on Win10&lt;/code&gt;, since IE11 is also installed along with Edge and therefore also useful for normal cross browser testing, especially on a Mac.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download &amp;amp; install &lt;a href="https://www.virtualbox.org/wiki/Downloads"&gt;VirtualBox&lt;/a&gt; &lt;em&gt;(or use your already existing virtual machine software)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Download &amp;amp; setup &lt;a href="https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/"&gt;Windows from Modern.IE&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install Screen Reader &amp;amp; Browsers
&lt;/h2&gt;

&lt;p&gt;The two most commonly used screen readers are JAWS (66%) and NVDA (64.9%). As you see, screen reader users tend to use more than just one screen reader. Since NVDA is free to use, we will use that one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download &amp;amp; install &lt;a href="https://www.nvaccess.org/download/"&gt;NVDA&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A screen reader software just lays on top of a normal browser and users can decide which browser they want to use. Hence you should also install other popular browsers besides IE. Usually JAWS is used with IE (24.7%) and NVDA is used with Firefox (23.6%). For Firefox you need to install a special extended support release (ESR).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download &amp;amp; install &lt;a href="https://www.mozilla.org/en-US/firefox/organizations/all/"&gt;Firefox ESR&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Download &amp;amp; install &lt;a href="https://www.google.com/chrome/"&gt;Chrome&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configure NVDA
&lt;/h3&gt;

&lt;p&gt;You should primarily go with the default configuration, but there are some options that are helpful with testing. If NVDA is running, press &lt;code&gt;Insert + N&lt;/code&gt; to open the NVDA menu.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open speech viewer by choosing &lt;code&gt;Tools&lt;/code&gt; -&amp;gt; &lt;code&gt;Speech viewer&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Check option &lt;code&gt;Show Speech Viewer on Startup&lt;/code&gt; &lt;em&gt;(always see what has been announced by the screen reader)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Open the settings window by choosing &lt;code&gt;Preferences&lt;/code&gt; -&amp;gt; &lt;code&gt;Settings...&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Choose &lt;code&gt;General&lt;/code&gt; -&amp;gt; select the &lt;code&gt;Language&lt;/code&gt; you want&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;Speech&lt;/code&gt; -&amp;gt; &lt;code&gt;Synthesizer&lt;/code&gt; -&amp;gt; Press &lt;code&gt;Change...&lt;/code&gt; -&amp;gt; select &lt;code&gt;Windows OneCore voices&lt;/code&gt; and press &lt;code&gt;OK&lt;/code&gt;. Then select the &lt;code&gt;Voice&lt;/code&gt; you like most.&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;Keyboard&lt;/code&gt; -&amp;gt; Uncheck &lt;code&gt;Speak typed characters&lt;/code&gt; &lt;em&gt;(prevents announcing characters while typing)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;Mouse&lt;/code&gt; -&amp;gt; Uncheck &lt;code&gt;Enable mouse tracking&lt;/code&gt; &lt;em&gt;(prevents announcing the objects below the mouse)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;Browse Mode&lt;/code&gt; -&amp;gt; Uncheck &lt;code&gt;Automatic Say All on page load&lt;/code&gt; &lt;em&gt;(prevents starting to read the whole document on page load)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally you should also install the Focus Highlight plugin. It shows you the current position of the navigator and focused object (see Focus and Browse Mode below).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download &amp;amp; install &lt;a href="https://addons.nvda-project.org/addons/focusHighlight.en.html"&gt;Focus Highlight&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Border&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;green &amp;amp; thin (dashed)&lt;/td&gt;
&lt;td&gt;indicates the navigator object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;red &amp;amp; thin&lt;/td&gt;
&lt;td&gt;indicates the focused object/control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;red &amp;amp; thick&lt;/td&gt;
&lt;td&gt;indicates when navigator object and focused object are overlapping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;blue &amp;amp; thick (dashed)&lt;/td&gt;
&lt;td&gt;indicates NVDA is in focus mode, i.e. key types are passed to the control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: After installing/configuring the screen reader and browsers you should take a snapshot from the system to be able to restore it after Windows has expired.&lt;/p&gt;

&lt;h1&gt;
  
  
  Usage of Screen Reader
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Focus and Browse Mode &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A screen reader essentially has two modes, browse and focus mode. When reading a page, blind users use browse mode by using &lt;code&gt;Down/Up&lt;/code&gt; arrow keys to sequentially read line by line. It moves the navigator object. Other shortcuts are used to move it to certain elements of the website like headings or links (see Browse/Read Mode Shortcuts below).&lt;/p&gt;

&lt;p&gt;If you want to interact with elements, like entering text into an input field, you have to enter focus mode. You can switch to it by pressing the &lt;code&gt;Enter&lt;/code&gt; key when the navigator object is over the element or by using the &lt;code&gt;Tab&lt;/code&gt; key to focus the next interactive element. In focus mode, the screen reader also tries to find and announce associated information (e.g. from a label or aria-describedby). You can go back to browse mode with &lt;code&gt;ESC&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyboard Shortcuts
&lt;/h2&gt;

&lt;p&gt;The following is an overview of most shortcuts for the NVDA screen reader.&lt;/p&gt;

&lt;h3&gt;
  
  
  General
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tab
Jump to next focusable element and activates focus mode where appropriate.

Ctrl
Abort announcing current stream of words

Ctrl + Home
Jump to beginning of the page

Ctrl + Alt + N
Start/Restart NVDA

Insert + N
Open NVDA menu

Insert + S
Toggle speech mode (on, off or beep mode)

Insert + F7
Shows the elements list.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Browse/Read Mode &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Down / Up Arrow
Read content sequentially line by line

Ctrl + Down / Up Arrow
Read paragraph by paragraph (or any other kind of container)

Left / Right Arrow
Read next / previous character

Enter
Switch to focus mode if available (sound is played)

H
Move to next heading

1-6
Jump to next headings on specific level

K
Move no next link

V / U
Jump to visited / unvisited links

L / I
Jump to lists / list item

G
Jump to images

T / Ctrl + Alt + Left/Right/Up/Down
Jump to tables / navigate through

F
Jump to forms

B
Jump to buttons

D
Jump to landmarks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Focus/Interaction Mode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Esc
Switch to browse/read mode (sound is played)

Space
Toggle checkboxes / radios or expand / collapse comboboxes

Enter
Activate Links / submit forms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Alternative Tools
&lt;/h1&gt;

&lt;p&gt;You may don't have the possibility to install a virtual machine on your computer. There are some alternative tools you can use to ensure accessibility to some extent. But be aware that the majority of blind users use NVDA or JAWS and they might behave differently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://www.chromevox.com/"&gt;Chrome Vox&lt;/a&gt; - browser extension for Chrome used as screen reader for chromebooks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://wave.webaim.org/extension/"&gt;WAVE&lt;/a&gt; - accessibility audits, as browser extension for Chrome and Firefox&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/web/tools/lighthouse/"&gt;A11y&lt;/a&gt; - accessibility audits, as npm module / cli (also integrated in Chrome Dev Tools)&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://pa11y.org/"&gt;Pa11y&lt;/a&gt; - accessibility audits, as npm module / cli, webservice, dashboard, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Further Links:
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.accessibility-developer-guide.com/"&gt;Accessibility Developer Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nvaccess.org/files/nvda/documentation/userGuide.html"&gt;NVDA User Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webaim.org/projects/screenreadersurvey7/"&gt;WebAIM Survey about Screenreaders&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>testing</category>
      <category>screenreaders</category>
    </item>
  </channel>
</rss>
