<?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: Anton Reindl</title>
    <description>The latest articles on DEV Community by Anton Reindl (@areindl).</description>
    <link>https://dev.to/areindl</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%2F390912%2F10622552-1ae0-486d-858e-a5d38a063c3a.jpeg</url>
      <title>DEV Community: Anton Reindl</title>
      <link>https://dev.to/areindl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/areindl"/>
    <language>en</language>
    <item>
      <title>Quick Tip: Nuxt &amp; Storyblok Error Handling (e.g. 404)</title>
      <dc:creator>Anton Reindl</dc:creator>
      <pubDate>Sun, 21 Apr 2024 07:38:48 +0000</pubDate>
      <link>https://dev.to/areindl/quick-tip-nuxt-storyblok-error-handling-eg-404-45fh</link>
      <guid>https://dev.to/areindl/quick-tip-nuxt-storyblok-error-handling-eg-404-45fh</guid>
      <description>&lt;p&gt;Welcome to my new Quick Tip series:&lt;/p&gt;

&lt;p&gt;Today we are looking at Error Handling when building websites with &lt;a href="https://nuxt.com/" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt; and &lt;a href="https://www.storyblok.com" rel="noopener noreferrer"&gt;Storyblok&lt;/a&gt; as CMS. If you haven't tried the two tools, go check out one of the awesome &lt;a href="https://www.storyblok.com/tp/add-a-headless-CMS-to-nuxt-3-in-5-minutes" rel="noopener noreferrer"&gt;tutorials&lt;/a&gt;. It's a perfect match for all your projects. &lt;/p&gt;

&lt;p&gt;Today we are looking into one specific use case:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build a blog post detail page with a dynamic slug in the URL param&lt;/li&gt;
&lt;li&gt;Fetch the content from Storyblok CMS&lt;/li&gt;
&lt;li&gt;If the content cannot be found (Storyblok returns 404), Nuxt should show an error Page&lt;/li&gt;
&lt;li&gt;The solution must work with Server-Side-Rendering and Server-Side-Generation&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;YourArticleDetailPage&lt;/span&gt; &lt;span class="na"&gt;:story=&lt;/span&gt;&lt;span class="s"&gt;"story"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Get the Slug from the Route&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRoute&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;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;

&lt;span class="c1"&gt;// Load the Content from /blog&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;story&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;useAsyncStoryblok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;// Handle the Error&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;story&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nf"&gt;createError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Page Not Found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;fatal&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="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&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;&lt;em&gt;Noteworthy:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The most important part of the solution is &lt;code&gt;fatal: true&lt;/code&gt; in the error object. This will trigger a full-screen error page in Nuxt and redirect the user to the error page.&lt;/p&gt;

&lt;p&gt;Now you can go ahead and create the error page with &lt;code&gt;error.vue&lt;/code&gt; - as described &lt;a href="https://nuxt.com/docs/guide/directory-structure/error" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Remember: the &lt;code&gt;error.vue&lt;/code&gt; must be kept in root, not &lt;code&gt;/pages&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's it for today's quick tip. &lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>storyblok</category>
      <category>errors</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Retry and Recursion in asynchronous Vuex Actions - a naïve approach to Firebase Image Resizing</title>
      <dc:creator>Anton Reindl</dc:creator>
      <pubDate>Thu, 01 Jul 2021 08:58:34 +0000</pubDate>
      <link>https://dev.to/areindl/retry-and-recursion-in-asynchronous-vuex-actions-a-naive-approach-to-firebase-image-resizing-400p</link>
      <guid>https://dev.to/areindl/retry-and-recursion-in-asynchronous-vuex-actions-a-naive-approach-to-firebase-image-resizing-400p</guid>
      <description>&lt;p&gt;In a web application we have recently built using Vue.js and Firebase, we needed an image uploader that enables the user to upload images (e.g. a profile picture). After the upload the image should be scaled down and resized to a set of predefined sizes. For that, we leverage the very neat Firebase extension &lt;em&gt;&lt;a href="https://firebase.google.com/products/extensions/storage-resize-images" rel="noopener noreferrer"&gt;Storage Image Resize&lt;/a&gt;&lt;/em&gt;. This extension is easy to configure and use. It gets the job done: every time a new image is uploaded to Firebase Storage, it starts the resizing and adds the scaled images to the same folder.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: This article assumes prior knowledge in Vue, Vuex and Firebase and will not cover those basics. It's not a tutorial but instead conceptualizes a software engineering pattern.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;em&gt;Update from July 2022: Resized Event available&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Finally, Firebase added an event to know when the resizing is done. &lt;a href="https://github.com/firebase/extensions/blob/master/storage-resize-images/CHANGELOG.md#version-0128" rel="noopener noreferrer"&gt;See Changelog&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;But in the simplicity of the server-side resizing there also lie problems: The frontend application neither knows if or when the backend process has started nor completed. It is totally independent. The only way to know if the process has been completed successfully is to check if the scaled files are present on the Storage.&lt;/p&gt;

