<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[svn]]></title><description><![CDATA[the ghost on the machine]]></description><link>https://svn.matthiashaak.com/</link><image><url>https://svn.matthiashaak.com/favicon.png</url><title>svn</title><link>https://svn.matthiashaak.com/</link></image><generator>Ghost 4.48</generator><lastBuildDate>Tue, 12 May 2026 09:58:18 GMT</lastBuildDate><atom:link href="https://svn.matthiashaak.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Load assets from your own domain with Storyblok and nginx]]></title><description><![CDATA[How to add a custom asset domain to a Storyblok website without additional cloud services, using a regular web server.]]></description><link>https://svn.matthiashaak.com/load-assets-from-your-own-domain-with-storyblok-and-nginx/</link><guid isPermaLink="false">680607480fe1a4054dd73a07</guid><category><![CDATA[dev]]></category><category><![CDATA[hosting]]></category><category><![CDATA[storyblok]]></category><category><![CDATA[headless]]></category><category><![CDATA[coding]]></category><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Mon, 21 Apr 2025 09:21:19 GMT</pubDate><media:content url="https://svn.matthiashaak.com/content/images/2025/04/gurkendoktor_a_close-up_photography_of_the_hand_of_a_runner_han_83e73a6a-40a8-4e5e-97f2-918e9fc42459.png" medium="image"/><content:encoded><![CDATA[<img src="https://svn.matthiashaak.com/content/images/2025/04/gurkendoktor_a_close-up_photography_of_the_hand_of_a_runner_han_83e73a6a-40a8-4e5e-97f2-918e9fc42459.png" alt="Load assets from your own domain with Storyblok and nginx"><p>Storyblok is an easy to understand and to work with CMS. It comes with a Digital Asset Management, and a powerful Image Optimisation API. For daily use on your website, there is only one small problem with this: the domain for the assets points to <code>a.storyblok.com</code>. Not everyone is happy with that, you might want to load assets from your own domain.</p><h2 id="why">Why</h2><p>There are a bunch of tutorials on how to set up your own asset domain with <a href="https://storyblok.com/tp/set-up-a-custom-assets-domain-using-amazon-cloudfront">Amazon CloudFront</a> or <a href="https://storyblok.com/tp/set-up-a-custom-assets-domain-using-microsoft-azure">Azure</a> or <a href="https://storyblok.com/tp/set-up-a-custom-assets-domain-using-google-cloud">Google Cloud</a> - but there is a much easier way. Especially when you don&#x2019;t want to add into Infrastructure or you are not a customer of any of those services this alternative can be quite interesting.</p><h2 id="the-setup">The setup</h2><p>Chances are, when you operate a website, you have a web server running<a href="#exp">*</a>. My personal preference is nginx, but that is to everyone for themselves to decide. As I know nginx better than the alternatives, I will give a quick example on how to achieve this.</p><p>The way you do it is that you set up a reverse proxy, as such:</p><!--kg-card-begin: html--><pre>
proxy_cache_path /var/www/asset_cache levels=1:2 keys_zone=asset-cache:500m inactive=60m;

server {
        listen   443 ssl http2;

        server_name assets.mydomain.tld;
        root /var/www/html;

        # adapt this to your SSL config
        ssl_certificate /etc/letsencrypt/assets.mydomain.tld/fullchain.cer;
        ssl_certificate_key /etc/letsencrypt/assets.mydomain.tld/assets.mydomain.tld.key;
        ssl_protocols TLSv1.3;

        location / {
                proxy_ssl_server_name on;
                proxy_cache asset-cache;
                proxy_pass https://a.storyblok.com;
                proxy_pass_request_headers on;
                add_header X-Cache-Status $upstream_cache_status;
        }
}
</pre><!--kg-card-end: html--><p>And that&#x2019;s it.</p><p>In your front end, add a helper to rewrite the asset URL such as this</p><!--kg-card-begin: html--><pre>
function transformImageSrc(src) {
   const url = new URL(src);
   if (url.hostname === &quot;a.storyblok.com&quot;) {
       url.hostname = &quot;assets.mydomain.tld&quot;;
   }
   return url.href;
}
</pre><!--kg-card-end: html--><p>(From the Storyblok Documentation)</p><p>Congratulations, you&#x2019;ve just set up your own asset domain &#x2013; but using this setup we can even dial it one bit further. What if we you want to load images from a folder like &#x201C;/assets/&#x201D;? The answer is: the setup needs to change only slightly:</p><!--kg-card-begin: html--><pre>proxy_cache_path /var/www/asset_cache levels=1:2 keys_zone=asset-cache:500m inactive=60m;

