<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Tijs Martens</title>
    <description>The latest articles on Forem by Tijs Martens (@martenstijs).</description>
    <link>https://forem.com/martenstijs</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%2F373159%2F6b64c38f-db52-4b04-bd96-c1fa0047d952.jpeg</url>
      <title>Forem: Tijs Martens</title>
      <link>https://forem.com/martenstijs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/martenstijs"/>
    <language>en</language>
    <item>
      <title>How we used Notion as a CMS for our blog.</title>
      <dc:creator>Tijs Martens</dc:creator>
      <pubDate>Wed, 19 Jan 2022 08:52:40 +0000</pubDate>
      <link>https://forem.com/martenstijs/how-we-used-notion-as-a-cms-for-our-blog-5b63</link>
      <guid>https://forem.com/martenstijs/how-we-used-notion-as-a-cms-for-our-blog-5b63</guid>
      <description>&lt;p&gt;A couple of months ago, Notion announced that they released a public API that everybody can use to consume their own Notion workspace.&lt;/p&gt;

&lt;p&gt;We were intrigued, and wanted to see how we could benefit from this new feature.&lt;/p&gt;

&lt;p&gt;Since we started with Rodi, we struggled with the optimisation of our landingspage. We still believe that “Rodi” is a great name for our cycling app, but we are not the only ones that like this name and there are other companies who share this name. As a consequence, ranking high on google isn’t that easy.&lt;/p&gt;

&lt;p&gt;One way to improve the SEO of a website is adding a blog. This gives google more context on what the product is that you’re promoting and if the blog posts are good, some people might link back to your website/ blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;So what were the requirements that we set for ourselves&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use an intuitive editor to write content&lt;/li&gt;
&lt;li&gt;Publishing and un-publishing an article, should not require any changes to the code&lt;/li&gt;
&lt;li&gt;Support markup and richt text formatting (titles, lists, links, code-blocks ....)&lt;/li&gt;
&lt;li&gt;SEO-friendly url’s&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After reading the docs of the Notion api, we decided that Notion could do the trick and we went to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notion setup
&lt;/h2&gt;

&lt;p&gt;The first thing we needed to do was creating a Notion database&lt;/p&gt;

&lt;p&gt;We added the following columns&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name - the title of the blog.&lt;/li&gt;
&lt;li&gt;Status - is not used in the code, but it’s handy to keep track of what’s the current status of an article in Notion.&lt;/li&gt;
&lt;li&gt;Published - checking the checkbox will immediately publish that article.&lt;/li&gt;
&lt;li&gt;Intro - a little description on what the article will touch.&lt;/li&gt;
&lt;li&gt;Url - the author of the blog can choose what the slug of the url will be. (rodi.app/blog/[Url])&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  Hacking everything together
&lt;/h2&gt;

&lt;p&gt;Our landing page is built using Next.js. I’ll not go into the details of the code and only cover some high level topics. But with the code snippets that are shown, you should be able to get an idea of what it takes to build a Notion powered blog. If you want to see all the code, &lt;a href="https://github.com/TijsM/rodi-landing-page/pull/22" rel="noopener noreferrer"&gt;you can check the pull request that added this blog to the website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can always find a “get started” and more details in the &lt;a href="https://developers.notion.com/" rel="noopener noreferrer"&gt;Notion docs&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get all the published articles
&lt;/h3&gt;

&lt;p&gt;First we want to get an overview of all the published articles.&lt;/p&gt;

&lt;p&gt;To be able to fetch items from our database, we need to share our database with our integration:&lt;/p&gt;

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

&lt;p&gt;When this is done, we can start coding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getBlog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;database_id&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;notion_database_id&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;published&lt;/span&gt; &lt;span class="o"&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;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;blogPost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blogPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Published&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkbox&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;published&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is everything we need to fetch all our articles and filter the articles out that are not published yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get de content of an article
&lt;/h3&gt;

&lt;p&gt;Because we want to be able to find an article, based on the custom url. We need to fetch all the articles and it’s properties first.&lt;/p&gt;

&lt;p&gt;When we have all the posts, we can look for the one that matches with the current url.&lt;/p&gt;

&lt;p&gt;Now we can use the id of this article to fetch the content of a page. Note that there is a maximum of 100 blocks. This is a limitation set by the Notion API.&lt;/p&gt;