&lt;p&gt;The resize extension is a cloud function and based on our tests we experienced execution times between 200ms and 2s (cold-start). Depending on the uploaded image size, the process can also take longer. In our configuration the function needs to create 4 predefined image sizes. Therefore there could be cases where the resized image is not yet ready to be used in the frontend application if called in a sequential way. So how can this potential unknown delay in process be solved?&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;First of all: there are different solutions to this problem. We sought for a solution that has (1) a high cohesion with the uploader component itself and does not require additional backend jobs and (2) can fail nicely with a fallback solution. &lt;/p&gt;

&lt;p&gt;Here is a schematic depiction of the process:&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%2Fdin3r7hctifhsb9jfcdi.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%2Fdin3r7hctifhsb9jfcdi.png" alt="image" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our real application we implement the following workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User selects file [Uploader Component]&lt;/li&gt;
&lt;li&gt;Upload action uploads file to Firebase Store [Vuex Store Action]&lt;/li&gt;
&lt;li&gt;Upload action returns the DownloadUrl for the storage object [Vuex Store Action]&lt;/li&gt;
&lt;li&gt;In parallel, on server side, the resize cloud function starts to process the images triggered automatically by the upload[Uploader Component]&lt;/li&gt;
&lt;li&gt;User is informed in the UI that the resizing starts [Uploader Component]&lt;/li&gt;
&lt;li&gt;The Uploader Component triggers the function to get the resized image url (&lt;code&gt;tryGettingResizedDownloadUrl()&lt;/code&gt;) [Vuex Store Action]&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;tryGettingResizedDownloadUrl()&lt;/code&gt; function will try to create a DownloadUrl. &lt;strong&gt;If it fails, it will wait 800ms and then retries again by calling itself recursively&lt;/strong&gt;. As soon as it finds a downloadUrl for that image, this Url is returned. It finds a download url by repeatedly checking if the file exists on the storage filesystem which matches the defined filename pattern (&lt;code&gt;filename_${width}x${height}.{fileExtension}&lt;/code&gt;). After x-retries it stops and returns &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the &lt;code&gt;tryGettingResizedDownloadUrl()&lt;/code&gt;-function does not deliver a result and returns &lt;code&gt;null&lt;/code&gt;, the full-size image is stored in the database as a fallback.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why a naïve approach?
&lt;/h3&gt;

&lt;p&gt;A naïve implementation is an implementation that has taken shortcuts for the sake of simplicity or by lack of knowledge (e.g. when the backend completed). It will not account for all the possible uses cases or try to fit in every situation. In potentially 99% of the cases, this naïve wait-and-try-again will be successful. Of course there can be other failures, but these are edge cases. We optimize for the standard case and make sure to fail nicely if something unexpected happens. This makes sure that other parts of the application are not affected by this implementation. &lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;The Vuex Action in the &lt;code&gt;storage.js&lt;/code&gt; Vuex-Module.&lt;br&gt;
Here we have implemented the naïve wait-and-try-again approach:&lt;/p&gt;


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


&lt;p&gt;As you can see, the retry is implemented as a try-catch-block. In case of failure, the retry variable is increased, the action waits 800ms  (&lt;code&gt;await sleep(800)&lt;/code&gt;) and the recursively calls itself again (&lt;code&gt;return dispatch('tryGettingResizedDownloadUrl', { url, size, retry })&lt;/code&gt;). It is crucial to add the &lt;code&gt;return&lt;/code&gt; before the &lt;code&gt;dispatch()&lt;/code&gt;, otherwise the action won't return anything.&lt;/p&gt;

&lt;p&gt;In our frontend Vue Photo Upload component we interact with the Vuex action:&lt;/p&gt;


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


&lt;p&gt;As you can see, the &lt;code&gt;resizedUrl&lt;/code&gt; variable awaits the the results from the action and is either a &lt;code&gt;string&lt;/code&gt; containing the URL or &lt;code&gt;null&lt;/code&gt;. In case of the latter, &lt;code&gt;fullImageUrl&lt;/code&gt; is saved.&lt;/p&gt;

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