server {
        listen   443 ssl http2;

        server_name www.mydomain.tld;
        root /var/www/html;

        # all your normal webserver configuration

        location /assets {
                proxy_ssl_server_name on;
                proxy_cache asset-cache;
                proxy_pass https://a.storyblok.com;
                proxy_pass_request_headers on;
                add_header X-Cache-Status $upstream_cache_status;
        }
}
</pre><!--kg-card-end: html--><p>And in the frontend change your helper to</p><!--kg-card-begin: html--><pre>function transformImageSrc(src) {
   const url = new URL(src);
   if (url.hostname === &quot;a.storyblok.com&quot;) {
       url.hostname = &quot;www.mydomain.tld/assets&quot;;
   }
   return url.href;
}
</pre><!--kg-card-end: html--><p><strong>Tada</strong>.</p><p>You might want to play around a little with different parameters and settings, the documentation for nginx reverse proxy can be found here: <a href="https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/">https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/</a></p><h2 id="other-web-servers">Other web servers</h2><p>Other web servers also allow you to set up reverse proxies. I haven&apos;t tested it, so your mileage may vary.</p><ul><li>Apache: <a href="https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html">https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html</a></li><li>Caddy: <a href="https://caddyserver.com/docs/caddyfile/directives/reverse_proxy">https://caddyserver.com/docs/caddyfile/directives/reverse_proxy</a></li><li>Traefik: <a href="https://community.traefik.io/t/traefik-as-reverse-proxy-server-to-external-web-server/15619/2">https://community.traefik.io/t/traefik-as-reverse-proxy-server-to-external-web-server/15619/2</a></li></ul><p>If you have anything to add or correct - reach out on Mastodon (@gurkendoktor@mastodon.cloud) </p><!--kg-card-begin: html--><a name="exp"><!--kg-card-end: html--><hr><p>* If don&#x2019;t have one because you run your site on e.g Netlify, there might still be a way to set up a proxy. For Netlify, this is described here: <a href="https://docs.netlify.com/routing/redirects/rewrites-proxies/">https://docs.netlify.com/routing/redirects/rewrites-proxies/</a></p></a>]]></content:encoded></item><item><title><![CDATA[The magic that is nginx_reverse_proxy]]></title><description><![CDATA[<p>I cannot count anymore how many times the<a href="https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/"> reverse proxy function of nginx</a> has saved my life - or sanity - in course of the last year. </p><p>Like every life-saving, close-to-magic technology, it needs mastery. Which I do not command, thus having to spend endless nights fiddling around for the</p>]]></description><link>https://svn.matthiashaak.com/the-magic-that-is-nginx_reverse_proxy/</link><guid isPermaLink="false">67a0ffd80fe1a4054dd73971</guid><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Mon, 03 Feb 2025 18:06:32 GMT</pubDate><media:content url="https://svn.matthiashaak.com/content/images/2025/02/0_3.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://svn.matthiashaak.com/content/images/2025/02/0_3.jpeg" alt="The magic that is nginx_reverse_proxy"><p>I cannot count anymore how many times the<a href="https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/"> reverse proxy function of nginx</a> has saved my life - or sanity - in course of the last year. </p><p>Like every life-saving, close-to-magic technology, it needs mastery. Which I do not command, thus having to spend endless nights fiddling around for the little joy of &quot;it works&quot;. </p><p>One pitfall is that you cannot seem to use <a href="https://nginx.org/en/docs/http/ngx_http_sub_module.html">sub_filter</a> (which replaces a string with another string) on compressed resources. Which is a bit of a bummer but at least there is a workaround: set an empty accept-encoding header. I found it here.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://marsbard.github.io/2016-07-30-replace-urls-behind-nginx-reverse-proxy/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Replace URLs with nginx sub_filter</div><div class="kg-bookmark-description">Replacing URLs in content served behind nginx reverse proxy</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.io/favicon.ico" alt="The magic that is nginx_reverse_proxy"><span class="kg-bookmark-author">Martin Cosgrave</span><span class="kg-bookmark-publisher">Martin Cosgrave</span></div></div><div class="kg-bookmark-thumbnail"><img src="http://marsbard.github.io/img/avatar-icon.png" alt="The magic that is nginx_reverse_proxy"></div></a></figure><p>But why am I so excited? Just a small example:</p><p>In my current scenario, I have a setup that generates a folder structure like this:<br><code>domain-like-folder/subfolders/of/content</code> <br>with language automatically prepended, meaning<br><code>language/domain-like-folder/subfolders/of/content</code> </p><p>What I need is <br><code>domain.tld/language/subfolders/of/content</code> </p><p>I set up a server with the above domain folder structure on a server called <code>origin.tld</code>. Then I configure domain.tld like this (simplified):</p><pre><code> location /language/ {               
  proxy_pass https://origin.tld/language/domain-like-folder/;
  proxy_set_header Accept-Encoding &quot;&quot;;
  sub_filter   /language/domain-like-folder/   /language/;
  sub_filter_once off;
}</code></pre><p>The <code>sub_filter</code> directive is necessary (and also a big part of the magic), as the generated links on the origin domain would look like this: <code>&lt;a href=&quot;/language/domain-like-folder/other-subfolder&quot;&gt;</code> which is what I&apos;m trying to avoid. Of course, <em>you</em> can use <code>sub_filter</code> much more sophisticatedly than I do here.</p><p>You know better? Write to me! @gurkendoktor@mastodon.cloud</p><p>I haven&apos;t posted in a while. That happens. A few posts will follow this one in the near future. Not all of them will be about nginx. Some will be about Astro, 11ty, Headless CMS, or other random stuff. </p>]]></content:encoded></item><item><title><![CDATA[The case of the missing images]]></title><description><![CDATA[Images on landing pages hosted within Eloqua are not being displayed on iOS17 private tabs. A summary of possible reasons and fixes.]]></description><link>https://svn.matthiashaak.com/images-missing-on-eloqua-pages/</link><guid isPermaLink="false">6511ac8cb6680f654e13808b</guid><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Sun, 01 Oct 2023 15:38:08 GMT</pubDate><media:content url="https://svn.matthiashaak.com/content/images/2023/10/img-elq-missing-links.png" medium="image"/><content:encoded><![CDATA[<img src="https://svn.matthiashaak.com/content/images/2023/10/img-elq-missing-links.png" alt="The case of the missing images"><p>It is Friday evening, a bunch of campaign landing pages on Eloqua are about to be launched, and suddenly no images are being loaded. Instead, a &#x201C;warning&#x201D; is displayed. What happened? And what does it mean? Can it be solved?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://svn.matthiashaak.com/content/images/2023/10/Screenshot-2023-09-22-at-17.26.28.jpeg" class="kg-image" alt="The case of the missing images" loading="lazy" width="1104" height="1308" srcset="https://svn.matthiashaak.com/content/images/size/w600/2023/10/Screenshot-2023-09-22-at-17.26.28.jpeg 600w, https://svn.matthiashaak.com/content/images/size/w1000/2023/10/Screenshot-2023-09-22-at-17.26.28.jpeg 1000w, https://svn.matthiashaak.com/content/images/2023/10/Screenshot-2023-09-22-at-17.26.28.jpeg 1104w" sizes="(min-width: 720px) 720px"><figcaption>Images are not visible, instead a system dialogue is presented</figcaption></figure><h3 id="prerequisites">Prerequisites</h3><p>Let&#x2019;s take a look at the setup first. On client side, I&#x2019;m using an iPhone 11 Pro with iOS17, pretty much stock, and Safari in Private mode. I live in a country where Private Relay is not available, so this is not enabled.</p><p>On the server side, the pages are hosted as landing pages in Eloqua. Eloqua is a marketing automation system by Oracle, and one of the key players in the martech industry.</p><p>When you are using Eloqua, there is an option to use your own (Sub)domain with Eloqua, which is basically achieved through CNAME DNS entries. These can be also used for assets such as images etc. So instead of the generic asset domain <code>img06.en25.com</code> the domain would be something like <code>assets.myELQdomain.com</code>. Other, similar solutions, have a similar option that works a similar way. It is safe to say that this is &#x201C;industry standard&#x201D;. This was also the setting of the landing page in question.</p><h3 id="the-issue">The issue</h3><p>When loading the page, things got interesting. Using <code>pages.myELQdomain.com/page</code> would present the landing page, but every request to <code>assets.myELQdomain.com</code> would not be responded to. Changing the image URL to <code>img06.en25.com</code> however would immediately load the image, so it can&#x2019;t be an issue of blocked content.</p><p>While trying to find an answer, I&#x2019;ve tried different proxying methods to see the phone&#x2019;s network traffic. The moment I used a proxy, the problem would not occur. &#x201C;Sus&#x201D;, as they say. The same problem happens when I reference the images on a website outside the Eloqua environment (or at least the <code>myELQdomain.com</code>). That makes it is really hard to investigate what&#x2019;s happening here. But at least we know that the very interesting way of naming images &#x2013; the generated file name has curly brackets &#x2013; is not the culprit.</p><h3 id="cname-cloaking">CNAME cloaking?</h3><p>The first suspicion was a prevention of CNAME cloaking. CNAME cloaking means setting up a <code>subdomain.mydomain.tld</code> DNS entry which points to <code>tracking.adtech.tld</code> and is a common technique to keep 3rd party cookies alive. For everyone interested in a deeper read, here is a <a href="https://tma.ifip.org/2020/wp-content/uploads/sites/9/2020/06/tma2020-camera-paper66.pdf">paper about CNAME cloaking</a> &#x2013; <a href="https://medium.com/nextdns/cname-cloaking-the-dangerous-disguise-of-third-party-trackers-195205dc522a">or you read it in a simpler fashion in a blog post from NextDNS</a>. Every tool that supports privacy (ad blockers, Pi-Hole, privacy conscious VPNs) have lists to prevent this, and basically either blocks the request from being sent, or resolve the domain <code>subdomain.mydomain.tld</code> to the server 0.0.0.0 or NXDOMAIN instead of the original entry, preventing 3rd parties from tracking you.</p><p>The suspicion for this being the reason arose as the assets domain is resolved to a CNAME which is resolved to another CNAME to another CNAME to the Akamai edge. That can however not be the (only) case, as for opening the image in a new tab would work. So it cannot be (only) that. What else can it be?</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://svn.matthiashaak.com/content/images/2023/10/eloqua_dns_cname.png" class="kg-image" alt="The case of the missing images" loading="lazy" width="800" height="523" srcset="https://svn.matthiashaak.com/content/images/size/w600/2023/10/eloqua_dns_cname.png 600w, https://svn.matthiashaak.com/content/images/2023/10/eloqua_dns_cname.png 800w" sizes="(min-width: 720px) 720px"><figcaption>Multiple CNAMEs on top of each other</figcaption></figure><h3 id="private-dns">Private DNS?</h3><p>The next suspicion was triggered by those 2 articles:<br>[<a href="https://help.nextdns.io/t/q6yq4xy/nextdns-stops-working-properly-when-updating-to-ios-17-ipados-17">https://help.nextdns.io/t/q6yq4xy/nextdns-stops-working-properly-when-updating-to-ios-17-ipados-17</a>]<br>[<a href="https://news.ycombinator.com/item?id=36245362">https://news.ycombinator.com/item?id=36245362</a>]</p><p>The private mode of iOS17 is using the Apple DNS server, thus enabling Private Relay through the back door. Interesting. And not possible to inspect (for me!), as this is based on QUIC and DNS over HTTPS. The moment you add a &#x201C;man in the middle&#x201D; (like a debugging proxy) everything &#xA0;is resolved normally. It also does not happen on MacOS, even though Safari 17 for macOS should have the same measures as its iOS sibling.</p><p>But again, opening the image in a new tab works, so it can&#x2019;t be that. Or not alone.</p><h3 id="another-possible-reason">Another possible reason</h3><p><strong>Headers</strong>. The page is sending the request with a <code>Sec-Fetch-Site: same-site</code> header, whereas it is sending <code>Sec-Fetch-Site: cross-site</code> to the <code>imgXX</code> domains. This makes sense, and the behaviour would make sense if the seemingly same site is resolved to what is a cross-site request header.<br>As the headers show the same behaviour when trying to reproduce it in an external setting, the problem is not reproducible anymore.</p><p><strong>A combination</strong>. None of the above alone seems to cause a problem. That makes it harder to detect, but also easier to circumvent.</p><h3 id="how-can-i-avoid-it">How can I avoid it?</h3><h4 id="host-assets-externally">Host assets externally</h4><p>Loading assets like images, CSS etc from an asset server does not seem to trigger the issue. So if you can, do that. It can be as easy as an AWS S3 bucket, or a bare metal nginx static web server. Your assets also may load faster, and you can fine tune the loading performance.</p><h4 id="host-the-pages-externally">Host the pages externally</h4><p>Let the landing page live outside Eloqua. You can still load the assets from Eloqua. This decreases the probability your page to run into a content blocker like uBlock Origin. On the other hand, it makes you miss out on the features of Eloqua like the block builder or easy form integration. You would need someone with deeper technical knowledge or a developer to help you here, especially transferring and connecting forms and actions. Delivering gated content is also changing.</p><h4 id="quick-fix-change-the-url">Quick fix: change the URL</h4><p>Open your landing page in the code view and replace every occurrence of <code>assets.myELQdomain.com</code> with <code>img06.en25.com</code> (or whatever the corresponding default for your Eloqua instance is). This might be cumbersome, but it is a quick solution.</p><h3 id="what-will-the-future-bring">What will the future bring?</h3><p>The problem occurred on iOS17 in a private tab. It is said that approx. 20-35% of users use a sort of &#x201C;incognito&#x201D; or &#x201C;private&#x201D; mode to surf the web. However, the setting to use the same &#x201C;non-tracking behaviour&#x201D; in all web browser tabs, is very easy to reach, and activation is highly encouraged. &#xA0;With the usually very fast adoption of new iOS version, it can be approximated that within the next 6 months 30% of all iOS traffic will see this behaviour. So ultimately, the root cause of this should be found out to see how it can be dealt with in a more sustainable manner.</p>]]></content:encoded></item><item><title><![CDATA[Building a modular marketing landing page with Eleventy / Part 2]]></title><description><![CDATA[This article demonstrates how to build a modular landing page (like Gutenberg blocks) with in Eleventy. Featuring shared content, and multisite scenario.]]></description><link>https://svn.matthiashaak.com/building-a-modular-landing-page-with-11ty-part-2/</link><guid isPermaLink="false">640b50aeb6680f654e13806c</guid><category><![CDATA[eleventy]]></category><category><![CDATA[frontend]]></category><category><![CDATA[dev]]></category><category><![CDATA[modular]]></category><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Fri, 10 Mar 2023 15:50:52 GMT</pubDate><media:content url="https://svn.matthiashaak.com/content/images/2023/03/eleventy_2.jpg" medium="image"/><content:encoded><![CDATA[<h2 id="recap">Recap</h2><img src="https://svn.matthiashaak.com/content/images/2023/03/eleventy_2.jpg" alt="Building a modular marketing landing page with Eleventy / Part 2"><p>In the <a href="https://svn.matthiashaak.com/building-a-marketing-landing-page-with-11ty-part-1/">first part of the series</a> we&#x2019;ve configured Eleventy to use flexible modules which bring all their own layouts and assemble them to a single page. Now let&#x2019;s crank it up and make it even more flexible and useful.</p><h2 id="next-steps-this-part">Next steps (this part)</h2><p>First of all, we still need to sort the modules in a way that they make sense and tell a story, to bring them into the correct order (or any order at all).</p><p>Our second task is to create multiple pages within one site with the same approach, so that we&#x2019;re not limited to building single-page websites &#x2013; and from there we will take a quick detour into building similar sites from with content shared between them.</p><p>While we touch things, we will also clean up the code (actually the content) a little, so that it is easier to understand and maintain.</p><h2 id="sorting-items">Sorting items</h2><p>When we left off, we&#x2019;ve had a modular landing page with configurable modules that can have different appearances. The content is still a little unorganised, though. Let&#x2019;s fix that first.</p><p>We want to display the modules in a certain order. We could achieve this by naming the modules accordingly, and rely on the file system&#x2019;s capability to do it as we need it. Or we make this a little more reliable, with one of Eleventy&#x2019;s custom filters. I found the solution <a href="https://learneleventyfromscratch.com/lesson/8.html#what-are-collections">here</a>, Eleventy docs about sorting can be found <a href="https://www.11ty.dev/docs/collections/#sorting">here</a>. You can also use as file name based sorting approach, you have to configure your collection accordingly.</p><p>What we want to achieve though is to sort all sections by their <code>displayorder</code> front matter property, and then pull out whatever we need.</p><pre><code class="language-js">    config.addCollection(&apos;sections&apos;, collection =&gt; {
        return collection
            .getFilteredByTag(&apos;section&apos;)
            .sort((a, b) =&gt; (Number(a.data.displayorder) &gt; Number(b.data.displayorder) ? 1 : -1));
        });    