&lt;p&gt;You’ll see that this is not the most performant/ideal solution imaginable, but given the requirements and technical limitations, it’s the best we can do.&lt;/p&gt;

&lt;p&gt;For us, this was not that big of an issue as we can use “&lt;a href="https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration" rel="noopener noreferrer"&gt;Incremental Static Regeneration&lt;/a&gt;” from Next.js. Next will cache the response and will serve our blog within a blink of an eye. (Learn more how we implemented Incremental Static Regeneration in &lt;a href="https://github.com/TijsM/rodi-landing-page/pull/25" rel="noopener noreferrer"&gt;this pull request&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allPosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getBlog&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;blogId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rich_text&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="nx"&gt;plain_text&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;
  &lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;id&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;page_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blogId&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;plain_text&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;intro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Intro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rich_text&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="nx"&gt;plain_text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;block_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blogId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;page_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&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="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;intro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&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;results&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;h3&gt;
  
  
  Display the content
&lt;/h3&gt;

&lt;p&gt;A Notion page consists out of lists of “blocks”, every block has a “type” that indicates if it’s normal text or if it’s a different type of component.&lt;/p&gt;

&lt;p&gt;We can loop over all these blocks and render the appropriate React component.&lt;/p&gt;

&lt;p&gt;If there is a type that’s not supported, nothing will be rendered.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blogContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paragraph&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Paragraph&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paragraph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;A&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heading_1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;H2&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heading_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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="nx"&gt;plain_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;H2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heading_2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;H3&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heading_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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="nx"&gt;plain_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;H3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bulleted_list_item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ListItem&lt;/span&gt; &lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ImageContainer&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StyledImage&lt;/span&gt;
              &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"fill"&lt;/span&gt;
              &lt;span class="na"&gt;objectFit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"contain"&lt;/span&gt;
            &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ImageContainer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CodeBlock&lt;/span&gt;
            &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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="nx"&gt;plain_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;We set ourselves the following requirements&lt;/p&gt;

&lt;p&gt;We can decide what content is and isn’t shown using the checkboxes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Use an intuitive editor to write content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notion is my favourite tool to write because of it’s ease of use.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Publishing and un-publishing an article, should not require any changes to the code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Publishing and un-publishing is done by checking a checkbox in the Notion database, it’s a breeze.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Support markup and richt text formatting (titles, lists, links, code-blocks ....)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For now titles, lists, links and code blocks are supported, if in the future this is not enough, We can easily add support for other components such as quotes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ SEO-friendly url’s&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can fully customise the url’s to strategically use important keywords to further improve our SEO.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Managing what articles are shown
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01zpvu89n04bo0qlno27.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01zpvu89n04bo0qlno27.gif" alt="demo on how to manage articles" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Managing the article
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8l9deuy05tdkw9z4mil.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8l9deuy05tdkw9z4mil.gif" alt="demo on how to manage the content of an article" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>nextjs</category>
      <category>react</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Does my business need a PWA or a native app?</title>
      <dc:creator>Tijs Martens</dc:creator>
      <pubDate>Fri, 23 Jul 2021 13:02:14 +0000</pubDate>
      <link>https://forem.com/bothrs/does-my-business-need-a-pwa-or-a-native-app-4h8c</link>
      <guid>https://forem.com/bothrs/does-my-business-need-a-pwa-or-a-native-app-4h8c</guid>
      <description>&lt;p&gt;A lot of companies or organizations might come up with an idea that they think has to be materialized as a native application. Well, I'm here to challenge that. While you should totally go after your crazy idea, a native application might not always be the right solution. &lt;/p&gt;

&lt;h1&gt;
  
  
  PWAs
&lt;/h1&gt;

&lt;p&gt;Let me introduce PWAs, otherwise known as progressive web applications. A PWA is a website or web application that uses some modern web technologies. These technologies enable web applications to implement some features which were only available to native mobile applications. They combine some of the best features of mobile applications and web technologies. &lt;/p&gt;

&lt;h1&gt;
  
  
  Which tool will guide you to success?
&lt;/h1&gt;

&lt;p&gt;Still not sure if you need a PWA or a native app for your next big thing? &lt;/p&gt;

&lt;h1&gt;
  
  
  TLDR;
&lt;/h1&gt;

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

&lt;h1&gt;
  
  
  Why you should go for a PWA and harness the power of websites
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Discoverability, &amp;amp; Shareability
&lt;/h2&gt;

&lt;p&gt;One of the things we take for granted on the web are hyperlinks. Every web page has an inherent link, making them about as shareable as they come.&lt;/p&gt;

&lt;p&gt;The web heavily depends on search engines. By applying some basic SEO (Search Engine Optimization) principles, people who have never heard of your site might land on it. &lt;/p&gt;

&lt;p&gt;This makes PWAs the best option for ecommerce and content-heavy platforms because people will often search for a product or piece of content and not for your site or app specifically. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=1QILz1lAzWY&amp;amp;t=15s" rel="noopener noreferrer"&gt;Data from Google&lt;/a&gt; shows that on average a user spends a lot more time on mobile apps than in browsers. And the biggest chunk of "app-time" is consumed by only a few apps. Unfortunately, it's really hard to play with the big boys at this time. &lt;/p&gt;

&lt;p&gt;On the other hand, an average Android user visits over &lt;a href="https://jmango360.com/mobile-app-vs-mobile-website-statistics/" rel="noopener noreferrer"&gt;100 different websites&lt;/a&gt; during a month. Long story short, the chance that your idea will be discovered in an organic way is much larger on the web than with native apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multichannel
&lt;/h2&gt;

&lt;p&gt;In traditional native app development you have to develop your app for every operating system. Luckily some progress has been made over the last years and apps for similar types of devices (phones, desktops) can be created from a single codebase. &lt;/p&gt;

&lt;p&gt;But as of today, there is no real solution that makes it possible to create a native app that will work flawlessly on all types of devices from one codebase.&lt;/p&gt;

&lt;p&gt;If you need a platform that needs to be accessed from multipele types of devices (iOS, Android, macOS, Windows, ... or any other device with a screen and internet connection), PWA's are definitely the way to go. &lt;/p&gt;

&lt;p&gt;A PWA is "just a website" with some added features. But this website should be able to run on all types of devices. From the latest and greatest foldable device to your old flip-phone with a basic browser.&lt;/p&gt;

&lt;p&gt;When your product is to be used on both desktop and mobile, a PWA might be the ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed of development &amp;amp; validation
&lt;/h2&gt;

&lt;p&gt;It seems that there are an infinite number of tools that can optimize the workflow of web developers. This often results in faster development on web than on mobile. &lt;/p&gt;

&lt;p&gt;One of the things I love when I come back to web development is how easy it is to share new features. Deploy separate from the core website and just create a link, I.E. &lt;a href="https://dev.yourdomain.new-feature.com" rel="noopener noreferrer"&gt;https://dev.yourdomain.new-feature.com&lt;/a&gt;. You can share this link with your team or client, and they can test the entire PWA with the new feature via this link. &lt;/p&gt;

&lt;p&gt;If everything looks good and everybody is satisfied, you can merge the new feature, and it will be online in literal seconds. When the speed of development, and rapid, repeated user testing is of the essence, you might want to give PWAs a second look.&lt;/p&gt;

&lt;h2&gt;
  
  
  App stores
&lt;/h2&gt;

&lt;p&gt;When creating a web project and you feel it's ready to launch, the only thing that's left to do is press the publish button and you're live. This is unfortunately not the case for native apps, as the only way of distributing your app is via the numerous app stores. While you can definitely benefit from the reach these platforms have, they have some  downsides to them as well:&lt;/p&gt;

&lt;h3&gt;
  
  
  Time delay
&lt;/h3&gt;

&lt;p&gt;You need to create developer accounts for Apple and Google. These are not free and need to be approved in some cases; a process that'll take a couple of days minimum.&lt;/p&gt;

&lt;p&gt;Once you have your account, you will have to create an app store listing: this includes a comprehensive summary of the app and visuals for all types of devices. &lt;/p&gt;

&lt;p&gt;After submitting, Apple and Google will take a few days to validate the listing and manually test your app to check if you don't violate their terms.&lt;/p&gt;

&lt;p&gt;If everything goes well, this will cost you about one week. If Apple has some feedback and doesn't approve your app straight away, this process will start over, and you will lose more time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apple and Google guidelines
&lt;/h3&gt;

&lt;p&gt;In some use cases, your app will never be approved or will be taken down when the store operators change their mind. &lt;/p&gt;

&lt;p&gt;Some people have created apps to inform people about the current state of the pandemic. They went through the entire process and saw their app get approved. A couple of days later they received a notification from Apple and Google with the message that any and all apps that have something to do with the pandemic will be taken down. There was nothing these developers could do to get their app back online again. In that sense, you're subjected to the whims of the app stores.&lt;/p&gt;

&lt;h3&gt;
  
  
  30% rule
&lt;/h3&gt;

&lt;p&gt;Apple takes 30% off of every sale you make on the app store. Furthermore, if your app just so happen to have "in-app purchases", Apple will also take 30% of that revenue. Because of this, &lt;a href="https://www.nytimes.com/2020/08/14/technology/apple-app-store-epic-games-fortnite.html" rel="noopener noreferrer"&gt;Spotify&lt;/a&gt; and &lt;a href="https://appleinsider.com/articles/20/08/23/apple-versus-epic-games-fortnite-app-store-saga----the-story-so-far" rel="noopener noreferrer"&gt;Epic Games&lt;/a&gt; have had multiple lawsuits with Apple. Google has also caught similar backlash over the same practice, hence they've recently announced to drop the service fee to 15%. Definitely something to keep in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updates
&lt;/h3&gt;

&lt;p&gt;Every time you want to update your app, the stores have to review said update. When it gets approved, it will become available in the stores for the user to download, obviously. But that means you have to rely on your users to keep your app updated. So if your first launch on the app store is riddled with bugs, things could get painful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Low entry barrier
&lt;/h2&gt;

&lt;p&gt;I like PWAs a lot because of their almost non-existent barrier to entry. There's virtually no installation process. Just click a link and you're good to go.&lt;/p&gt;

&lt;p&gt;If you want to test a feature from an app you need to download the entire app first(I know, this sounds obvious). but this is a big barrier. &lt;a href="https://www.apptweak.com/en/aso-blog/average-app-conversion-rate-per-category-2019" rel="noopener noreferrer"&gt;In 2020 the average conversion rate in the iOS app store was 34% and on the Google Play Store it was only 26%&lt;/a&gt;. These conversion rates highly depend on the app category, naturally. But the fact remains that app stores themselves can create friction for your users. &lt;/p&gt;

&lt;p&gt;Installing a native app also takes up precious space on your phone. Many people with cheaper or older phones struggle with this storage restriction. That's another win for PWAs; even if your phone has barely any space left, you can still access them no problem.&lt;/p&gt;

&lt;h1&gt;
  
  
  Where PWAs fall short and native apps take over.
&lt;/h1&gt;

&lt;h2&gt;
  
  
  User experience
&lt;/h2&gt;

&lt;p&gt;While you can create great experiences on the web, native apps just feel "snappier" in comparison. Animations and gestures are often smoother and just work better with native apps.&lt;/p&gt;

&lt;p&gt;For developers, native apps are better suited if pixel perfect design is what you're after. It's really cool that a PWA can be used on almost every device, from a smart fridge to a smart watch. But that also means that your user interface has to be responsive beyond what's practical.&lt;/p&gt;

&lt;p&gt;Native apps also make use of device specific components. A toggle switch is a great example of this. This component is the same in all the apps the user has, he knows exactly how it looks and how it works. &lt;/p&gt;

&lt;h2&gt;
  
  
  (Re)-engagement
&lt;/h2&gt;

&lt;p&gt;Once a native app is installed on a device, you have more options to nudge your users.&lt;/p&gt;

&lt;p&gt;There always is an app icon that reminds the user your app exists. You can also use badges. You know, the red dot in the upper right corner of the icon, signalling you've got a notification or something.&lt;/p&gt;

&lt;p&gt;Another powerful tool to re-engage users are push notifications. With targeted and personalized push notifications you can get the users back to your app in no time.&lt;/p&gt;

&lt;p&gt;The features mentioned above are also available on Android for PWA's. But they don't exist on iOS. If your project could benefit from these re-engagement features, a native application might be a better approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Device features
&lt;/h2&gt;

&lt;p&gt;Native applications have much better access to system and device features of your phone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Battery info&lt;/li&gt;
&lt;li&gt;Brightness info&lt;/li&gt;
&lt;li&gt;Access to calendar&lt;/li&gt;
&lt;li&gt;Access to contacts&lt;/li&gt;
&lt;li&gt;Access to files&lt;/li&gt;
&lt;li&gt;Access to sensors&lt;/li&gt;
&lt;li&gt;Access to your camera&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these features are available to modern Android devices for PWAs, but saying they're reliable would be a stretch&lt;/p&gt;

&lt;p&gt;With a native app, you also have more control over the camera, and the performance of the GPS sensor is often times a bit better.&lt;/p&gt;

&lt;p&gt;If your project requires one of the above features, a PWA is often not an option, making a native application the only way to go.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Be it due to some preconceived notion, but many people think native applications are the correct choice regardless. But as we discussed earlier, for some use cases it makes much more sense to implement your idea as a PWA. Some benefits such as shareability and speed can definitely give you an edge. Then again, if providing a stellar user experience and having re-engagement tactics at your disposal is more your jam, a native application might be more suited for the job.&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>reactnative</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why you should create a PWA for your side-project</title>
      <dc:creator>Tijs Martens</dc:creator>
      <pubDate>Mon, 24 May 2021 09:29:33 +0000</pubDate>
      <link>https://forem.com/martenstijs/why-you-should-create-a-pwa-for-your-side-project-21f9</link>
      <guid>https://forem.com/martenstijs/why-you-should-create-a-pwa-for-your-side-project-21f9</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;We, (web-)developers, often have some small ideas for fun projects. Most of these ideas are not startup worthy but are just for the sake of learning something new or just having fun. One of the advantages of creating these is that you're building a strong portfolio with a "I am a creator" vibe.&lt;/p&gt;

&lt;p&gt;Well, with a little more effort, you can upgrade these projects to look a lot more professional when sharing them on your socials.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why do we create these side projects
&lt;/h1&gt;

&lt;p&gt;I really like to create small and fun projects, not everything we make needs to have 1000 daily users to be developed. Sometimes we just have a little idea or something that's bothering us and by creating a small tool, we can solve that. &lt;/p&gt;

&lt;p&gt;Another reason why I like to make small projects is to explore new technologies. It's hard to try something new in production software. &lt;/p&gt;

&lt;p&gt;Creating side projects and proof-of-concepts are also a great way to develop a personal brand. By having small, non significant, projects on your portfolio you show that you have a hand's-on mentality. Which I believe a lot of companies want.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is a PWA
&lt;/h1&gt;

&lt;p&gt;You could write an entire thesis on what a PWA is and isn't. (whoops, maybe I did exactly that 😅). But in it's essence a PWA is just a regular website which makes use of modern web technologies to provide a user experience which is close to that of a native application. &lt;/p&gt;

&lt;p&gt;It's an attempt to bridge the gap between &lt;a href="https://www.bothrs.com/service/apps" rel="noopener noreferrer"&gt;native applications&lt;/a&gt; and &lt;a href="https://www.bothrs.com/service/pwa" rel="noopener noreferrer"&gt;web applications&lt;/a&gt;. It's taking the strongest points from both worlds and combines them. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have the reach of the web. People can find your project from a search engine and it has an easy to access URL.&lt;/li&gt;
&lt;li&gt;You have similar capabilities as a native application. A PWA can still provide some functionality when offline and it can also re-engage users with push-notification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many people would consider a PWA as a website which can be installed on a device.  The lighthouse audit which is embedded in the Chrome dev-tools confirms this. So how do you make a regular website installable?&lt;/p&gt;

&lt;h1&gt;
  
  
  How to make a PWA installable.
&lt;/h1&gt;

&lt;p&gt;Your website has to fullfill these 3 requirements&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register a service worker&lt;/li&gt;
&lt;li&gt;Register a manifest.json&lt;/li&gt;
&lt;li&gt;Be served over HTTPS&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Service worker
&lt;/h2&gt;

&lt;p&gt;According to the lighthouse audit, a PWA should register a service worker which has the "start_url" in it's scope. There is no requirement based on what the functionality of the service worker should be. To make the site installable, there should just be a service worker, it's totally fine if this web worker has no functionality. &lt;/p&gt;

&lt;p&gt;So it's fine if it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nb"&gt;self&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="s2"&gt;install&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;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;service worker installed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nb"&gt;self&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="s2"&gt;activate&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;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;service worker activated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This service worker should be registered in the startup file of your project. For me (in Svelte.js) that's&lt;br&gt;
&lt;/p&gt;

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





```jsx
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! Now your website registers a service worker. &lt;/p&gt;

&lt;p&gt;You can do al lot of cool things to improve the performance of  your web app with a service worker. But that's outside the scope of this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Manifest.json
&lt;/h2&gt;

&lt;p&gt;The manifest.json file is a file which provides some extra metadata about the website to the device. There are a lot of tools which can help you with generating the correct assets and the manifest file itself&lt;/p&gt;

&lt;p&gt;I like to use this one: &lt;a href="https://www.dunplab.it/web-app-manifest-generator" rel="noopener noreferrer"&gt;https://www.dunplab.it/web-app-manifest-generator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According the the MDN docs, a manifest should at least include the following properties&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"Spotify album browser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"short_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;"Spotify album browser"&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;"/"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A progressive web application that suggests albums based on the preferences and listening history of the user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#191414"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"theme_color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#1db954"&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;"icons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"images/icons/apple-icon-152x152-dunplab-manifest-4531.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image/png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sizes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"152x152"&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you should link to this file in the head-tag of your website:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"manifest"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/manifest.json"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. HTTPS
&lt;/h2&gt;

&lt;p&gt;This one might be the easiest. It's 2020, serving a website without HTTPS is just not acceptable anymore. There are many platforms where you can host your side projects for free with https (Vercel, Netlify, ...). I like to use Vercel for it CI/CD features, but most likely other platforms can do the same.&lt;/p&gt;

&lt;p&gt;So that's it! that's all! That wasn't to bad huh? For this effort your website, or should I call it PWA? is installable as a standalone experience on al modern devices. Most browsers (not Safari) will even prompt the user to install it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why should I create a PWA for my side project.
&lt;/h1&gt;

&lt;p&gt;There are many reasons why you should add these 3 simple steps to your next side project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Engagement: A PWA can be accessed from a URL just like a regular website. There is a really low barrier to test your little prototype/ tool. If you would've created a native app, this barrier is way bigger.&lt;/li&gt;
&lt;li&gt;It looks hella good on screenshots/ screencasts. The little project you will make will look and feel like a native app. Without having to deal with the hassle of actually creating a native app. When you share your app in a video or just as screenshots, they look a lot better/ more professional than when you share screenshots of a web-browser.&lt;/li&gt;
&lt;li&gt;Showing excellence: Progressive web applications are kinda new (debatable) in the world of web-development. Showing that you know what it is and know how to implement is, shows that you are actively following the latest trends within web development. This could be a crucial factor when you're trying to land your next job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  An Example
&lt;/h1&gt;

&lt;p&gt;A couple weeks ago I struggled with finding new music on Spotify. For some reason, the Spotify recommendation engine kept on suggesting the same albums over and over again. Coincidentally at that time I wanted to try out Svelte.&lt;/p&gt;

&lt;p&gt;I did some research and I found out that Spotify has a great API which is really easy to use. &lt;/p&gt;

&lt;p&gt;So I had a plan. Create a good looking, personalized, album suggesting PWA.&lt;/p&gt;

&lt;p&gt;After two weekends of developing and getting to know Svelte I had my PWA up and running. I recorded a screencast and shared it on my LinkedIn. It didn't explode or went viral. But yet, more people then expected gave it a try and I received a lot of feedback. One week later, I shared my project on reddit.&lt;/p&gt;

&lt;p&gt;In both cases, I didn't have that many likes or upvotes. But a lot of the people that saw my post actually gave it a try. Which is in the end the goal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6m9mndu5u5m4m54r6jw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6m9mndu5u5m4m54r6jw.png" alt="first peek = LinkedIn post - second peek = LinkedIn post shared - third peek = shared on Reddit" width="800" height="119"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;You can give this little project a try at &lt;a href="https://album-browser.vercel.app/" rel="noopener noreferrer"&gt;https://album-browser.vercel.app/&lt;/a&gt;. &lt;/p&gt;

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

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6zfnsixohnkenzec32m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6zfnsixohnkenzec32m.png" alt="Landing screen on desktop" width="800" height="488"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1yhb0wcz48824oogytxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1yhb0wcz48824oogytxu.png" alt="Login screen on desktop" width="800" height="488"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0c2ga21qizc9huiefp1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0c2ga21qizc9huiefp1.png" alt="App on desktop" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>svelte</category>
      <category>spotify</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