&lt;p&gt;For us, this naïve retry-approach is a very feasible option. Whenever the naïve approach fails we receive a ticket in Sentry - our  application monitoring platform. We acknowledge that there are more secure solutions. But retrying makes a lot of sense in our case and it makes sure the upload and resizing is cohesive and implemented in a step-by-step process.&lt;/p&gt;

&lt;p&gt;What do &lt;strong&gt;you&lt;/strong&gt; think about this solution? Let us know in the comments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Template
&lt;/h3&gt;

&lt;p&gt;Here is the template for a recursive retry function in Vuex:&lt;/p&gt;


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


</description>
      <category>firebase</category>
      <category>retry</category>
      <category>vuex</category>
      <category>recursion</category>
    </item>
    <item>
      <title>Software Architecture Documentation - The arc42 Notion Template</title>
      <dc:creator>Anton Reindl</dc:creator>
      <pubDate>Tue, 09 Mar 2021 10:13:26 +0000</pubDate>
      <link>https://dev.to/areindl/software-architecture-documentation-the-arc42-notion-template-4lo2</link>
      <guid>https://dev.to/areindl/software-architecture-documentation-the-arc42-notion-template-4lo2</guid>
      <description>&lt;p&gt;Our company uses &lt;a href="https://www.notion.so/product" rel="noopener noreferrer"&gt;Notion&lt;/a&gt; as our central workspace to organize work and document things. It's a great tool and we are really happy with it. The simple, fast and yet extremely powerful user-interface was the key advantage of Notion when we chose it over the very popular &lt;a href="https://www.atlassian.com/software/confluence" rel="noopener noreferrer"&gt;Confluence&lt;/a&gt; by Atlassian.&lt;/p&gt;

&lt;p&gt;So obviously it's also our go-to-tool for documenting the software architecture in our projects. When we document software architecture we use the very pleasant &lt;a href="https://arc42.org/download" rel="noopener noreferrer"&gt;arc42 template&lt;/a&gt;. It was created by Dr. Peter Hruschka &amp;amp; Dr. Gernot Starke and is available in multiple formats. The template is free to use. &lt;/p&gt;

&lt;p&gt;We prepared a Notion clone of the arc42 template to accelerate your work and make it easier to get started. &lt;br&gt;
Simply duplicate the page to your workspace:&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%2Fgheobxbq382vfdmmk4fl.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%2Fgheobxbq382vfdmmk4fl.png" alt="image" width="335" height="75"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://antonreindl.notion.site/Arc42-Notion-Template-96d4cfba68f54f60a9101991f96fd1b6?pvs=4" rel="noopener noreferrer"&gt;Simple Template&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://antonreindl.notion.site/Arc42-Notion-Template-with-help-text-3e76db1673414098b20554dffee7b8c8?pvs=4" rel="noopener noreferrer"&gt;Simple Template with Help Texts&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;UPDATE from October 2023&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our documentation was growing a lot. At some point, the single-page template was too cluttered and the loading times increased.&lt;/p&gt;

&lt;p&gt;There we added a new and more organized template with subpages and new database. We recommend you use this one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://antonreindl.notion.site/Arc42-Architecture-Documentation-Template-with-Subpages-b1f03844201a4d8d9998d3344eb9356e?pvs=4" rel="noopener noreferrer"&gt;Arc42 Template with Subpages&lt;/a&gt;&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%2Fi9343ntvbzsjd8ez25cb.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%2Fi9343ntvbzsjd8ez25cb.png" alt=" " width="800" height="946"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If you are new to arc42 I can really recommend the book &lt;em&gt;Arc42 by Example: Software Architecture Documentation in Practice&lt;/em&gt; by Gernot Starke und Stefan Zörner. It helped us a lot to get started with documenting.&lt;/p&gt;

&lt;p&gt;So here are our key take-aways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Be correct: name one responsible person who owns the architecture and carefully instruct others to provide chapters of the architecture documentation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make it maintainable: the architecture documentation should be the single source of truth and very easy to adapt. Notion has a built-in version control that allows clear revision of chapters. Use the discussion and commenting feature to discuss architectural decisions in the document.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stay accessible: first of all, only document the relevant things - avoid unnecessary stuff and focus on the really important parts of the architecture. When you write documentation avoid overly complex language. Me short and precise. It's documentation, not a novel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write iteratively: software architecture evolves over time. It's not something that can be done 100% upfront. It will grow together with your application. So make sure that it gets updated on a regular basis and is part of your (agile) development process.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am happy to hear your thoughts on documenting your architecture in the comments.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PS: Again thanks to Gernot Starke und Peter Hruschka for providing this great template for free.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>notion</category>
      <category>arc42</category>
      <category>architecture</category>
      <category>documentation</category>
    </item>
  </channel>
</rss>