</code></pre><p>One small trick: if your layout still needs to be flexible, you can use increments of 10 between each section and leave some space to easily add sections inbetween without having to renumber everything. A bit like back in the days when we were using line numbers for programming.</p><h2 id="building-multiple-pages-together">Building multiple pages, together</h2><p>So far, we&#x2019;ve built one page with different sections. That&#x2019;s fine for a landing page, but even landing pages tend to consist of multiple (sub-)pages. So let&#x2019;s build multiple pages with our existing approach.</p><h3 id="multiple-pages">Multiple pages</h3><p>Creating multiple pages in principle is easy with Eleventy. You create one Markdown file for each page you want to build, and you&#x2019;re mostly fine. Eleventy will build the structure for you, and make it all accessible to the <code>pagination</code> functionality. Pagination is a mighty tool. However, we are not talking about pagination today, instead we will use a slightly different approach.</p><p>To recap, this is our landing page:</p><pre><code>---
title: &quot;My Awesome Landing Page&quot;
layout: &quot;layout.njk&quot;
header:
  heroImage:
  mainHeadline:
  secondaryHeadline:
  primaryCTAText:
  primaryCTALink:

footer:
  companyName: &quot;Awesome, Inc.&quot;

config:
  date: 2022

---
</code></pre><p>We want only a few, distinct pages, that we can link &#x201C;manually&#x201D;. So let&#x2019;s introduce a new piece of data and call it <code>pagealias</code>. You could use the slug of the page here or pretty much anything, as long as it is unique.</p><p>In the markdown file, we need to change two things: to our <code>index.md</code> file, we&#x2019;ll add the following:</p><p><code>alias: landingpage</code></p><p>In the markdown files of the modules or sections we want to be displayed on that page, we need to add something as well to connect them. We do it as such:</p><p><code>pagealias: landingpage</code></p><p>Now it is more obvious, to which page this belongs.</p><p>Depending on your preferences, you could map it to something existing (like the <code>permalink</code>), or be a bit more obvious and in the &#x201C;master file&#x201D; notate the pagealias too. I&#x2019;ve chosen the second option here so the approach is more obvious.</p><p>Make sure reflect this in your template as such:</p><pre><code>{% for item in collections.sections %}
	{% if item.pagealias == alias %}
		{% if item.data.layout %}

			{% include item.data.layout %}

		{% endif %}
	{% endif %}
{% endif %}
</code></pre><p>Now, only content relevant for this particular page will appear on it.</p><p>Next, let&#x2019;s create a file <code>aboutus.md</code></p><pre><code>---
title: &quot;My Awesome About Us Page&quot;
layout: &quot;layout.njk&quot;
header:
  heroImage:
  mainHeadline:
  secondaryHeadline:
  primaryCTAText:
  primaryCTALink:

footer:
  companyName: &quot;Awesome, Inc.&quot;

config:
  date: 2022

alias: aboutus
---
</code></pre><p>After that, let&#x2019;s create a folder <code>aboutus</code> in our source folder, and add the following file <code>aboutus1.md</code></p><pre><code>---
title: &quot;Simple text / image section for about us&quot;
tags: &quot;section&quot;
layout: &quot;modules/text-image-basic.njk&quot;
headline: &quot;All about us!&quot;
image: &quot;assets/images/awesome_team.jpg&quot;
imagealttext: &quot;An awesome image&quot;
permalink: false
pagealias: aboutus
---
This is all about us. You can make it about anything you want, but it is really only about us.

</code></pre><p>When the build runs, you will see in your <code>dist</code> folder two files: index.html and aboutus.html &#x2013; with the respective content on each page. Isn&#x2019;t that great news?</p><p>There are some caveats to this. If your website contains a really awful lot of pages, and those contain a lot of section each, the &#x201C;collections.sections&#x201D; might hit hard on your memory, and nested loops may impact your build performance.</p><p>In the scenarios I&#x2019;m using this for (approx. 100 pages), I don&#x2019;t see any impact (Eleventy is blazingly fast!) but your mileage may vary. It would then also pose different challenges in regards of content organisation, but that&#x2019;s not to be covered here.</p><h3 id="dry-up">DRY up</h3><p>Let&#x2019;s clean things up quickly, as we move forward. To do that, we apply the DRY principle here.</p><p>DRY stands for Don&#x2019;t repeat yourself. That&#x2019;s a principal that got famous with Ruby on Rails, and although it&#x2019;s not always practical to stick to it by the book, it should be on the list of things to achieve &#x2013; for once as multiple occurrences of the same code are harder to maintain and might introduce bugs through the back door.</p><p>In our case, it&#x2019;s a bit more simple. We don&#x2019;t want to litter our front matter with redundant information. So &#x2026; let&#x2019;s look at it. We have in every of our module&#x2019;s front matter</p><pre><code>tags: 
- section

pagealias: landingpage
permalink: false
</code></pre><p>To clean this up a little, Eleventy&#x2019;s data model helps us. We put all our sections in a folder called <code>sections</code>, and within this place a <code>sections.json</code> with the following content:</p><pre><code>{
	permalink: false,
	pagealias: &quot;landingpage&quot;
	tags: [&quot;section&quot;]
}
</code></pre><p>This would clear our frontend matter to quite an extent. One could argue that this is not a semantic use of tags, but tags in Eleventy are meant to structure your content both in a semantic and technical way &#x2013; or do anything you like, really.</p><h2 id="re-purposing-items">Re-purposing items</h2><p>One of the things that you come across is that you have the same items re-appearing over the page. I want to quickly demonstrate a way to re-purpose items. This will get more important in Part 3 of the series, but here&#x2019;s a start.</p><p>You can repurpose items in different ways. The path that we&#x2019;re taking here is on module level &#x2013; we&#x2019;re building a module that uses shared content. With this approach, we have control where this module appears on the page on a page level. For repeating items that have a fixed position within the page structure, the approach might be different.</p><p>In our example, we will build a proof section. Because what we need for ultimate credibility is of course some proof from our existing customers, that everything is awesome, and they are the happiest. We choose here the &#x201C;user quotes&#x201D; section: &#x201C;What our customers say&#x201D;. We would need this kind of section towards the end of the landing page, and around the middle on the &#x201C;about us&#x201D; page.</p><p>First, let&#x2019;s create a &#x201C;shared&#x201D; folder within our source folder, so we have a place for content that might be shared across pages. This is not necessary for Eleventy, only for us to find it more easily.</p><p>Within this folder, we create a &#x201C;quotes&#x201D; folder, wherein each Markdown file is one quote. An example would look like this, you can come up with more.</p><pre><code>---
tags:
	- quote

avatarimage: https://thispersondoesnotexist.com/image
avatarimagealt: &quot;an image of a random person, pretending to be our customer&quot;
username: Klaus Kleinholz
quote: This building block system is awesome! I don&apos;t know how I could live without it before.
---
</code></pre><p>Next, we make all the quotes accessible in a collection.</p><pre><code class="language-js">    config.addCollection(&apos;userquotes&apos;, collection =&gt; {
        return collection
            .getFilteredByTag(&apos;quote&apos;);
        });    
