<?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: Jan </title>
    <description>The latest articles on DEV Community by Jan  (@friebe).</description>
    <link>https://dev.to/friebe</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%2F122474%2Ff4a3a011-3722-4884-8b4b-a0de0f008312.png</url>
      <title>DEV Community: Jan </title>
      <link>https://dev.to/friebe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/friebe"/>
    <language>en</language>
    <item>
      <title>Why I Built Yet Another Grocery List App (And Why I'm Not Sorry)</title>
      <dc:creator>Jan </dc:creator>
      <pubDate>Sat, 26 Apr 2025 18:42:29 +0000</pubDate>
      <link>https://dev.to/friebe/why-i-built-yet-another-grocery-list-app-and-why-im-not-sorry-129h</link>
      <guid>https://dev.to/friebe/why-i-built-yet-another-grocery-list-app-and-why-im-not-sorry-129h</guid>
      <description>&lt;p&gt;&lt;strong&gt;Let’s be honest:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
There are already 500 grocery list apps out there.&lt;br&gt;&lt;br&gt;
And yet — I still built &lt;strong&gt;LoopList&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Why? Because every week, I found myself typing out the same boring list and going slowly insane.&lt;br&gt;
I'm a structured person.&lt;br&gt;&lt;br&gt;
I love organization — but I &lt;strong&gt;hate shopping&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
And I absolutely refuse to keep typing "milk," "eggs," "oats" every single week.&lt;br&gt;
So: &lt;strong&gt;I built my own app.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Problem solved.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes LoopList Different
&lt;/h2&gt;

&lt;p&gt;LoopList isn't just another “type it in and check it off” app.&lt;br&gt;
&lt;strong&gt;LoopList actually thinks with you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Personal Product Archive&lt;/strong&gt;
Everything you buy is saved. Next week, just reactivate items instead of retyping them.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pantry Stock Management&lt;/strong&gt;
You can track how much you have left (e.g., eggs medium in stock). When you're running low, LoopList suggests restocking automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart Intervals&lt;/strong&gt;
Set how long your supplies usually last (e.g., "eggs last 7 days").
BUT: LoopList adjusts those intervals dynamically if it notices you're running out sooner.
(Tiny AI, big impact.)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimalistic and Efficient&lt;/strong&gt;
No ads. No bloated features. Just you, your pantry, and your smarter shopping loop.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Who LoopList Is For
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;People who buy &lt;strong&gt;pretty much the same things&lt;/strong&gt; every week.&lt;/li&gt;
&lt;li&gt;Structured folks who want to &lt;strong&gt;shop efficiently&lt;/strong&gt; without retyping.&lt;/li&gt;
&lt;li&gt;Anyone who wants to &lt;strong&gt;manage pantry stock smartly&lt;/strong&gt;, not just manage lists.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why I’m Asking for Feedback
&lt;/h2&gt;

&lt;p&gt;I know LoopList won’t change the world. &lt;/p&gt;

&lt;p&gt;But it &lt;strong&gt;solved a real problem for me&lt;/strong&gt;. And maybe it can help you too.&lt;/p&gt;

&lt;p&gt;LoopList was born out of pure real-life frustration — not some startup pitch deck.&lt;/p&gt;

&lt;p&gt;You can try it here: &lt;a href="https://looplist.netlify.app/" rel="noopener noreferrer"&gt;https://looplist.netlify.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm genuinely looking for honest feedback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What works?&lt;/li&gt;
&lt;li&gt;What’s missing?&lt;/li&gt;
&lt;li&gt;What would you change?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Thanks for checking it out — and maybe for giving it a shot!&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Jan&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>sideprojects</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Using Astro Image Optimization Benefits with Tina CMS Cloud in Production build</title>
      <dc:creator>Jan </dc:creator>
      <pubDate>Sun, 16 Jun 2024 17:15:30 +0000</pubDate>
      <link>https://dev.to/friebe/using-astro-image-optimization-benefits-with-tina-cms-cloud-in-production-build-5a01</link>
      <guid>https://dev.to/friebe/using-astro-image-optimization-benefits-with-tina-cms-cloud-in-production-build-5a01</guid>
      <description>&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;In production, the benefits of image optimization using Astro's Image/Picture component are lost when integrating with Tina CMS and Tina Cloud. However, I have found a workaround to resolve this issue. Whether this workaround is the best or a good fit for client websites remains uncertain, but here are the details. For further context, you can read the github discussion &lt;a href="https://github.com/tinacms/tinacms/discussions/4035#discussioncomment-6264333" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;Typically, you have a markdown file with an image path pointing to a locally based image, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;imgSrc: /src/assets/img/logo.png
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To leverage the image optimization benefits of the Astro Image component, such as generating multiple versions of an image based on device density, you would use dynamic image imports as described &lt;a href="https://docs.astro.build/en/recipes/dynamically-importing-images/" rel="noopener noreferrer"&gt;in astro docs&lt;/a&gt;. Everything works perfectly in development mode when you run npm run &lt;code&gt;"tinacms dev -c \"astro dev\"",&lt;/code&gt;. &lt;a href="https://tina.io/docs/cli-overview/" rel="noopener noreferrer"&gt;Tina CLI commands&lt;/a&gt; But for clarity here an example of importing images dynamically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ImageMetadata&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;astro&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;Image&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;astro:assets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;imgSrc&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="p"&gt;}&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;imgSrc&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&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;images&lt;/span&gt; &lt;span class="o"&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="nx"&gt;ImageMetadata&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/src/assets/img/*.{jpeg,jpg,png,gif}&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;imgSrc&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&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="nx"&gt;imgSrc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" does not exist in glob: "src/assets/img/*.{jpeg,jpg,png,gif}"`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;
  &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;70&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;70&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;imgSrc&lt;/span&gt;&lt;span class="p"&gt;]()}&lt;/span&gt;
  &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;densities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
  &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lazy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Production Issue
&lt;/h2&gt;

&lt;p&gt;In production, Tina CMS typically retrieves its media from its own CDN server (e.g., &lt;a href="https://assets.tina.io/image" rel="noopener noreferrer"&gt;https://assets.tina.io/image&lt;/a&gt;). This setup conflicts with Astro's automatic image optimization and the associated glob pattern.&lt;/p&gt;

&lt;p&gt;The function tries to search for the image in the local path. This works in development mode, but in production, the &lt;code&gt;imgSrc&lt;/code&gt; returned from the markdown file is no longer a local string. Instead, it returns a URL like &lt;a href="https://assets.tina.io/image" rel="noopener noreferrer"&gt;https://assets.tina.io/image&lt;/a&gt;, causing the search function aka glob function to fail and resulting in a build error.&lt;/p&gt;

&lt;p&gt;For more information, refer to the github discussion &lt;a href="https://github.com/tinacms/tinacms/discussions/4035#discussioncomment-6264333" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workaround for Using Image Optimization
&lt;/h2&gt;

&lt;p&gt;To still leverage Astro's image optimization in production, you need to adapt the build script:&lt;/p&gt;

&lt;h2&gt;
  
  
  Modified Build Script
&lt;/h2&gt;

&lt;p&gt;The build script must be updated to:&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="nl"&gt;"scripts"&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="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tinacms build --local -c &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;astro build&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;However, be aware that dynamically fetching data will no longer work as expected. For example, querying dynamic data using Tina's client in for example a Date component will not working properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example script of my dynamically data fetches
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatMarkdownUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formatDateString&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../utils/format&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nb"&gt;document&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;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&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;client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../tina/__generated__/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getLiveDatesResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;try&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;live_dateConnection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                                &lt;span class="na"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;date&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;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This workaround allows you to leverage Astro's image optimization benefits in production while using Tina CMS and Tina Cloud. However, it introduces limitations in dynamically fetching data, which may not be suitable for all use cases. Evaluate whether this approach fits your project's requirements and constraints.&lt;/p&gt;

&lt;p&gt;For further reading and to join the discussion, check out the GitHub discussion.&lt;/p&gt;

</description>
      <category>astro</category>
      <category>javascript</category>
      <category>jamstack</category>
    </item>
  </channel>
</rss>