</code></pre><p>Now, we have all the quotes in one place.</p><p>Then, we create the module layout file <code>userquotes.njk</code>:</p><pre><code>&lt;section class=&quot;section_userquotes&quot;&gt;
	&lt;h2&gt;{{ item.data.title }}&lt;/h2&gt;
	&lt;ul class=&quot;quotes&quot;&gt;
	{% for quote in userquotes %}
		&lt;li class=&quot;quote&quot;&gt;
			&lt;img src=&quot;{{quote.data.avatarimage}}&quot; alt=&quot;{{quote.data.avatarimagealt&quot;&gt;
			&lt;q&gt;{{quote.data.quote}}&lt;/q&gt;
			&lt;h5&gt;{{quote.data.username}}&lt;/h5&gt;
		&lt;/li&gt;
	{% endfor %}
	&lt;/ul&gt;
&lt;/section&gt;
</code></pre><p>We can even choose to make this configurable with parameters and assign this to only certain pages, but for now let&#x2019;s use this one as is. We will come back to this in the next episode.</p><p>Finally, we need to create a module for each page in the corresponding content area (that is the <code>sections</code> folder and the <code>aboutus</code> folder).</p><pre><code>---
title: &quot;And this is what our customers say&quot;
tags: &quot;section&quot;
layout: &quot;modules/userquotes.njk&quot;
permalink: false
pagealias: landinpage
displayorder: 7
---

</code></pre><p>And</p><pre><code>---
title: &quot;But enough about us, here&apos;s our customers&quot;
tags: &quot;section&quot;
layout: &quot;modules/userquotes.njk&quot;
permalink: false
pagealias: aboutus
displayorder: 3
---
</code></pre><p>The items that we&#x2019;ve already banished to the .json file are just displayed here for clarity. You can remove them if you have applied the above principle.</p><h2 id="multisite-shared-content">Multisite, shared content</h2><p>So now we&#x2019;ve got this done, let&#x2019;s imagine the following scenario: you want to build multiple landing pages about the same thing &#x2013; say a washer and a dryer. There are some differences, but also content items (or even whole pages) that these sites share.</p><p>There are Eleventy plugins that take care of that, e.g <a href="https://github.com/bnoctis/eleventy-multisite">Eleventy multisite</a>. However, I want to show a very simple approach. This is not a scalable approach by any means, and doesn&#x2019;t cover edge cases, it is just how far you can get without having to know much about development.</p><p>To do so, we go back to a technique that we&#x2019;ve been using before. Without big changing, we&#x2019;re creating another alias which we call &#x201C;site&#x201D;. This can contain the whole domain, if you want &#x2013; or just a shortcut / slug of sort. It just needs to be unique.</p><p>In our front matter, we add the <code>site</code> property to the initial document, and all the modules. This way, you make sure that information only displays on the sites you want to have it.</p><p>The only thing that we need to change is in our base template:</p><pre><code>{% for item in collections.sections %}
	{% if site in item.data.site %}
		{% if item.pagealias == alias %}
			{% if item.data.layout %}

				{% include item.data.layout %}

			{% endif %}
		{% endif %}
	{% endif %}
{% endfor %}
</code></pre><p>(Note we&#x2019;re using <code>in</code> here instead of <code>==</code> so that we can assign content to multiple sites.)</p><p>It might seem cumbersome to attach information to every different content item, but in the end this approach is not meant to be for a multisite with 1.000 pages each. It&#x2019;s just for a small set of sites that ideally share content between them and are set up the same way. Otherwise you might want to either go a different route, or set up multiple projects individually.</p><p>To save the sites in individual folders, we need to modify the <code>permalink</code> in the front matter so that it would like this:</p><pre><code>---
# &#x2026;
site: site1.com
permalink: &quot;{{ site }}/{{filePathStem}}/{{fileSlug}}/index.html&quot;
# &#x2026;
---
</code></pre><p>As a result, the generated pages would be available in <code>dist/site1/&#x2026;</code>. There is a way that you can configure this in the .eleventy.js config file from Eleventy 2.0.0 onwards, which makes it again a bit easier.</p><p>There&#x2019;s one thing missing though. Something important. We have not been dealing with any images or CSS so far. What is very easy understandable to start with is to just add a passthrough copy to the <code>.eleventy.js</code> config file.</p><p>The process is described <a href="https://www.11ty.dev/docs/copy/">in the docs for passthroughCopy</a>.</p><p>Our file can look like this:</p><pre><code class="language-js">module.exports = config =&gt; {
    config.addPassthroughCopy(&quot;assets/**&quot;);
};
</code></pre><p>When we&#x2019;re dealing with multiple <strong>sites</strong> however, things get a little more complicated. The most straightforward approach is to copy it into every single site folder. In a very blunt manner, we can add an after hook and perform a synchronous copy operation. So &#x2026; let&#x2019;s do that and start off with quickly creating a new collection of the <code>site</code> property with Eleventy&#x2019;s custom filtering:</p><pre><code class="language-js">config.addCollection(&quot;sites&quot;, function(collectionApi) {
        return collectionApi.getAll().filter(function(item) {           
            return &quot;site&quot; in item.data;
        });
});    
</code></pre><p>Now we have a collection, but as I couldn&#x2019;t work out how to access this in Javascript through build time, we need a slight addition as such:</p><pre><code class="language-js">//put these outside the module.exports section of .eleventy.js
const fs = require(&apos;fs&apos;)
const path = require(&apos;path&apos;)
let siteNames = new Array();

config.addCollection(&quot;sites&quot;, function(collectionApi) {
        // build our own collection based on the &quot;site&quot; property
        let sites = collectionApi.getAll().filter((item) =&gt; item.data.site);        
        
        //make collection contents available to the be accessed by overall config
        for(let i=0;i&lt;sites.length;i++) {
            siteNames.push(sites[i].data.site);
        }

        //make collection available to eleventy
        return sites;
    });

    config.on(&apos;eleventy.after&apos;, async ({ dir, results, runMode, outputMode }) =&gt; {
       sitesNames.forEach(element =&gt; {
             fs.cpSync(&quot;./src/shared&quot;,&quot;./dist/&quot;+element+&quot;/shared&quot;,{recursive:true});
        });        
    });
</code></pre><p>Which will copy everything from the &#x201C;shared&#x201D; folder into the destination folder for the generated sites. If you have site specific assets, you can add something like this</p><pre><code class="language-js">sitesNames.forEach(element =&gt; {
        fs.cpSync(&quot;./src/shared&quot;,&quot;./dist/&quot;+element+&quot;/shared&quot;,{recursive:true});
		fs.cpSync(&quot;./src/&quot;+element+&quot;/assets/**&quot;,&quot;./dist/&quot;+element+&quot;/assets&quot;,{recursive:true});
        });     
</code></pre><p>You need to create a folder with the site name in the<code>src</code> folder, and after page creation everything will copied automatically. However, this does not trigger reload in an <code>eleventy --serve</code> scenario.</p><h2 id="summary">Summary</h2><p>We&#x2019;ve learnt how to extend our page to multiple pages and to sites, how to display things in a certain order and how to share content between pages and sites.</p><h2 id="next-part">Next part</h2><p>In part 3 we will extend what we have built so far and apply it for the scenario of A/B testing, and will also create a basic personalisation. With static pages? Yes, with static pages. This is where we would need a bit more than Eleventy, but I promise that no Javascript will be involved.</p><h2 id="addendum">Addendum</h2><p>I really can recommend you to go through the <a href="https://learneleventyfromscratch.com/">Learn Eleventy From Scratch website</a>. Even if you might not need the &#x201C;from scratch&#x201D; part, some concepts are explained in a more practical and understandable way than in the docs. The docs are brilliant, but (by design) have the abstract, general approach whereas on that site you can find good examples of how things <em>actually</em> look like when you work with them. You then would need to backtrace from them to the overall concept.</p>]]></content:encoded></item><item><title><![CDATA[Building a modular marketing landing page with Eleventy / Part 1]]></title><description><![CDATA[My approach to building a flexible landing page in Eleventy. ]]></description><link>https://svn.matthiashaak.com/building-a-marketing-landing-page-with-11ty-part-1/</link><guid isPermaLink="false">6333d9edb6680f654e138032</guid><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Wed, 28 Sep 2022 05:37:51 GMT</pubDate><media:content url="https://svn.matthiashaak.com/content/images/2022/09/dtdi_building_with_11ty.jpg" medium="image"/><content:encoded><![CDATA[<h2 id="precondition">Precondition</h2><img src="https://svn.matthiashaak.com/content/images/2022/09/dtdi_building_with_11ty.jpg" alt="Building a modular marketing landing page with Eleventy / Part 1"><p>In many digital marketing contexts, you would need &#x201C;a quick landing page&#x201D; for a product, a service, or something. It is a single page that would look something like this, consisting of a set of different content blocks / sections / modules / components &#x2013; whatever you want to call it &#x2013;, presenting content in different ways.</p><figure class="kg-card kg-image-card"><img src="https://svn.matthiashaak.com/content/images/2022/09/landing-page-wireframe-example-long.jpg" class="kg-image" alt="Building a modular marketing landing page with Eleventy / Part 1" loading="lazy"></figure><p>A decent numbers of web developers have been there, and for some it&#x2019;s still their daily life. Everyone has their own approach to it, I&#x2019;ve been looking for something flexible and scalable with a high Developer Experience and the possibility of a quick and reliable delivery.</p><p>I will show you in this series how to build a scalable page with the Static Site Generator Eleventy (11ty / <a href="https://11ty.dev">11ty.dev</a>), how to build varieties of the same page (e.g personalisation or A/B testing) and how to extend this to a multisite environment. Another approach to the same problem is shown here <a href="https://github.com/ttntm/11ty-landing-page">https://github.com/ttntm/11ty-landing-page</a> &#x2013; see for yourself where the differences are and which approach suits you and your needs more.</p><h2 id="why-eleventy">Why Eleventy?</h2><p>There are a lot of static site generators out there to choose from (for a good overview you can start <a href="https://jamstack.org/generators/">here</a>. (If you need an introduction to the general topic, look at &#x201C;<a href="https://www.netlify.com/blog/2020/04/14/what-is-a-static-site-generator-and-3-ways-to-find-the-best-one/">What is a static site generator?</a>&#x201D;) The choice can be made based on technology, hype cycle, existing infrastructure, or &#x201C;other criteria&#x201D;.</p><p>I have settled on Javascript as I have lesser experience with Go, Ruby, or Python &#x2013; and PHP &#x2026; well, is PHP. I use it and I don&#x2019;t hate it, but in the overall Jamstack ecosystem it is not very prominent. Besides the fact, that <a href="https://rosswintle.uk/2021/12/hang-on-php-is-a-static-site-generator/">PHP <em>is</em> a static site generator already.</a></p><p>While looking at the different options, these parts were the most important to me:</p><ul><li>any number of sections should be possible</li><li>sections should have a <strong>configurable</strong> flexible appearance</li><li>DRY (if possible)</li><li>Markdown based (I might want to scale this to a headless CMS)</li><li>No server or client side dependencies beyond the SSG itself.</li></ul><p>Second tier criteria were:</p><ul><li>Availability of skills</li><li>Learning curve</li><li>Extensibility (multi-site / multi-language)</li></ul><p>The not so important things were</p><ul><li>build support &#x201C;on the edge&#x201D;</li><li>build times</li><li>&#x201C;native integration&#x201D; (e.g into Netlify, Vercel, &#x2026; )</li></ul><p>Eleventy fulfils two criteria out of the box.</p><p>There is no framework dependency on client side, means we don&#x2019;t need to ship Javascript if we don&#x2019;t want to, which is always a big big plus in my book. Additionally, we are not subject to dependencies beyond Eleventy. That might vary for your use case, but I like to leverage the <a href="https://speaking.adactio.com/ZCJ61M">layers of the web</a> to their full extent to build a robust experience.</p><p>You can use Markdown (amongst other languages) to store content.</p><p>Besides that, it&#x2019;s blazingly fast and flexible, and I&#x2019;ve read a lot of good things about it. But after so much ado, let&#x2019;s get going.</p><h2 id="building-the-page">Building the page</h2><p>Given that content and design are ready, let&#x2019;s build the page. We quickly start with setting up the page build environment as such:</p><p><code>src</code> &#x2013; the folder where our source files are. Layouts, content, images, media, everything has its place under this.</p><p><code>dist</code> &#x2013; here is where the rendered page lives.</p><p>Create a file <code>.eleventy.js</code> that looks like this:</p><pre><code>module.exports = config =&gt; {
	return {
        dir: {
            input: &apos;src&apos;,
            output: &apos;dist&apos;
        }
    }
};
</code></pre><p>If you prefer something different, you can change this in <code>.eleventy.js</code></p><p>To learn more about setting up and configuring an Eleventy project, check <a href="https://11ty.dev">the 11ty website</a> or <a href="https://www.11ty.dev/docs/tutorials/">one of their great tutorials</a>.</p><h3 id="the-layouts">The layout(s)</h3><p>The layout can be split into the main layout (base template, if you might), and the partials for the different parts of the page. The sections part plays a special role, but we&#x2019;ll come to that.</p><p>The layouts live in a special folder, <code>src/_includes</code>.</p><h4 id="the-base-main-layout">The base / main layout</h4><p>We start with the overall layout definition: <code>layout.njk</code></p><pre><code>&lt;html&gt;
	{% include &quot;partials/head.njk&quot; %}
	&lt;body&gt;
		{% include &quot;partials/header.njk&quot; %}
		{% include &quot;partials/sections.njk&quot; %}
		{% include &quot;partials/footer.njk&quot; %}
	&lt;/body&gt;
&lt;/html&gt;
</code></pre><h4 id="the-partials">The partials</h4><p>The layout for the partials can all be found within the <code>_includes/partials</code> folder. You can put them wherever you want, you just need to let Eleventy know about this.</p><p>Let&#x2019;s start with a very simple HTML head section in <code>partials/head.njk</code>:</p><pre><code>&lt;title&gt;
	{{ page.title }}
&lt;/title&gt;
</code></pre><p>After that, we define the header. It consists of two types of headlines, a big image and two CTAs (primary and secondary)</p><p><code>partials/header.njk</code></p><pre><code>&lt;header&gt;
	&lt;img class=&quot;image-hero-image&quot; 
		 src=&quot;{{ header.heroImage }}&quot;&gt;
	&lt;div class=&quot;text&quot;&gt;
		&lt;h1 class=&quot;text-hero-primary&quot;&gt;
			{{ header.mainHeadline }}
		&lt;/h1&gt;
		&lt;h2 class=&quot;text-hero-secondary&quot;&gt;
			{{ header.secondaryHeadline }}
		&lt;/h2&gt;
	&lt;/div&gt;
	&lt;div class=&quot;buttons&quot;&gt;
		&lt;a class = &quot;button-cta-primary&quot; 
			href = &quot;{{ header.primaryCTALink }}&quot;&gt;
			{{ header.primaryCTAText }}
		&lt;/a&gt;
	&lt;/div&gt;
&lt;/header&gt;
</code></pre><h4 id="the-sections">The sections</h4><p>For the sections, we use Eleventy&#x2019;s <strong><a href="https://www.11ty.dev/docs/collections/">collections</a></strong> feature. We could set this based on folders or tags, for the sake of simplicity we&#x2019;re using the tag &#x201C;section&#x201D; for every section here. The file looks as follows:</p><p><code>partials/sections.njk</code></p><pre><code>{% for item in collections.section %}

	{% if item.data.layout %}

		{% include item.data.layout %}

	{% endif %}

{% endfor %}
</code></pre><p>This iterates over all files that belong to the <code>section</code> collection, checks if <code>layout</code> data is present in the frontmatter and includes the corresponding file. This gives us a lot of flexibility in how the content can be set up for the individual module.</p><p>An example section layout could look like this:</p><p><code>modules/text-image-basic.njk</code></p><pre><code>&lt;section class=&quot;item.data.sectionClass&quot;&gt;
	&lt;div class = &quot;container-section-image&quot;&gt;
		&lt;img class=&quot;image-section-image&quot; src=&quot;{{ item.data.image }}&quot; alt=&quot;{{ item.data.imagealttext }}&quot;&gt;
	&lt;/div&gt;
	&lt;div class = &quot;container-section-text&quot;&gt;
		&lt;h2&gt;{{ item.data.headline }}&lt;/h2&gt;
		{{ item.templateContent | safe }}
	&lt;/div&gt;
&lt;/section&gt;
</code></pre><p>Let&#x2019;s create the template for the footer quickly, before moving on to the data.</p><p><code>partials/footer.njk</code></p><pre><code>&lt;footer&gt;
	&lt;div class = &quot;footer-conversion-area&quot;&gt;
		&lt;!-- use your signup input here --&gt;
	&lt;/div&gt;
	&lt;hr&gt;
	&amp;copy; {{ config.date }}
&lt;/footer&gt;
</code></pre><h3 id="the-data">The data</h3><p>Let&#x2019;s start with the main file, <code>index.md</code>. This holds all the page meta information, like title etc. as well as the &#x201C;static parts&#x201D; like content for the header and the footer. All of that is in the front matter.</p><pre><code>---
title: &quot;My Awesome Landing Page&quot;
layout: &quot;layout.njk&quot;
header:
  heroImage:
  mainHeadline:
  secondaryHeadline:
  primaryCTAText:
  primaryCTALink:

footer:
  companyName: &quot;Awesome, Inc.&quot;

config:
  date: 2022

---
</code></pre><p>So with that done, we&#x2019;re moving forward to the sections. Creating a content section for the layout above in the <code>sections/section1.md</code> file.</p><pre><code>---
title: &quot;Simple text / image section&quot;
tags: &quot;section&quot;
layout: &quot;modules/text-image-basic.njk&quot;
headline: &quot;Most awesome feature&quot;
image: &quot;assets/images/awesome.jpg&quot;
imagealttext: &quot;An awesome image&quot;
permalink: false
---
Amazing news for amazing people: **Our newest feature is now available for everyone.** You can see it in the other sections.
This is a second paragraph, it is only here for demonstration purposes.
</code></pre><p>There are 2 key things to note here:</p><p>The frontmatter carries a <code>layout</code> entry, just as any file would. This assigns the correct sectional layout to the content.</p><p>You might have spotted the <code>permalink: false</code> entry. This means, that Eleventy will <strong>not</strong> generate a separate page from this Markdown file as it normally would. Try to remove it and you&#x2019;ll end up with a /sections/section1/index.html file.</p><p>Additionally, the &#x201C;long copy&#x201D; content is not part of the frontmatter. This retains the full power of Markdown to be used in your content creation, you could even add extra images etc.</p><p>So far, we have the header, footer, and one section. Let&#x2019;s create a few more.</p><h3 id="flexing-the-sections">Flexing the sections</h3><p>To be really flexible, we need to create some more, different sections.</p><p>Let&#x2019;s start with the layout file: <code>modules/video.njk</code></p><pre><code>&lt;section class=&quot;{{ item.data.sectionClassName }}&quot;&gt;
	&lt;div class=&quot;container-text&quot;&gt;
		&lt;h2&gt;
			{{ item.data.headline }}
		&lt;/h2&gt;
		{{ item.templateContent | safe }}
	&lt;/div&gt;
	&lt;div class=&quot;container-video&quot;&gt;
		&lt;video controls=&quot;yes&quot; poster=&quot;{{item.data.posterimage}}&quot;&gt;
			&lt;source src=&quot;{{item.data.video}}&quot; type=&quot;video/mp4&quot;&gt;
            	Your browser does not support the video tag.
		&lt;/video&gt;
	&lt;/div&gt;
&lt;/section&gt;
</code></pre><p>Now let&#x2019;s create our content file: <code>sections/section_video.md</code></p><pre><code>---
title: &quot;Video section&quot;
headline: &quot;Watch how it comes to life&quot;
tags: &quot;section&quot;
layout: &quot;modules/video.njk&quot;
video: &quot;my-video.mp4&quot;
posterimage: &quot;assets/images/my-awesome-image.jpg&quot;
permalink: false
---
Here is additional content that accompanies the video. 

**See for yourself!**
</code></pre><p>We&#x2019;d probably also want an image that can be full width (or not), with an annotation, so we create <code>modules/full-image.njk</code></p><pre><code>&lt;section&gt;
    &lt;img src=&quot;{{ image }}&quot; alt=&quot;{{ imagealttext }}&quot;&gt;
    &lt;caption&gt;{{ imagecaption | safe }} &lt;/caption&gt;
&lt;/section&gt;
</code></pre><p>And the data file <code>section-image.md</code>:</p><pre><code>---
title: &quot;Simple image section&quot;
tags: &quot;section&quot;
layout: &quot;modules/full-image.njk&quot;
image: &quot;assets/images/awesome.jpg&quot;
imagealttext: &quot;An awesome image&quot;
imagecaption: &quot;A longer caption for &lt;strong&gt;this particular&lt;/strong&gt; image&quot;
permalink: false
---
</code></pre><p>So, the idea is clear. We&#x2019;re carrying a <code>layout</code> section in every section along with the content. This enables us to have as many different sections as we want. This is the first step on building modular landing pages.</p><h2 id="conclusion">Conclusion</h2><p>So far the ride has been smooth. We have now at our hands a simple and elegant way to quickly build an extensible marketing landing page. Especially when you have an existing component library or similar, it is very easy and very fast to set up pages in that way.</p><p>In its current state, this is only working to build one page, no links between pages and no &#x201C;sub-pages&#x201D;. After all, the first step was to build a flexible landing page and nothing more.</p><p><a href="https://svn.matthiashaak.com/building-a-modular-landing-page-with-11ty-part-2/">In the next step </a>we will see how we can use this approach with different pages, page-specific content and content shared across sections and pages.</p><p>Another thing to keep in mind is that this is <em>one</em> way to achieve it. There might be many others, the same could probably implemented with some template logic and some clever CSS, but in my opinion this approach is more obvious and easier to maintain. And I believe that a good Developer Experience results in better products.</p><h2 id="thanks">Thanks</h2><p>To all the amazing people behind Eleventy (especially <a href="https://www.zachleat.com/">Zach Leat</a>), to Stephanie Eckles and her <a href="https://11ty.rocks/">11ty.rocks</a> which you should really have a deep look into. </p>]]></content:encoded></item><item><title><![CDATA[Weekly Recap 1902]]></title><description><![CDATA[Reminiscing a thing long gone, fighting against legacy systems, a way to collaborate, and a no-code tutorial for a website]]></description><link>https://svn.matthiashaak.com/weekly-recap-1902/</link><guid isPermaLink="false">5dacb599d194522848a8d39d</guid><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Sun, 20 Oct 2019 19:31:28 GMT</pubDate><media:content url="https://svn.matthiashaak.com/content/images/2019/10/DSCF1652-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://svn.matthiashaak.com/content/images/2019/10/DSCF1652-1.jpg" alt="Weekly Recap 1902"><p>Reminiscing a thing long gone, fighting against legacy systems, a way to collaborate, and a no-code tutorial for a website.</p><h2 id="1-a-trip-down-memory-lane">1. A trip down memory lane</h2><p>Flash might be dead, but this week there was a few things that got a bit of the 2000s vibe back.</p><p>First, someone posted <a href="https://www.youtube.com/watch?v=eE6IOwiRPG4">a Youtube clip of the old 2advanced website</a> on Twitter.</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="480" height="270" src="https://www.youtube.com/embed/eE6IOwiRPG4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><figcaption>Video of 2advanced flash website</figcaption></figure><p>YouTube has more of those, I find the later ones even more characteristic for that era &#x2013; but go see for yourselves.</p><p>Then, a couple of times <a href="https://www.vice.com/en_us/article/d3awk7/flash-is-responsible-for-the-internets-most-creative-era">this Vice article about the creative era that flash coined</a>, and I really recommend to read it. The whole thing deserves an article on its own, but whereas from the standpoint of a standardized open web Flash is much questionable, the whole era, the spirit, and what it kicked off is unparalleled.</p><p>As an aside, <a href="https://www.vice.com/en_us/article/a33z78/yahoo-games-demise-shows-what-the-death-of-flash-could-feel-like">this article about Yahoo! Games</a> and the role Flash played in it (and, to an extent, made it), is also a very interesting read.</p><h2 id="2-can-i-use-for-emails">2. Can i use for emails</h2><p>All web developers (should) know <a href="https://caniuse.com">caniuse.com</a>, a page that shows the availability of CSS properties for every browser. <a href="https://caniuse.email/">Now this also exists for email development</a>, and it basically shows that Outlook sucks. This is the biggest reason why email development is still so cumbersome.</p><h2 id="3-designer-and-developers-it-doesn-t-have-to-be-hate">3. Designer and developers: it doesn&#x2019;t have to be hate</h2><p>Brad Frost and Dan Mall continuously share their attempts on improving the collaboration between designers and developers. Their attempt is the <a href="http://danmall.me/articles/hot-potato-process/">Hot Potato Process</a>, which sounds less fun than it seems to be.</p><h2 id="4-creating-a-page-in-webflow">4. Creating a page in Webflow</h2><p>I have been made aware of <a href="https://webflow.io%20">webflow.io </a> a couple of times now, and only heard much praise about it. I have only dipped my toes in the water, and didn&#x2019;t get very far as of now. However, <a href="https://designcode.io/webflow-course">this tutorial</a> might help me with that &#x2013; it teaches how to create &#x201C;a highly converting landing page&#x201D;, which is a task that can be happily taken over by a a low-code person in a low-code tool. Almost like Flash back then ;)</p>]]></content:encoded></item><item><title><![CDATA[My own personal data warehouse]]></title><description><![CDATA[<p>Just a quick one, all the logs are going throughs an ELK stack now. Pretty proud, more to come.</p>]]></description><link>https://svn.matthiashaak.com/roar-the-elk-is-here/</link><guid isPermaLink="false">5da79154d194522848a8d393</guid><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Wed, 16 Oct 2019 21:55:48 GMT</pubDate><media:content url="https://svn.matthiashaak.com/content/images/2019/10/DSCF3260-2.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://svn.matthiashaak.com/content/images/2019/10/DSCF3260-2.jpg" alt="My own personal data warehouse"><p>Just a quick one, all the logs are going throughs an ELK stack now. Pretty proud, more to come.</p>]]></content:encoded></item><item><title><![CDATA[Weekly Recap 1901]]></title><description><![CDATA[A (weekly) compilation of things that I stumbled across and found interesting.]]></description><link>https://svn.matthiashaak.com/weekly-recap-1901/</link><guid isPermaLink="false">5da37580d194522848a8d370</guid><category><![CDATA[weekly]]></category><category><![CDATA[summary]]></category><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Sun, 13 Oct 2019 19:09:53 GMT</pubDate><media:content url="https://svn.matthiashaak.com/content/images/2019/10/DSCF1652.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://svn.matthiashaak.com/content/images/2019/10/DSCF1652.jpg" alt="Weekly Recap 1901"><p>I have realized that I should write more. One of the easier ways of getting (back) into the habit is summarizing things that have already been written. So I&#x2019;ve set myself the goal of organizing the things I stumble across on a weekly basis, to keep them as a library to reference for myself and to make them available to whoever reads this here blog.</p><p>Part of the reason why I&#x2019;m doing this is based on this tweet, which I couldn&#x2019;t agree to more: <a href="https://twitter.com/usabilitycounts/status/1165717906849583104">https://twitter.com/usabilitycounts/status/1165717906849583104</a>. Writing helps to structure the thinking, and ultimately the outcome.</p><p>Here&#x2019;s the first post of this kind, and the one I hope to establish a long tradition.</p><h2 id="1-designing-enterprise-apps-is-not-easy">1. Designing enterprise apps is not easy</h2><p>In this blog post <a href="https://uxdesign.cc/how-white-space-killed-an-enterprise-app-and-why-data-density-matters-b3afad6a5f2a">https://uxdesign.cc/how-white-space-killed-an-enterprise-app-and-why-data-density-matters-b3afad6a5f2a</a> Christie Lenneville and Patrick Deuley describe the challenges they were presented with when redesigning an enterprise application that people use to get work done <em>in large amounts</em>.</p><p>They present us with the general notion that everyone likes white-spacey, airy apps that feel like contemporary products from successful start-ups, and that whitespace is not the only solution to structure content, or make things more efficient and still avoiding eyesore.</p><p>Key take away is</p><blockquote>&#x201C; [&#x2026;] our users don&#x2019;t have a choice, they have to use the tools we design&#x2014;if they don&#x2019;t think a system is usable, they can&#x2019;t decide to just go download a different one. So we owe it to them to put their daily productivity first&#x2014;always.&#x201D;</blockquote><h2 id="2-the-ussr-computer-of-the-future-that-never-was">2. The USSR computer of the future, that never was</h2><p>This article on inexhibit <a href="https://www.inexhibit.com/case-studies/project-sphinx-when-the-ussr-tried-to-change-the-computer/">https://www.inexhibit.com/case-studies/project-sphinx-when-the-ussr-tried-to-change-the-computer/</a> shows an interesting approach from the USSR of the late 80s to design a computer of the future, for the people. How bold was the move?</p><blockquote>&#x201C;The system was intended to replace all the technological devices in a house, computers, telephone, television, radio, audio system, and so on. To do that, a number of peripherals were included.&#x201D;</blockquote><p>It is basically a communist apple ecosystem of the 1980s, with the idea of a multiuser system in hardware.</p><p>For a continued reading on a more general topic, <a href="https://www.inexhibit.com/specials/history-of-computer-design-the-most-innovative-and-unconventional-pcs-ever-made/">this article about &#x201C;visionary computers of the past&#x201D;</a> gives you more interesting stuff to indulge and think about.</p><h2 id="3-ar-experience-in-a-sports-arena">3. AR experience in a sports arena</h2><p><a href="https://twitter.com/sergcio/status/1171071776350253062/video/1">https://twitter.com/sergcio/status/1171071776350253062/video/1</a></p><p>While the experience is enormously cool (in my book), it raises the question of how &#x201C;real&#x201D; our memories or the &#x201C;memories&#x201D; we share will actually be in the future.</p><h2 id="4-you-need-data-to-build-data-driven-products-and-that-s-the-hard-part">4. You need data to build data driven products, and that&#x2019;s the hard part</h2><p>The article <a href="https://medium.com/@gokulrajaram/the-death-of-a-startup-10738255d1e0">The death of a startup</a> gives an insight about the best ideas not surviving reality because of a small, but existential flaw.</p><blockquote>&#x201C;Without training data, there was no ML model. There was no AI. Without the model, there was no solution. Without the solution, no company.&#x201D;</blockquote><p>It is short, but gives you again a quick summary of how to plan a product or &#x201C;what to do instead&#x201D;. Most valuable sentence is probably this, at least if you want or need your idea / product / company to earn money:</p><blockquote>&#x201C;Deliver value versus trying to push through your vision of a perfect world. Live to fight another day.&#x201D;</blockquote><h2 id="5-object-oriented-ux">5. Object Oriented UX</h2><p>In conjunction with the tweet linked above goes <a href="https://alistapart.com/article/object-oriented-ux/">this article about Object Oriented UX</a>. It is from 2015, but I find it not the less valid.</p><blockquote>&#x201C;That&#x2019;s OOUX: putting object design before procedural action design, and thinking about a system through the lens of the real-world objects in a user&#x2019;s mental model (products, tutorials, locations), not digital-world actions (search, filter, compare, check out). We determine the actions after first defining the objects, as opposed to the traditional actions-first process that jumps straight into flows, interactions, and features.&#x201D;</blockquote><p>This approach is something that might be adapted more easily by &#x201C;technical people&#x201D; than by &#x201C;visual people&#x201D;, but I think it&#x2019;s worth to at least try to view the thing you&#x2019;re trying to build through this lens. It will most likely also reduce friction when it goes into implementation, as missing things are uncovered early and a way of organizing internal data can be easily derived from this.</p><h2 id="6-e-commerce-sites-lack-the-right-filters-and-users-don-t-like-that">6. e-commerce sites lack (the right) filters, and users don&#x2019;t like that</h2><p>Filters on e-commerce sites help users narrow down the abundance of choice and find what they&#x2019;re actually looking for. Key is to always have the attributes that are shown on the product available as filters, too &#x2013; as this is what differentiates the products. The lack of those often causes frustration and abandonment, as <a href="https://baymard.com/blog/have-filters-for-list-item-info">this article of Baymard Institute</a> shows &#x2013; up to 38%.</p><p>Linked from this page is the <a href="https://baymard.com/ux-benchmark">UX Benchmark</a>, which is almost worth an entry of its own.</p><h2 id="7-mixed">7. Mixed</h2><ul><li><a href="https://codepen.io/oliviale/full/GRKQoKM">CSS Grid: Magazine Layout</a></li><li><a href="https://domenicobrz.github.io/webgl/projects/blurry-web-spider/">Blurry Web Spiders (WebGL)</a></li></ul><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Exercise: I&apos;m distilling good <a href="https://twitter.com/hashtag/design?src=hash&amp;ref_src=twsrc%5Etfw">#design</a> (verb) into 4 questions.<br><br>1) What problem are you solving?<br>2) Who are you solving it for?<br>3) How will you verify you solved #1 for #2?<br>4) How will you protect against unexpected negative consequences? <br><br>If u don&apos;t like these, what are your 4?</p>&#x2014; Scott Berkun (@berkun) <a href="https://twitter.com/berkun/status/1169372594660728833?ref_src=twsrc%5Etfw">September 4, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><h2 id="misc">Misc</h2><p>Also, I have stumbled across Svelte, and I really like their concepts <a href="https://svelte.dev/blog/svelte-3-rethinking-reactivity">explained in this article / video &#x201C;Rethinking Reactivity&#x201D;</a>. I hope I&#x2019;ll find the time to dip my toes into it.</p>]]></content:encoded></item><item><title><![CDATA[Ulysses and Ghost]]></title><description><![CDATA[<p>The new Ulysses feature really adds so much value to my &#x201C;ideal blogging setup&#x201D; that I hope to actually start writing more again.</p><p>A few years ago, I got started with editing in Markdown or a flavour thereof. Once you go there, it&#x2019;s hard getting back</p>]]></description><link>https://svn.matthiashaak.com/ulysses-and-ghost/</link><guid isPermaLink="false">5cf4275ad194522848a8d35f</guid><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Sun, 02 Jun 2019 19:57:04 GMT</pubDate><media:content url="https://svn.matthiashaak.com/content/images/2019/06/DSCF7517-1.63df5affd68744cd9451af150f868e9c.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://svn.matthiashaak.com/content/images/2019/06/DSCF7517-1.63df5affd68744cd9451af150f868e9c.jpg" alt="Ulysses and Ghost"><p>The new Ulysses feature really adds so much value to my &#x201C;ideal blogging setup&#x201D; that I hope to actually start writing more again.</p><p>A few years ago, I got started with editing in Markdown or a flavour thereof. Once you go there, it&#x2019;s hard getting back to anything else. I still have a problem with writing Word Documents. Markdown is not perfect, but it helps me a lot writing structured documents and being quite fast in that. A little sideway to <a href="http://docutils.sourceforge.net/rst.html">reStructuredText</a> (<em>IT! IS! ONE! WORD!</em>) ended in tears, and I will hate making tables forever.</p><p>I&#x2019;m using <a href="https://ulysses.app">Ulysses</a>, although I have also tried iA Writer and found this not too bad. But as I&#x2019;ve had a beer or two with some of the good people behind Ulysses and I really like their attitude towards their product, and their general work ethics, I shifted and (almost) never looked back.</p><p>In the beginning I struggled with the application contained library (I like my files), and I wanted Dropbox support, as I did not trust Apple with anything that has to do with networks, leave alone syncing. I still find it a little odd to have links being represented in non-clear text, and images to be inline, but I do appreciate the beauty of it &#x2013; and I constantly confuse that syntax anyway.</p><p>Around the same time, I discovered <a href="https://ghost.org">Ghost</a> as a beautiful and very promising blogging solution, opposed to the then-already starting to bloat Wordpress. It looked minimalistic, yet thought through. There was also some promise in the admin panel that I haven&#x2019;t seen yet but hey: aim high.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://svn.matthiashaak.com/content/images/2019/06/DSCF7517-1.jpg" class="kg-image" alt="Ulysses and Ghost" loading="lazy" title="Test Image"><figcaption>This is just a test image that I&apos;ve put into my Ulysses article</figcaption></figure><p>I&#x2019;ve installed Ghost in version 0.3.1 as a test, and loved it. Although I rarely had time to write &#x2013; or something to write about, but I duly installed the updates and saw what changed. I even had a Medium theme installed once&#x2026; The writing experience was straightforward but beautiful. I&#x2019;m still not sure how I feel about the blocks, but again: it makes life so much easier for non-technical users. And those who are can always add functionality.</p><p>Ghost lacked an API for a long time, and probably for a good reason &#x2013; and I still ended up copying and pasting things from Ulysses, if I couldn&#x2019;t export them as PDFs or needed them in the web. Not so much in this publication, as the posting frequency is rather low.</p><p>But yet, all of the time, I&#x2019;ve had the &#x201C;ideal setup&#x201D; in mind. A clean web interface, text based articles formatted in markdown, and something that enables me to easily post &#x201C;on the go&#x201D; without logging in, uploading images etc pp. But also not a native app of the publishing platform, as these tend to suck at some point.</p><p>When I read the news about Ulysses integrating with Ghost, it seemed like I finally found what I was looking for. Now it&#x2019;s time to take this for a spin, and produce the first content with it. I&#x2019;m looking forward for more to come, and this time more frequently than yearly.</p>]]></content:encoded></item><item><title><![CDATA[Ghost has responsive images]]></title><description><![CDATA[<p>I&apos;ve tried Ghost almost as soon as it came out. I liked the simplicity and writing in Markdown. Things changed, and you could see the inspiration from other publishing platforms. However, one thing was always annoying me: images.</p><p>When uploading an image to ghost, it would be sent</p>]]></description><link>https://svn.matthiashaak.com/ghost-has-responsive-images/</link><guid isPermaLink="false">5b8f0d0816c7370e62528071</guid><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Thu, 06 Sep 2018 20:05:09 GMT</pubDate><media:content url="https://svn.matthiashaak.com/content/images/2018/09/DSCF4369.JPG" medium="image"/><content:encoded><![CDATA[<img src="https://svn.matthiashaak.com/content/images/2018/09/DSCF4369.JPG" alt="Ghost has responsive images"><p>I&apos;ve tried Ghost almost as soon as it came out. I liked the simplicity and writing in Markdown. Things changed, and you could see the inspiration from other publishing platforms. However, one thing was always annoying me: images.</p><p>When uploading an image to ghost, it would be sent the same way as it was uploaded. This means that images straight from the camera would be delivered to the reader in all its Megapixel beauty. For &quot;mature CMS&quot; or publishing platforms there would be a way to resize them for delivery, or at least compress them. This almost always required using GDlib or imagemagick and maintaining an additional component. Or your own asset server. </p><p>For Ghost there was no such option, and for a long time also no API to plug into for this. <a href="https://twitter.com/ShahakShapira/status/1036614765743927297">This GitHub issue</a> speaks for itself and is a good documentation for the situation.</p><p>Things have changed, and with the release of Ghost 2.1 images are being resized. Finally! This was what was missing, and I hope the team will put this forward. I&apos;ll insert some images to demonstrate this.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://svn.matthiashaak.com/content/images/2018/09/DSCF0918.jpg" class="kg-image" alt="Ghost has responsive images" loading="lazy"><figcaption>Just a sample image (Umm Suqueim / 1st Al Khail road)</figcaption></figure><p>Additionally, a &quot;Gallery&quot; block has been introduced, so let&apos;s see how useful this is.</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://svn.matthiashaak.com/content/images/2018/09/DSCE4937.jpg" width="2000" height="1125" loading="lazy" alt="Ghost has responsive images" srcset="https://svn.matthiashaak.com/content/images/size/w600/2018/09/DSCE4937.jpg 600w, https://svn.matthiashaak.com/content/images/size/w1000/2018/09/DSCE4937.jpg 1000w, https://svn.matthiashaak.com/content/images/size/w1600/2018/09/DSCE4937.jpg 1600w, https://svn.matthiashaak.com/content/images/size/w2400/2018/09/DSCE4937.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://svn.matthiashaak.com/content/images/2018/09/DSCF0127.jpg" width="2000" height="1125" loading="lazy" alt="Ghost has responsive images" srcset="https://svn.matthiashaak.com/content/images/size/w600/2018/09/DSCF0127.jpg 600w, https://svn.matthiashaak.com/content/images/size/w1000/2018/09/DSCF0127.jpg 1000w, https://svn.matthiashaak.com/content/images/size/w1600/2018/09/DSCF0127.jpg 1600w, https://svn.matthiashaak.com/content/images/size/w2400/2018/09/DSCF0127.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://svn.matthiashaak.com/content/images/2018/09/DSCF3045.jpg" width="2000" height="1125" loading="lazy" alt="Ghost has responsive images" srcset="https://svn.matthiashaak.com/content/images/size/w600/2018/09/DSCF3045.jpg 600w, https://svn.matthiashaak.com/content/images/size/w1000/2018/09/DSCF3045.jpg 1000w, https://svn.matthiashaak.com/content/images/size/w1600/2018/09/DSCF3045.jpg 1600w, https://svn.matthiashaak.com/content/images/size/w2400/2018/09/DSCF3045.jpg 2400w" sizes="(min-width: 720px) 720px"></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://svn.matthiashaak.com/content/images/2018/09/DSCF3056.jpg" width="2000" height="1333" loading="lazy" alt="Ghost has responsive images" srcset="https://svn.matthiashaak.com/content/images/size/w600/2018/09/DSCF3056.jpg 600w, https://svn.matthiashaak.com/content/images/size/w1000/2018/09/DSCF3056.jpg 1000w, https://svn.matthiashaak.com/content/images/size/w1600/2018/09/DSCF3056.jpg 1600w, https://svn.matthiashaak.com/content/images/size/w2400/2018/09/DSCF3056.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://svn.matthiashaak.com/content/images/2018/09/DSCF3079.jpg" width="2000" height="1334" loading="lazy" alt="Ghost has responsive images" srcset="https://svn.matthiashaak.com/content/images/size/w600/2018/09/DSCF3079.jpg 600w, https://svn.matthiashaak.com/content/images/size/w1000/2018/09/DSCF3079.jpg 1000w, https://svn.matthiashaak.com/content/images/size/w1600/2018/09/DSCF3079.jpg 1600w, https://svn.matthiashaak.com/content/images/size/w2400/2018/09/DSCF3079.jpg 2400w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>A Gallery. Without individual captions.</figcaption></figure><p>This is a promising start. I don&apos;t know if there is a way to sort images, but one of the biggest needs has been addressed and I think Ghost is even more usable right now. </p><p>The images look quite compressed when viewed 1:1, but are astonishingly small in size. The gallery images cannot be clicked and zoomed, but that&apos;s more a theme thing. This page weighs 2.1MB, which is half of the size that any of these images had before the upload. There are no alt attributes possible.</p><!--kg-card-begin: markdown--><p>However, when you insert a markdown block, you can still insert images &quot;the old way&quot;. Which means, you could actually use an alt attribute.</p>
<p><img src="https://svn.matthiashaak.com/content/images/2018/09/DSCF9636-1.jpg" alt="Ghost has responsive images" loading="lazy"></p>
<p>The image will be treated the same way (longest side 2000px, hard compressed). I have uploaded the image above with an original size of almost 6MB.</p>
<p>So, happy publishing images!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[it's time]]></title><description><![CDATA[From all the dimensions, time is the one we tend to overrate the most because we tend to believe we cannot move in it as we please.]]></description><link>https://svn.matthiashaak.com/its-time/</link><guid isPermaLink="false">5b15aaa08296ab640df412fd</guid><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Mon, 04 Jun 2018 21:17:02 GMT</pubDate><media:content url="https://svn.matthiashaak.com/content/images/2018/06/DSCF9251-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://svn.matthiashaak.com/content/images/2018/06/DSCF9251-1.jpg" alt="it&apos;s time"><p>A time to rethink. To recreate, to re-create. A time to rebuild. Or build new.</p>
<blockquote>
<p>The time is now<br>
<em>(Moloko)</em></p>
</blockquote>
<p>No worries, that is just a test post with the new theme. However, exciting things ahead. Also, almost a year has passed and it&apos;s time to publish another article, in the true tradition of this here publication.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Payment Gateways in the UAE]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I have recently come across the necessity to implement a payment system to an existing web site. The easiest way would probably be to use <a href="https://stripe.com/">Stripe</a>, but unfortunately this is not available here in the UAE.</p>
<p>However, there are a lot of alternatives. I have compiled a small overview over</p>]]></description><link>https://svn.matthiashaak.com/payment-gateways-in-the-uae/</link><guid isPermaLink="false">5b05b7fa8296ab640df412e4</guid><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Sat, 01 Apr 2017 14:28:33 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>I have recently come across the necessity to implement a payment system to an existing web site. The easiest way would probably be to use <a href="https://stripe.com/">Stripe</a>, but unfortunately this is not available here in the UAE.</p>
<p>However, there are a lot of alternatives. I have compiled a small overview over the current ecosystem. The largest payment provider <a href="http://www.network.ae">Network</a> is not listed, as they do not have any prices on their web site and also rather cater to the larger businesses.</p>
<table>
<tr>
<th>Provider</th><th>	Setup Fee</th><th>	Monthly Fee	</th><th>Transaction Fee</th>
</tr>
<tr>
<td><a href="http://www.payfort.com/">Payfort</a> </td><td>	0</td><td>	0</td><td>	AED 1.00 + 3.00&#xA0;%</td>
</tr>
<tr>
<td>
<a href="https://www.ccavenue.ae/">CCAvenue</a></td><td>	0</td><td>AED 200</td><td>	AED 1.00 + 3.00&#xA0;%</td>
</tr>
<tr>
<td><a href="https://www.checkout.com/">checkout.com</a></td><td>	0</td><td>	0</td><td>AED 0.75 + 2.75%</td></tr>
<tr>
<td>
<a href="https://telr.com/">Telr</a></td><td>0</td><td>AED 349</td><td>	0</td></tr>
<tr>
<td>
<a href="https://www.paypal.com">PayPal</a></td><td>	0</td><td>	0</td><td>USD 0.30 + 2.90% 	</td>
</tr>
</table>
<p>This list is by no means exhaustive, just the ones I found with a bit of research.</p>
<p>I have always considered the smallest available plan. These are suitable for smaller transaction volumes (up to 20kAED / month), with larger volumes there are different plans available.</p>
<p>All of these bring their own SDK or integration solution, however the quality and the amount of plugins might vary. In general it is safe to say that every provider offers a &quot;web checkout&quot; solution as developers are used from PayPal for years, and a WordPress plugin. The next step of evaluation is to have a glimpse into their code and try to set up an example flow for a single checkout and a monthly subscription model.</p>
<p>A very interesting link I found for this topic is <a href="https://blog.jadopado.com/the-2016-edition-online-payments-in-the-uae/">https://blog.jadopado.com/the-2016-edition-online-payments-in-the-uae/</a>. Please do make an exception and read the comments, as they are insightful!</p>
<p><strong>UPDATE:</strong> As jadopado got acquired by noon, the mentioned post can now be found here: <a href="https://blog.esanjo.com/the-2016-edition-online-payments-in-the-uae-76aeccbdc922">https://blog.esanjo.com/the-2016-edition-online-payments-in-the-uae-76aeccbdc922</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[HTTP2 again!]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>So Google <a href="https://developers.google.com/web/updates/2016/04/chrome-51-deprecations?hl=en">removed the support for NPN from Chrome and only supports ALPN now</a>. ALPN is only available in OpenSSL 1.0.2 and above. At this time, only Ubuntu 16.04 ships with this version.</p>
<p>I&apos;m running Ubuntu 14.04.</p>
<p>And so, as for a lot of</p>]]></description><link>https://svn.matthiashaak.com/http2-again/</link><guid isPermaLink="false">5b05b7fa8296ab640df412e3</guid><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Mon, 18 Jul 2016 18:29:35 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>So Google <a href="https://developers.google.com/web/updates/2016/04/chrome-51-deprecations?hl=en">removed the support for NPN from Chrome and only supports ALPN now</a>. ALPN is only available in OpenSSL 1.0.2 and above. At this time, only Ubuntu 16.04 ships with this version.</p>
<p>I&apos;m running Ubuntu 14.04.</p>
<p>And so, as for a lot of other people, all of my websites were back to HTTP/1.1. Goodbye tomorrow. Luckily, there exists a version of nginx that is already compiled against OpenSSL 1.0.2. It is available <a href="https://launchpad.net/~fxr/+archive/ubuntu/nginx-alpn">here (stable branch)</a>. After adding this repository and an <code>apt-get update</code> i was able to install nginx. On one of my servers.</p>
<p>On another one (this one), I couldn&apos;t manage to update OpenSSL. After a while it turned out, that the main difference on this server is, that, in opposite to the other one, I don&apos;t have the <a href="https://launchpad.net/~ondrej/+archive/ubuntu/php">ondrej PHP-repository</a> in my list. After adding it, OpenSSL upgraded flawlessly, and so did nginx.</p>
<p>Finally! But instead of seeing my site being delivered in HTTP2, I was greeted with a friendly <code>ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY</code> from Chrome.</p>
<p>I searched around and a little help of The Internet revealed that my <code>ssl_ciphers</code> were using obsolete algorithms. So I fixed the line to<br>
<code>ssl_ciphers &apos;ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:AES256+EDH&apos;; </code><br>
and voila, there it is again.</p>
<p>In brief:<br>
<code>add-apt-repository ppa:fxr/nginx-alpn add-apt-repository ppa:ondrej/php apt-get update apt-get upgrade apt-get install nginx</code></p>
<p>Be careful, this can have a lot of side effects. It might replace your PHP version (if you want to use PHP7 on Ubuntu 14.04 you should add this repository anyway). Your mileage may vary. Don&apos;t say I didn&apos;t warn you.</p>
<p>Good luck!</p>
<p><strong>Update:</strong> I found <a href="https://gist.github.com/mbejda/a1dabc45b32aaf8b25ae5e8d05923518">this gist</a>, you might have less side effects if you take this ppa instead of <code>ondrej/php</code> to upgrade your OpenSSL.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[DigitalOcean introduces block storage]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I am a happy DigitalOcean customer. Almost all my stuff is running there now, I just have one <a href="https://www.linode.com/?r=79c83184de253595f66967b5e7796353fe0a29c2">Linode</a> machine for comparison reasons. This blog is running on the smallest DigitalOcean droplet that is available. I&apos;m using nginx proxy_cache for this, so a lot of load is</p>]]></description><link>https://svn.matthiashaak.com/digitalocean-introduces-block-storage/</link><guid isPermaLink="false">5b05b7fa8296ab640df412e2</guid><category><![CDATA[hosting]]></category><category><![CDATA[news]]></category><category><![CDATA[storage]]></category><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Wed, 13 Jul 2016 20:30:07 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>I am a happy DigitalOcean customer. Almost all my stuff is running there now, I just have one <a href="https://www.linode.com/?r=79c83184de253595f66967b5e7796353fe0a29c2">Linode</a> machine for comparison reasons. This blog is running on the smallest DigitalOcean droplet that is available. I&apos;m using nginx proxy_cache for this, so a lot of load is taken from database and the node process.</p>
<p>For small websites with low to average traffic and a performant setup you can get away with ridiculously low costs. There was just one thing, that was slightly bothering me: if you had a lot of media, for example videos or audio files, you are quickly at the end of the included storage. I have a project with a very limited set of users and a low load, but it is very dependent on a lot of video and high-res photo assets &#x2013; as this is the nature of this project.</p>
<p>My headache is at an end, the issue has finally been solved: <a href="https://www.digitalocean.com/company/blog/block-storage-more-space-to-scale/?refcode=94e4bbc5842d&amp;utm_campaign=Referral_Invite&amp;utm_medium=Referral_Program">Digital Ocean introduces Block Storage</a>.</p>
<p>DO&apos;s Block storage is SSD based and &#x2013; from what I read &#x2013; just another volume that you can mount anywhere. The only thing I still wonder is whether you could use a volume as a common asset storage for a number of nodes. I suspect it won&apos;t work out of the box, but there might be solutions for this soon. It&apos;s not an uncommon use. Sadly, as it&apos;s not available in my region as of now, I couldn&apos;t try it yet.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Tilerator]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>More than 6 years ago I wrote a small tool in Flash to solve a problem with Cocos2D sprites.<br>
For this framework, the tile maps had to have a little gap every so and so many pixels with the content continuing after the gap. So if you created a tile</p>]]></description><link>https://svn.matthiashaak.com/tilerator/</link><guid isPermaLink="false">5b05b7fa8296ab640df412e0</guid><category><![CDATA[coding]]></category><dc:creator><![CDATA[matze]]></dc:creator><pubDate>Tue, 05 Jul 2016 12:45:12 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>More than 6 years ago I wrote a small tool in Flash to solve a problem with Cocos2D sprites.<br>
For this framework, the tile maps had to have a little gap every so and so many pixels with the content continuing after the gap. So if you created a tile map, let&#x2019;s say in Photoshop, and exported it, you would still have to insert all the gaps horizontally and vertically.<br>
That is some cumbersome task, and I had no idea how to automate this in Photoshop, so I wrote this little tool. I called it tilerator.</p>
<p><img src="https://svn.matthiashaak.com/content/images/2016/07/tilerator1.jpg" alt="tilerator screenshot" loading="lazy"><br>
Screenshot of tilerator</p>
<p><img src="https://svn.matthiashaak.com/content/images/2016/07/tilerator2.jpg" alt="tilerator screenshot 2" loading="lazy"><br>
Screenshot of tilerator with a composition</p>
<p>It took me a night to make it and two days to fix the bugs. It did the job, and you could even create your own compositions. I had it available online for download for quite a while, and at least 2 people who I don&#x2019;t know used it. I have no stats, but I think there was some traffic.</p>
<p>Success. People had a tool.</p>
<p>At some point, moving from one server to another, I forgot to put it online again.  Flash was dead anyway, and so seemed to be AIR applications, and no one seemed to miss the tilerator. The sprite framework had a tool to do the job (so I reckon). I was busy with a lot of other things, and rarely thought about it again, except when I got reminded by Facebook, that there is a page for this and it had one new visitor.</p>
<p>When I stumbled across it the other day and thought about it, it occurred to me that I never even considered making a JavaScript version of this. And once you go JavaScript, why not create it as an npm module? In a way that you can even use it as command line tool, or part of a build chain?</p>
<p>As a first step I researched if there is anything like that. Somehow yes, but it had dependencies on either gdlib or ImageMagick. Ok, I thought, I can do without that. I dusted off the old ActionScript code, removed the cobwebs form MXML and it looked as if it could be converted almost effortless. The canvas element almost works the same way, and how hard can it be?</p>
<p>Well&#x2026; Canvas exists only in a browser context. There are modules that simulate it so you can use it also in a CLI environment (such as a server, or in a task of a task runner). These modules however are dependent on &#x2026; yes: gdlib or ImageMagick. So much for that. There had to be another way, and there is. You just take the image data and work on it. As simple as that! I found two libraries, one for JPEG and one for PNG, and once I get my head wrapped around finding an [x,y] index in a one-dimensional array (actually a Buffer), it was almost immediately finished.</p>
<p>So here it is: tilerator as an npm module. Have fun, play with it, fork it, make it better. I rather have it out in the open than collecting dust on my hard drive. Maybe it&#x2019;s useful to someone.</p>
<p><a href="https://www.npmjs.com/package/tilerator">tilerator on npmjs</a> |  <a href="https://github.com/gurkendoktor/tilerator">tilerator on github</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>