<?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"><channel><title><![CDATA[Food for Developers]]></title><description><![CDATA[Food for Developers]]></description><link>https://blog.activeno.de</link><generator>RSS for Node</generator><lastBuildDate>Mon, 13 Apr 2026 14:36:49 GMT</lastBuildDate><atom:link href="https://blog.activeno.de/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Supabase or Keycloak? A complete Guide]]></title><description><![CDATA[Author: David Lorenz (@activenode) is a Supabase Expert and Web Software Architect writer of the well-known Supabase book supa.guide. This article was originally published at Skycloak, a Keycloak Cloud provider.
Choosing the right tools for authentic...]]></description><link>https://blog.activeno.de/supabase-or-keycloak-a-complete-guide</link><guid isPermaLink="true">https://blog.activeno.de/supabase-or-keycloak-a-complete-guide</guid><category><![CDATA[supabase]]></category><category><![CDATA[keycloak]]></category><category><![CDATA[authorization]]></category><category><![CDATA[authentication]]></category><category><![CDATA[rbac]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Tue, 07 Jan 2025 11:05:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736249880408/bf0c2f57-3357-4ad0-abe0-b7d2393e0149.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Author: David Lorenz (@activenode) is a Supabase Expert and Web Software Architect writer of the well-known Supabase book <a target="_blank" href="http://supa.guide/">supa.guide</a>. This article was <a target="_blank" href="https://skycloak.io/blog/supabase-or-keycloak-a-complete-guide/">originally published</a> at <a target="_blank" href="https://skycloak.io/">Skycloak</a>, a Keycloak Cloud provider.</p>
<p>Choosing the right tools for authentication and authorization can make or break your web application’s security and user experience. In this article, we’ll explore the key differences between <strong>Supabase</strong> and <strong>Keycloak</strong>, two powerful platforms for identity management. You’ll discover their unique features, ideal use cases, and how they compare when securing modern applications. If you’re looking for a robust, enterprise-grade identity solution, <strong>Keycloak</strong> offers unparalleled flexibility and security.</p>
<p>With <a target="_blank" href="https://skycloak.io">https://skycloak.io</a> hosting and configuring Keycloak is as easy as snapping your fingers, so if you don’t want to run your own containers, just deploy a Keycloak instance with them, worry-free.</p>
<p>By the end of this article, you’ll know when to choose Supabase, Keycloak, or even how they can work together for a seamless authentication system.</p>
<p>What you’ll learn in this article:</p>
<ul>
<li><p>Understanding the crucial difference of Authentication vs. Authorization</p>
</li>
<li><p>What Supabase and Keycloak are</p>
</li>
<li><p>How Supabase authenticates and authorizes users</p>
</li>
<li><p>How to decide if you need Keycloak or if Supabase will be sufficient</p>
</li>
<li><p>How to connect Supabase with Keycloak</p>
</li>
<li><p>Realising why using Keycloak can be beneficial as your default Authorization and Authentication system</p>
</li>
<li><p>Alternative solutions to Supabase &amp; Keycloak</p>
</li>
<li><p>A final conclusion of the key points</p>
</li>
</ul>
<p>Let’s kick it off.</p>
<h1 id="heading-authentication-vs-authorization">Authentication vs. Authorization</h1>
<p>Authentication is the process of verifying a user's identity in a web application. For example, when you log into a website using your username and password, the system checks if these credentials are valid and match an existing user account - this is authentication.</p>
<p>Authorization, on the other hand, determines what actions a user is allowed to perform within the application. After you're logged in, the system checks what parts of the website you can access or what operations you can perform, such as viewing certain pages, editing content, or accessing admin features. This is authorization.</p>
<p>For example, if you visit the page <code>/admin/show-all-users</code>, the authorization system checks - with your authentication information - if this authenticated user is authorized to access this part of the application.</p>
<p>Understanding the distinction between these two concepts is crucial when designing secure web systems. While authentication verifies who the user is, authorization controls what that user can do within the application.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736246802087/b1b1c183-9371-4294-9853-6600f1ce6381.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-supabase">Supabase</h1>
<h2 id="heading-what-is-supabase">What is Supabase?</h2>
<p>Supabase is a popular Firebase alternative that has gained significant traction over the past two years. As of this writing, it boasts around 75,000 GitHub stars, placing it near the top 100 repositories.</p>
<p>At its core, Supabase is essentially a "superpowered" Postgres database wrapped in a carefully curated toolstack.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736246946489/4efc7957-bcf5-453f-a499-b107c140f671.png" alt class="image--center mx-auto" /></p>
<p>It's crucial to understand that Supabase isn't a non-standard version of Postgres. Rather, it's standard Postgres enhanced with a selection of excellent extensions and settings, surrounded by services that leverage the database.</p>
<p>For instance, the <code>Auth</code> service (previously called <code>GoTrue</code>) handles user authentication, while the Realtime service delivers user-specific real-time data, among many other features.</p>
<p>Supabase isn't a single entity—it's a comprehensive toolkit centered around the Postgres database. This suite of tools addresses common "Day One" challenges faced by most products, while also offering advanced features like logging and monitoring.</p>
<ul>
<li><p>Where to host the database?</p>
</li>
<li><p>How to implement authentication?</p>
</li>
<li><p>How to implement realtime data?</p>
</li>
<li><p>Where to store files?</p>
</li>
<li><p>How to implement scheduled tasks?</p>
</li>
<li><p>How to manager users?</p>
</li>
<li><p>Where to store secrets?</p>
</li>
<li><p>How to collect logs and reports?</p>
</li>
<li><p>How to get data from payment providers?</p>
</li>
<li><p>etc.</p>
</li>
</ul>
<p>Most importantly: All of those pieces, are open source and can be self-hosted. I personally love using the zero-effort variant by using their hosted <a target="_blank" href="http://supabase.com">supabase.com</a> service.</p>
<blockquote>
<p>For more than 90% of my new clients, I use Supabase, even for enterprise level clients. Simply because it just works, is fully open-source and can be self-hosted. Plus, it’s language-independent: Although many Supabase libraries exist, everything is callable via REST API as well.</p>
</blockquote>
<p>But now, let’s have a look at Supabase’s Authentication and Authorization.</p>
<h2 id="heading-how-does-supabase-authenticate">How does Supabase authenticate?</h2>
<p>Supabase uses the <a target="_blank" href="https://github.com/supabase/auth">Supabase Auth</a> system to authenticate users, formerly known as <code>GoTrue</code> which was initially <a target="_blank" href="https://github.com/netlify/gotrue">developed by Netlify</a> and massively improved and extended by Supabase.</p>
<p>In simple words, authentication works like this:</p>
<p>You create a user in the Supabase Dashboard or programmatically via one of the many Supabase libraries (e.g. for JavaScript, or Golang) by providing an E-Mail, e.g. <code>foo@snoo.bar</code></p>
<ol>
<li><p>This user is now saved in the Postgres database in a protected place, in the <code>auth</code> schema in the <code>auth.users</code> table</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247003353/058efb85-0ed3-464b-ab11-67550a6d51e6.png" alt class="image--center mx-auto" /></p>
<p> When a user then logs in (e.g. because you provided a login form and called the <code>supabase.signInWithPassword()</code> method, the user credentials are checked on the Supabase backend.</p>
</li>
<li><p>If the user credentials are valid, a signed session is returned and saved (a so-called JWT which contains also the information when the session expires) - you are <strong>authenticated</strong>.</p>
</li>
<li><p>When you now try to fetch something from the database e.g. with the Supabase JS library via <code>supabase.from('tableName').select('*').limit(1)</code>, it will verify on the server if the authentication is valid and trustworthy. If yes, it will execute the commands with the <strong>Authorization</strong> feature (see below) as the guardian.</p>
</li>
</ol>
<p>From a DX-perspective, the authentication is an absolute no-brainer as you literally only need one <a target="_blank" href="https://supabase.com/docs/reference/javascript/auth-signinwithidtoken">small code snippet</a> <a target="_blank" href="https://supabase.com/docs/reference/javascript/auth-signinwithidtoken">to sign someone in</a>. Also the creation of a user is just one line of code programmatically.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247066379/434a59a9-2448-4379-bc5a-f79ffc29c116.png" alt class="image--center mx-auto" /></p>
<p>But what about Google Login or Magic Login then? Let’s talk about that next.</p>
<h3 id="heading-authentication-methods-with-supabase">Authentication methods with Supabase</h3>
<p>Supabase Auth not only supports logging in via password but a variety of authentication methods such as <strong>Magic Link</strong>, generic One-Time passwords (OTP Login), Phone login and many 3d-party Identity providers such as logging in via:</p>
<ul>
<li><p>Generic Single Sign On (SSO) via <strong>SAML 2.0</strong> (<a target="_blank" href="https://www.youtube.com/watch?v=l-6QSEqDJPo&amp;t=30s">SAML 2.0 Explanation</a>) - which is a standard also supported by Keycloak (amongst many as you’ll see in the Keycloak section)</p>
</li>
<li><p>Sign in with Keycloak - a very crucial one</p>
</li>
<li><p>Sign in with Apple</p>
</li>
<li><p>Sign in with Google</p>
</li>
<li><p>Sign in with Azure</p>
</li>
<li><p>Sign in with Bitbucket</p>
</li>
<li><p>Sign in with Discord</p>
</li>
<li><p>Sign in with Facebook</p>
</li>
<li><p>Sign in with Figma</p>
</li>
<li><p>Sign in with GitHub</p>
</li>
<li><p>Sign in with GitLab</p>
</li>
<li><p>Sign in with Kakao</p>
</li>
<li><p>Sign in with LinkedIn</p>
</li>
<li><p>Sign in with Notion</p>
</li>
<li><p>Sign in with Twitch</p>
</li>
<li><p>Sign in with Twitter</p>
</li>
<li><p>Sign in with Slack</p>
</li>
<li><p>Sign in with Spotify</p>
</li>
<li><p>Sign in with WorkOS</p>
</li>
<li><p>Sign in with Zoom</p>
</li>
<li><p>More providers coming</p>
</li>
</ul>
<p>But how to use those within Supabase and does it interfere with existing users? Let’s take “<strong>Sign in with GitHub</strong>” for example and have a look:</p>
<p>To implement GitHub login with Supabase, you go into your GitHub account and create a new OAuth Application in your GitHub <a target="_blank" href="https://github.com/settings/developers">Developer Settings</a>.</p>
<p>There you’ll enter the Callback URL from Supabase (see image) with which GitHub will exchange authentication information.</p>
<p>Then, you’ll get a so-called <strong>Client ID</strong> and <strong>Client Secret</strong> from GitHub after submitting your GitHub application form which you fill in the Identity Provider information of Supabase:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247117463/522cc7d2-223a-4b8e-8e02-ca92e983a142.png" alt class="image--center mx-auto" /></p>
<p>That’s all. Now, you are able to trigger the signInWithOAuth({provider: 'github'}) which will forward users to the GitHub login page where they need to confirm that they want to sign in in your Supabase-powered application with their GitHub account.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247131581/af3060ff-42f7-4262-8762-c269fc6a0976.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-multiple-providers-same-email-supabase-has-automatic-linking">Multiple providers, same email? Supabase has automatic Linking</h3>
<p>A common question is: What happens if you add multiple login options to your applications and have a “Sign in with GitHub”, a “Sign in with Apple” and a standard magic Link (email-only) login?</p>
<p>If the user’s email is the same e.g. <code>ant@supabase.io</code>, and that email is confirmed to be verified by the provider, the identity information is <a target="_blank" href="https://supabase.com/docs/guides/auth/auth-identity-linking?queryGroups=language&amp;language=js#automatic-linking">all stored with the same user</a> - hence, it doesn’t matter with which provider they log in, they will always get access to their user data.</p>
<p>Pretty helpful. But what about different email identities, can they also be linked to one account? Yes, check the following section.</p>
<h3 id="heading-multiple-providers-different-email-but-same-user-supabase-has-manual-linking">Multiple providers, different email but same user? Supabase has manual linking</h3>
<p>If there is a user signed into your application, e.g. via GitHub login, but wants to use the Apple Login in the future, Supabase provides a <code>linkIdentity({ provider: 'PROVIDER_NAME' })</code> function to trigger a signed-in user to be redirected to the chosen provider e.g. <code>PROVIDER_NAME=apple</code> , confirm the sign-in process and be redirected to the application where then the apple identity is also stored and linked to the existing account.</p>
<p>This would then even allow to delete the older existing identity afterwards.</p>
<h2 id="heading-how-authorization-works-in-supabase">How Authorization works in Supabase</h2>
<p>We will now look at how we can give users specific access to specific resources in Supabase.</p>
<p>These two things sound contradictory but are not:</p>
<ol>
<li><p>You can control fine-grained access to data per user in Supabase</p>
</li>
<li><p>The user management UI of Supabase is pretty plain. You can change a users password, you can delete a user, create a new user, request a new password but there is no “Permission Administration” in the user management.</p>
</li>
</ol>
<p>So, how to do the first one, if there’s no such thing as a “Permission Administration”?</p>
<p>Let’s make a specific sample. Let’s take a specific user for that: <a target="_blank" href="mailto:ant@supabase.io"><code>ant@supabase.io</code></a> with the user id in Supabase being <code>005dfbcb-fb85-4955-9a58-a743350218e1</code> (a normal UUID).</p>
<p><strong>Authorization the “old” way</strong></p>
<p>When a user is authenticated in Supabase, you can confirm it by using the supabase client and calling <code>supabase.getUser()</code> (or manually doing so with the REST API if you prefer that).</p>
<p>Now imagine this Postgres table <code>todos</code>:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>todo_id (int8)</td><td>user_id (uuid)</td><td>todo_task (text)</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td><code>005dfbcb-fb85-4955-9a58-a743350218e1</code></td><td>Clean the living room</td></tr>
<tr>
<td>2</td><td><code>413a6486-0ff0-4be2-886a-d8798a48be9c</code></td><td>Wash the socks</td></tr>
<tr>
<td>3</td><td><code>005dfbcb-fb85-4955-9a58-a743350218e1</code></td><td>Make some indian curry</td></tr>
</tbody>
</table>
</div><p>If you have a backend in your application, you could now use it to retrieve only the tasks from that table where the <code>user_id</code> matches with the user id you got from the authenticated user</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> getUser()
<span class="hljs-keyword">const</span> current_user_id = user.data.id;

<span class="hljs-keyword">const</span> db = <span class="hljs-keyword">await</span> connectWithDb();
<span class="hljs-keyword">const</span> todos = <span class="hljs-keyword">await</span> db.fromTable(<span class="hljs-string">'todos'</span>).select([
  <span class="hljs-string">'todo_id'</span>,
  <span class="hljs-string">'todo_task'</span>
]).where({ <span class="hljs-attr">user_id</span>: current_user_id });
</code></pre>
<p>That’s one solution but it obviously can only run on the backend as you will need to connect with the database with your credentials.</p>
<p>Let’s now look at the solution that became popular with Supabase: Authentication-bound Row Level Security.</p>
<p><strong>Authorization with Row Level Security, Introduction</strong></p>
<p>Row Level Security (RLS) is not a Supabase invention, it’s again a Postgres standard. If RLS is enabled on a table it means no one but admin roles in the database will be able to read or write on that table by default.</p>
<p>If you’ve only worked with databases the old way, this approach might seem new to you since many people only use one <code>postgres</code> root user to connect to the database and not multiple different roles. So, let’s get into it deeper to understand how it works and why it’s wonderful.</p>
<p>Let’s stay on the raw database level at first. If you create a new Postgres user (=role) in the database, you could give that Postgres user read access to specific tables or even specific rows within a specific table (that’s why it’s called Row Level Security).</p>
<p>For example, if you created a Postgres user <code>foobar</code>, you could give that user access to only the second todo in the <code>todos</code> table with this SQL:</p>
<pre><code class="lang-jsx">ALTER TABLE todos ENABLE ROW LEVEL SECURITY;
CREATE POLICY <span class="hljs-string">"foobar can only access one row"</span>
ON todos FOR SELECT
TO foobar
USING ( todos.todo_id = <span class="hljs-number">2</span> );
</code></pre>
<p>Now, when you would connect with this <code>foobar</code> user to the Postgres database and that user would run a <code>SELECT * FROM todos</code> , all the database would return would be just one row.</p>
<p>But this is just the raw explanation of RLS. How can we bind it to a Supabase authenticated user? is every Supabase user its own Postgres user? No, not really, but there’s something that eludes this requirement. See next.</p>
<p><strong>Authorization with Row Level Security, using the</strong> <code>auth.*</code> functions of Supabase</p>
<p>We’ve covered 2 things:</p>
<ol>
<li><p>How to use the <code>user_id</code> to limit what we return - as a root user connected on the backend to the database. Which you can do in Supabase because it’s just Postgres.</p>
</li>
<li><p>What RLS is in general: a way to restrict row access to Postgres users.</p>
</li>
</ol>
<p>What we now want to do is:</p>
<ol>
<li>Using RLS to bind it to a Supabase user without even the need to connect directly to the database and be able to safely use the connection both on frontend and backend without exposing secret data. Sounds exciting, right?</li>
</ol>
<p>Let’s go through it step by step. The Supabase client, or more generically, the Supabase API, allows to request data from Postgres by constructing commands (similiar to what an ORM does).</p>
<p>In JavaScript, you can request data from the <code>todos</code> table via Supabase like this:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> supabase = createClient(SUPABASE_URL, SAFE_SUPABASE_ANON_KEY);
<span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> supabase.from(<span class="hljs-string">'todos'</span>).select();
<span class="hljs-keyword">const</span> todoData = data; <span class="hljs-comment">// just to be very explicit here</span>
</code></pre>
<p>But will this contain all todo entries? Even if we call this in the frontend? Can we even call this on the frontend? Yes, we can call this on the frontend as the <code>anon key</code> of Supabase is safe to expose (as opposed to the Supabase <code>service_role</code> key which is not safe to expose as it has admin rights).</p>
<p>But what will it return? By default, tables created in Supabase have RLS enabled. Hence, this request would go to the database and, if no <code>POLICY</code> is defined on that table, simply return everything that the existing policies allow: nothing, an empty array, 0 rows. So, it’s safe by default.</p>
<p>But what happens in the background? How would it know if a user has access or not?</p>
<p>When the request is sent to the API (which is the PostgREST library), it will take an existing session, verify it and then execute the SQL statement either as <code>anon</code> role in the database (if no valid authentication is available) or as <code>authenticated</code> role. So it will definitely execute that SQL statement in the database but those 2 roles don’t have admin privileges and hence, with activated RLS and no defined policy, nothing is returned.</p>
<p>This opens 2 questions:</p>
<ol>
<li><p>Why would I want to give the <code>anon</code> role (so no authentication) give access to data?</p>
</li>
<li><p>If every user has the same role, how can I prevent that one user can access data from another user?</p>
</li>
</ol>
<p>The first question is rather straightforward to answer: Imagine a news page. You want non-logged in users to be able to read entries from the <code>top_news</code> table. This would be easy to achieve in Supabase by simply executing this SQL:</p>
<pre><code class="lang-jsx">ALTER TABLE top_news ENABLE ROW LEVEL SECURITY;

CREATE POLICY <span class="hljs-string">"everyone can read the news"</span>
ON top_news FOR SELECT
TO anon
USING ( <span class="hljs-literal">true</span> );
</code></pre>
<p>This however specifically targets the non-authenticated users but you literally want <em>everyone</em> to be able to read so you can simplify it with this statement:</p>
<pre><code class="lang-jsx">ALTER TABLE top_news ENABLE ROW LEVEL SECURITY;

CREATE POLICY <span class="hljs-string">"everyone can read the news"</span>
ON top_news FOR SELECT
USING ( <span class="hljs-literal">true</span> );
</code></pre>
<p>Now, let’s jump to the second question. We cannot simply do something like this as it would allow all authenticated users to access all <code>todos</code> entries:</p>
<pre><code class="lang-jsx">CREATE POLICY <span class="hljs-string">"authenticated users can read todos"</span>
ON todos FOR SELECT
TO authenticated
USING ( <span class="hljs-literal">true</span> );
</code></pre>
<p>But, within the database there is a helper function to make it more specific. It’s called <code>auth.uid()</code> and it is <em>always</em> the id of the currently authenticated user (or <code>null</code> if not authenticated). That means, we can do this:</p>
<pre><code class="lang-jsx">CREATE POLICY <span class="hljs-string">"authenticated users can read their own todos"</span>
ON todos FOR SELECT
TO authenticated
USING ( todos.user_id = auth.uid() );
</code></pre>
<p>Now, when a user is logged in and <code>const { data } = await supabase.from('todos').select();</code> is executed, data will really only contain user-bound data - authorization solved.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247227939/02d40d4a-b995-443f-b099-c824dc0e2337.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-keycloak">Keycloak</h1>
<h2 id="heading-what-is-keycloak-and-why-is-it-so-popular">What is Keycloak and why is it so popular?</h2>
<p>Keycloak is an open-source Identity and Access Management solution that solves both <strong>Authentication</strong> and <strong>Authorization</strong> for applications. It's highly regarded for being enterprise-ready, open-source, and supporting all well-known standards, making it the go-to solution for companies needing a robust Auth system.</p>
<p>Keycloak offers a centralized system for user management, single sign-on (SSO), and identity brokering. This makes it particularly attractive for organizations grappling with complex identity management needs. Its flexibility and extensive integration capabilities have fueled its widespread adoption in enterprise environments.</p>
<p>But does that mean it should only be used for enterprise projects? No, but it is a good choice for enterprise projects.</p>
<p>But let’s have a look at how Keycloak solves authentication and what types of authentication it supports.</p>
<h2 id="heading-how-does-keycloak-authenticate-and-what-authentication-methods-does-it-support">How does Keycloak authenticate and what authentication methods does it support?</h2>
<p>Keycloak is your authentication manager. You can manage users and user permissions directly in Keycloak without the need of any 3d-party identity provider - however, Keycloak does support identification via every 3d-party provider.</p>
<p>Same as Supabase, Keycloak allows you to create users and login with those users directly with Keycloak.</p>
<p>Keycloak supports various authentication methods, including:</p>
<ul>
<li><p>Username and password authentication</p>
</li>
<li><p>Two-factor authentication (2FA)</p>
</li>
<li><p>Social login (e.g., Google, Facebook, Twitter)</p>
</li>
<li><p>SAML 2.0</p>
</li>
<li><p>OpenID Connect</p>
</li>
</ul>
<p>The connection between your application and Keycloak will always either be done via SAML or OpenID connect. But don’t worry, you don’t need deep knowledge about these standards to use them.</p>
<p>When a user attempts to log in, Keycloak verifies their credentials against its user database or the configured identity provider. Upon successful authentication, Keycloak issues tokens (such as JWT) and responds back to your application. So it’s all very similiar to how Supabase solves authentication - so far, so clear.</p>
<h3 id="heading-authenticating-with-pure-keycloak">Authenticating with pure Keycloak</h3>
<p>Let’s make a concrete sample: We have an application, e.g. a Laravel PHP app or a Next.js app called “my-app” and we want to log in with a user via Keycloak. How would that work?</p>
<ol>
<li><p>You need to create a so-called “Client” in Keycloak; the client is the bridge to authenticate with your application. This is easy: In Keycloak, click on “Clients” and then an “Create Client” and give it a client id, e.g. <code>my-app</code></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247262115/3a06188c-6ec6-4971-8898-9c4705d7bb84.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>You need to have a user created to even be able to log in at all, so head to the Users section and click on “Create new user”</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247290471/f5ea5cb5-ccf9-4883-867f-33da5f18a5dd.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>In this user creation form, the only required field is “username”. This is interesting because the username doesn’t have to be an email, it can be any alphanumeric name. Also, it doesn’t ask for a password at all, so how will the user be able to log in? We will solve this question soon</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247304680/2067e87d-fbb7-4260-a514-8abcd03ac81f.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>After you click create, you’ll see the created user with a lot of options attached to it - this is already giving an impression of the power of Keycloak.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247360613/3a936016-3160-4e5a-b156-562347682804.png" alt class="image--center mx-auto" /></p>
<p> Before doing anything else, we need a way for maria to authenticate. We’ll use a password to be able to log in. You can just go to the <strong>Credentials</strong> tab in Keycloak and then set a password for that user. But even more so, you can also require that Keycloak prompts the user, after using that password, to set a new password - with the <strong>Temporary</strong> checkbox. Neat, right</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247495697/13e1023d-447e-4b37-b348-30795cadcf49.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Now, we want <code>maria</code> to be able to authenticate in our application with that Keycloak user. How do we do achieve that? Without noticing, we created an <strong>OpenID Connect</strong> (look at the screenshot from point 1) standard client in Keycloak in step 1, named <code>my-app</code>. We now need to do two things:</p>
<ol>
<li><p>Make our application (let’s use a JavaScript sample) request authentication from Keycloak and make Keycloak return a valid session for a valid login to our application.</p>
</li>
<li><p>Tell Keycloak that our application is actually <em>allowed</em> to request authentication that (by default no application can access anything from Keycloak)</p>
</li>
</ol>
</li>
</ol>
<p>    OpenID connect is a standard and Keycloak exposes the required information to connect via this standard at this URL in JSON format: <code>your-keycloak-url/realms/master/.well-known/openid-configuration</code> ; or, in my specific case on Skycloak the URL is <a target="_blank" href="https://skycloak-id.app.skycloak.io/realms/test-realm/.well-known/openid-configuration">https://skycloak-id.app.skycloak.io/realms/test-realm/.well-known/openid-configuration</a> . You don’t have to read those values manually but it’s something nice to look at (these are not secret values very obviously but the client needs those to connect).</p>
<p>    If you ask yourself why I have <code>test-realm</code> in my URL instead of <code>master</code>: Keycloak allows to use multiple separate contexts called <strong>Realms</strong>. E.g. you can manage 5 completely separate applications with 5 different realms. Another Keycloak advantage, think of it as “Multiple Keycloaks in Keycloak”.</p>
<p>    But let’s get back to how we can request an authentication with our JavaScript application. For this, you simply need to use and configure the Keycloak library - so, in our case, the <a target="_blank" href="https://www.keycloak.org/securing-apps/javascript-adapter">JS library</a> <code>keycloak-js</code>.</p>
<ol start="7">
<li><p>Instantiate the Keycloak instance like so:Instantiate the Keycloak instance like so:</p>
<pre><code class="lang-jsx"> <span class="hljs-keyword">import</span> { Keycloak } <span class="hljs-keyword">from</span> <span class="hljs-string">'keycloak-js'</span>;
 <span class="hljs-keyword">const</span> kc = <span class="hljs-keyword">new</span> Keycloak({
   <span class="hljs-attr">url</span>: <span class="hljs-string">'&lt;https://my-skycloak-id-xabcy.app.skycloak.io&gt;'</span>,
   <span class="hljs-attr">clientId</span>: <span class="hljs-string">'my-app'</span>,
   <span class="hljs-attr">realm</span>: <span class="hljs-string">'test-realm'</span> <span class="hljs-comment">// "master" by default</span>
 });
</code></pre>
<p> Now, nothing happens yet. We need to tell the library to check and require an authentication. E.g. like so:</p>
<pre><code class="lang-jsx"> <span class="hljs-keyword">const</span> keycloak = <span class="hljs-keyword">new</span> Keycloak({
   <span class="hljs-attr">realm</span>: <span class="hljs-string">'test-realm'</span>,
   <span class="hljs-attr">url</span>: <span class="hljs-string">'&lt;https://5b77dccb-0080-4629-a47a-ab5b18a50a4c.app.skycloak.io&gt;'</span>,
   <span class="hljs-attr">clientId</span>: <span class="hljs-string">'my-app'</span>
 });

 <span class="hljs-built_in">window</span>.onload = <span class="hljs-function">() =&gt;</span> {
   keycloak.init({ <span class="hljs-attr">onLoad</span>: <span class="hljs-string">'login-required'</span> });
 }
 <span class="hljs-comment">//....</span>
</code></pre>
<p> My app runs on <a target="_blank" href="http://localhost:3000"><code>localhost:3000</code></a> and will then, on page load, trigger a redirect to authenticate to Keycloak and show an error like: <strong>“Invalid parameter: redirect_uri”</strong>. This is because we haven’t configured <code>localhost:3000</code> (our app) to be legitimated to authenticate with our Keycloak. Let’s change that.</p>
</li>
<li><p>We can configure the allowed redirect URIs by going to our my-app client in Keycloak and adding our app’s url like so: <a target="_blank" href="http://localhost:3000/*">http://localhost:3000/* (so any URL on</a> <a target="_blank" href="http://localhost:3000">localhost:3000</a> shall be legitimated to request authentication from Keycloak)</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247575100/2f45e3c9-3994-4e25-8e5b-50bcab8a0b7f.png" alt class="image--center mx-auto" /></p>
<p> When you save that and then try again opening your page and click the button,you will not see an error but the login form from Keycloak. Here, I can now enter maria as username and the password credentials I’ve set:</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247588055/85e86a12-6de7-4fc7-83c5-90f530d6f8f1.png" alt class="image--center mx-auto" /></p>
<p> After logging in, Keycloak immediately asks us to change the password to something new:</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247612953/489b1b1c-445b-4421-8318-6ad2009baeef.png" alt class="image--center mx-auto" /></p>
<p>Once done, Keycloak will usually also ask for providing an Email and Name but you can turn off that these fields are required in your Keycloak <strong>Realm settings</strong> in the sub section <strong>User profile</strong>. After those initial actions, you’re headed back to the application page and the keycloak library will have saved the session in your app - you’re now authenticated via Keycloak with the user maria.</p>
</li>
<li><p>You can now load the user profile object - and hence verify the successful authentication by e.g. adding a button that triggers it</p>
<pre><code class="lang-jsx">&lt;button
     type=<span class="hljs-string">"button"</span>
     onClick={<span class="hljs-function">() =&gt;</span> {
         keycloak.loadUserInfo().then(<span class="hljs-function">(<span class="hljs-params">profile</span>) =&gt;</span> {
         <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'profile'</span>, profile);
       });
     }}
    &gt;
    Load profile
&lt;/button&gt;
</code></pre>
</li>
<li><p>A basic authentication flow with Keycloak: done ✅</p>
</li>
</ol>
<p>Let’s now have a look at how Keycloak would help to give access to specific data (Authorization).</p>
<h3 id="heading-authenticating-with-3d-party-providers-or-sso-in-keycloak">Authenticating with 3d-party providers or SSO in Keycloak</h3>
<p>You've just learned how to use Keycloak for user authentication. Now, let's explore how you can integrate third-party providers as Identity Providers (IDPs) in Keycloak. For example, GitHub. When set up, Keycloak will add a "Sign in with GitHub" option to its login form, connect with GitHub, and store the user information in Keycloak.</p>
<p>To set this up, navigate to the "Identity Providers" section in Keycloak. You can choose from pre-defined providers for a straightforward setup, or if your desired provider isn't listed, you can define it manually according to the standard protocols.</p>
<p>This flexibility means Keycloak supports any Identity Provider that uses the common standards OpenID Connect or SAML—which essentially covers almost all providers.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247710567/c003f8d1-7986-4b0e-9d51-f7e4902c0ee6.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-how-to-authorize-with-keycloak-a-fine-grained-permission-system">How to authorize with Keycloak: A fine-grained permission system</h2>
<p>The simplest approach to authorization is what we discussed in the Supabase section: saving a user's ID alongside data in the database to control access. This method works regardless of whether you use Supabase, Keycloak, or any system that provides unique user IDs.</p>
<p>Keycloak, however, offers a more sophisticated permission management system. Let's take a quick look at it.</p>
<p>If we check the "Role mapping" tab for our <code>maria</code> user in Keycloak, we'll see a <code>default-roles-...</code> entry. Using the Keycloak JavaScript library we set up earlier, you can access an authenticated user's roles via <code>keycloak.realmAccess</code>, which includes this <code>default-roles-...</code> role.</p>
<p>In Keycloak's "Realm roles" section, you can create new roles and nest them within others. For instance, you could create a "content-admin" role and include "content-editor" and "content-publisher" roles within it. You can then assign these roles to users and check them on the backend using <code>keycloak.realmAccess</code> to manage access to specific actions in your system.</p>
<p>For example, if you assign someone the "content-admin" role and another person the "content-editor" role, you'd only need to check for "content-editor" permissions, as "content-admin" already includes "content-editor" capabilities.</p>
<p>Keycloak's functionality extends further, allowing you to assign attributes to roles and use these roles to manage internal Keycloak permissions if desired.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247727609/f5000f22-5838-45a5-9a3f-50139164628d.png" alt="In this sample, you can see two Users in Keycloak where both have the Default role defined but only “User A” has the role “Admin”" class="image--center mx-auto" /></p>
<p>In summary, Keycloak provides a role-based system with inheritance and fine-grained attributes for roles. This approach makes role management and assignment more flexible and independent. You could even create a Keycloak user with the ability to assign only certain roles to other users, which could be useful for a service team.</p>
<p>Remember, though: These role definitions and attributes in Keycloak only take effect if you design your application's backend to check for these roles.</p>
<h1 id="heading-keycloak-or-supabase">Keycloak or Supabase?</h1>
<p>On the surface, both Supabase and Keycloak use the same standards to authenticate a user and, in theory, both support arbitrary providers since Supabase also supports the generic SAML 2.0 standard. However, SAML is a standard that is more used in enterprise contexts. E.g. using SAML with GitHub is only possible in GitHub enterprise as of today.</p>
<p>Let’s have a look at when to use what and if it makes sense to use both.</p>
<h2 id="heading-when-should-i-use-keycloak-over-supabase">When should I use Keycloak over Supabase?</h2>
<p>There are multiple reasons that would pinpoint to using Keycloak instead of Supabase:</p>
<ul>
<li><p>You need to support identity providers that Supabase doesn’t support</p>
</li>
<li><p>You need to have enterprise SSO support e.g. connecting Auth with an enterprise MS365 login</p>
</li>
<li><p>You want real emailless logins</p>
</li>
<li><p>You need to support User Federation systems such as Kerberos or Ldap</p>
</li>
<li><p>You want to decouple the database management UI from the user management UI</p>
</li>
<li><p>You want to be able to manage user permissions in a UI</p>
</li>
<li><p>You want to create a team of people to be able to manage users and their permissions</p>
</li>
<li><p>You need a more complex and flexible role-based access control system that goes beyond simple user permissions and you want to manage it in one centralized IAM UI</p>
</li>
<li><p>You require detailed audit logs of user authentication and authorization activities</p>
</li>
<li><p>You want to manage multiple different Authentication providers for different applications, e.g. for different clients, in one place (you can use one Keycloak with different Realms, no need to have multiple instances of Keycloak)</p>
</li>
<li><p>You want to define authentication flows / rules such as requiring users to change their password every 6 months</p>
</li>
<li><p>You want to easily and fully impersonate users with the click of a button</p>
</li>
<li><p>You want to separate the Auth system from the database system to stay more flexible long-term</p>
</li>
</ul>
<p>There are certainly more things Keycloak can do but the above list should provide a good first measure for you to decide if you want/need Keycloak.</p>
<p>Let’s have a look at Supabase.</p>
<h2 id="heading-when-should-i-use-supabase-over-keycloak">When should I use Supabase over Keycloak?</h2>
<p>Choosing between Supabase and Keycloak often depends on the specific needs of your project. While Keycloak excels in complex enterprise scenarios, Supabase offers a streamlined, developer-friendly approach that can be ideal for many modern web and mobile applications. If your project doesn't require the advanced features that make Keycloak necessary, Supabase can be an excellent choice. Here are some scenarios where Supabase might be the preferable option:</p>
<ul>
<li><p>You’re using Supabase anyways due to its database, storage and realtime capabilities and now also need authentication</p>
</li>
<li><p>You want a simple, integrated all-in-one solution for authentication and database management</p>
</li>
<li><p>You don’t need to manage different apps / clients with the same Auth system</p>
</li>
<li><p>You want to directly bind database data access to users as shown in the RLS samples in the previous Supabase sections</p>
</li>
<li><p>You want to build your own role management system by reflecting roles and permissions in the database</p>
</li>
<li><p>You're building a new application and don't need complex user management features such as requesting password rotation routinely</p>
</li>
<li><p>You don’t need the ability to invalidate current sessions via the UI</p>
</li>
</ul>
<p>For many people that are building an application with Supabase, the built-in authentication and authorization features will greatly be sufficient to also build complex projects.</p>
<p>But end of the day, you have to make your decision and you might even want to use both. Let’s look at that combination now.</p>
<h2 id="heading-how-and-why-to-connect-keycloak-with-supabase-and-use-both-together">How and why to connect Keycloak with Supabase and use both together</h2>
<p>We already discussed that you can use a 3d-party login with Supabase such as “Sign in with Google”. Keycloak can do so too. But you can also do a “Sign in with Keycloak” which will then e.g. go to your <a target="_blank" href="http://SkyCloak.io">SkyCloak.io</a> (hosted Keycloak) instance so suddenly your wonderful Keycloak becomes the authenticator for Supabase.</p>
<p>This is super easy to achieve. You would be using Supabase and its libraries to authenticate with Supabase and, when doing so, Supabase would request that authentication from Keycloak. So technically, your application will redirect to Supabase and then Supabase will redirect to Keycloak. Keycloak will do the authentication and give the result back to Supabase. Then, if everything went well, Supabase would respond with a Supabase authentication to your application.</p>
<p>This allows you to manage users within Keycloak whilst making use of all Supabase Auth features. A pretty insane combination and also easy to set up. To achieve that, not your application, but Supabase itself will be a client of Keycloak. So, in Keycloak you would create a new client called “supabase”, set the authentication type to confidential (<strong>Client Authentication On</strong>) and provide the Callback URL from Supabase (<code>your-supabase-url/auth/v1/callback</code>) as Redirect URI in the Keycloak client creation:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247768740/356b35a4-8c1c-4c83-be4b-c9ffe4291feb.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247778867/af1893c2-6198-49be-9aad-d01bbb530117.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247784707/83f0dd3b-c43b-4c4c-af29-9614507a876d.png" alt class="image--center mx-auto" /></p>
<p>Then, you need to move to the “<strong>Credentials</strong>” Tab of that Keycloak client to obtain the client secret. Copy it into your clipboard:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247800004/8c0edcff-12c4-4504-93e6-cf789815ac01.png" alt class="image--center mx-auto" /></p>
<p>After that, go into your Supabase instance, move to the “<strong>Providers</strong>” section, open Keycloak, enable it and add the client id, the obtained secret and the Realm URL of Keycloak as follows:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736247822059/d251ffda-5dec-42ef-b22f-c2b8c31364f8.png" alt class="image--center mx-auto" /></p>
<p>After creation of that connection (click “Save”), you can now use Supabase to log in via Keycloak. E.g., in JavaScript you would trigger an authentication with:</p>
<pre><code class="lang-jsx">supabase.auth.signInWithOAuth({ 
  <span class="hljs-attr">provider</span>: <span class="hljs-string">'spotify'</span>, 
  <span class="hljs-attr">scopes</span>: <span class="hljs-string">'openid profile email'</span> 
});
</code></pre>
<p>That also means: You can then connect to Keycloak any identity provider you want. You now have: The universal solution: You can manage users in Keycloak but use them with Supabase and also use the authorization methods of Supabase.</p>
<p>Supabase will still obviously create a user copy (requiring a user to have a valid email) but it will only authenticate and verify that user with Keycloak.</p>
<p>Supabase will then create a Session token which contains the Supabase data as well as the Keycloak session data. The latter is saved in <code>supabaseSessionData.provider_token</code> . That means that you can also read the Keycloak token directly from the Supabase token and e.g. store the role / permission information there.</p>
<p>Now it’s up to you to decide which solution you want.</p>
<h1 id="heading-how-skycloak-makes-usage-of-keycloak-even-easier">How Skycloak makes usage of Keycloak even easier</h1>
<p>SkyCloak is a raw but fully managed Keycloak deployment system. It fully takes away the pain of deployment which is indeed a crucial point when it comes to efficiency. Using Keycloak is one thing, deploying and configuring it properly is another.</p>
<p>Forget about that and just use it. With SkyCloak</p>
<p>you can focus on building your application while leveraging the powerful features of Keycloak. SkyCloak handles the complex deployment and maintenance tasks, ensuring your Keycloak instance is always up-to-date, secure, and optimized for performance. This allows you to take advantage of Keycloak's robust authentication and authorization capabilities without the overhead of managing the infrastructure yourself.</p>
]]></content:encoded></item><item><title><![CDATA[Why Next.js should focus on reducing source code complexity]]></title><description><![CDATA[On my last post, I got wild comments from the "social" internet community.
It went from: "I didn't read it but I disagree"

to people telling me I'm just a random guy to randomly (moreso unfoundedly) ranting on Next.js - despite the fact that I broug...]]></description><link>https://blog.activeno.de/why-nextjs-should-focus-on-reducing-source-code-complexity</link><guid isPermaLink="true">https://blog.activeno.de/why-nextjs-should-focus-on-reducing-source-code-complexity</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[Remix]]></category><category><![CDATA[Nuxt]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Mon, 27 May 2024 17:53:54 GMT</pubDate><content:encoded><![CDATA[<p>On my last post, I got wild comments from the "social" internet community.</p>
<p>It went from: "I didn't read it but I disagree"</p>
<p><img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExOXo3cThrdTd1eHh0eXJoa2o3a2lxZ3pnd21zcXV4bjQyeTlqeGxmcyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/3o6UBlHJQT19wSgJQk/giphy.gif" alt class="image--center mx-auto" /></p>
<p>to people telling me I'm just a random guy to randomly (moreso unfoundedly) ranting on Next.js - despite the fact that I brought facts to the table and preambling that I love using Next.js and that's why I need to critizice - cause I want it in my toolstack and I want it to be a great foundation lasting for years. Still, <strong>my love for Next.js doesn't change the fact that it's core is still a piece of complexity that is beyond acceptable - IMO</strong>.</p>
<p>This time let me give you even more facts and more preamble context.</p>
<h2 id="heading-tldr-preamble">TL;DR preamble</h2>
<ul>
<li><p>I'm a Web Software Architect working in the Web for 22+ years and was happy to observe every change since the ages of Netscape and "what is border-radius?"</p>
</li>
<li><p>Been leading huge enterprise software migrations in well-known companies</p>
</li>
<li><p>Complexity is normal in big projects, but there is a difference of something being complex and something being at the edge of unmaintainability. The latter can be observed and identified very easily: Take 5 different experienced people in the field, give them a few different, similiar, tasks to dig in the sourcecode and let them do it / find it for each project. The amount of time and frustration will show you if it's just big and complex or also unmaintainable.</p>
</li>
<li><p>I have been digging a lot of sourcecode repositories, some for contribution, some for interest, some for research. I either contribute or help in contribution (e.g. through my team/discord/slack channels) in many different repos from Mantine, Storybook, Listmonk, tabler-icons, Supabase and probably a few more which I don't remember</p>
</li>
<li><p>I have digged into the Next.js sourcecode for a reason to find why the request object isn't the same from middleware to page (it's the same object origin, I'm not elaborating on this again in this post) so I wanted to find out. And as part of trying to find the reason, I was utterly stunned by the amount of time I had to invest as opposed to any other repository I've digged into.</p>
</li>
</ul>
<h2 id="heading-whats-this-about">What's this about?</h2>
<p>It's about facts and attention. It's not about ranting or hating. It's about driving attention and causing change. <strong>I love using NextJS from the perspective of using it</strong>. Most people won't go deeper than that because there's no reason for them to dip into the sourcecode. And because the result they're seeing often looks great and is well-marketed, they get the impression "they know what they're doing" - this is something I've also been told when criticizing their caching decisions. Now they finally changed it in v15 so let's just reflect this and agree that they certainly have tried their best but "they know what they're doing" <em>didn't age well</em>.</p>
<p>I need to highlight: <strong>this is normal</strong>. Things change, things are wrong, things get reverted/changed. Means: community feedback is respected if it's loud enough. That's good to hear!</p>
<p>Valid critique is the path for improvement. Always, at every state. And this is another post criticizing what's wrong in the vercel-driven Next.js world and what is seeking change.</p>
<p>Let's get into it.</p>
<h2 id="heading-the-source-code-of-nextjs-is-way-too-huge-and-complex-and-vercel-admits-it">The source code of Next.js is way too huge and complex and Vercel admits it</h2>
<p>I've tried tagging <a class="user-mention" href="https://hashnode.com/@leerob">Lee Robinson</a> multiple times, so am I doing now (ping <a class="user-mention" href="https://hashnode.com/@rauchg">Guillermo Rauch</a> ). I've been long enough in enterprises to know that it's all about politics and marketing to get more paying customers. The shiny, perfect videos from Vercel at every gig speak a clear language: A lot of money is spent on making things look good for everything that's seen on the outside. That's lovely, I like that. But I want equal money spent on improving the source code.</p>
<p>The Next.js sourcecode isn't seen on the outside. It's something that happens on the inside. It's by definition a historically grown codebase and has seen a lot of change over the last years. Now, let me tell you this: In huge projects that undergo huge breaking changes over the years I've seen most often two things:</p>
<ol>
<li><p>At one point of time, the project is rewritten because the code has changed so much that the complexity was unbearable or</p>
</li>
<li><p>Company expects same or even faster feature implementation but due to the given complexity this is only possible by adding more flags, more code, more complexity and this is then "backed" to run safely by adding tests. And this feeling i get with Next.js' sourcecode.</p>
</li>
</ol>
<p>I've been talking to one person at Vercel (I won't mention names here because I know how internal politics of enterprises can backfire with such things) and that person admitted my statements being right about Vercel having problems with the grown complexity of the Next.js code and that they'd be happy for "contributions". I told that person, that I, being an individual person who needs to earn money on a daily, cannot simply provide one PR to fix that. Especially, since I cannot simply work on it for weeks, then open a huge PR and they'd be like "Yeah can't merge that, had like 100 other PRs in between which would conflict". This thing has to be tackled from the inside.</p>
<p>Now, let's get to the facts.</p>
<h2 id="heading-the-facts">The facts</h2>
<p>As everyone complains all the time, even about facts, let me also give a preamble here: If you take multiple analysis tools you'll get multiple different results, maybe even opposing ones as metrics differ or as the final indicator based on the metrics differ. There is no tool that clearly can identify this, so we have to use our own brain and make use of the numbers.</p>
<p>Comparison does / is based on / respects</p>
<ul>
<li><p><code>dist/</code> and <code>src/compiled</code> files are deleted before scan</p>
</li>
<li><p>Simlilar project features (nuxt.js, remix)</p>
</li>
<li><p>Similiar project complexity (I'm not comparing next.js to my one-file repo)</p>
</li>
</ul>
<h3 id="heading-a-few-facts-about-nextjs-in-general">A few facts about Next.js in general</h3>
<p>Next.js is a composition of <strong>React</strong> + <strong>Express</strong> server (tl;dr version). So, the crucial tools, like the server and the React rendering, were already provided by other frameworks. Why do I state this? Using one server tool and one frontend tool should be reachable with a clean structure. Adding more tools surrounding it, as well.</p>
<p>Within the <code>src/</code> belongs source code. Next.js, has a lot of dist files within src subdirectories that are comitted. It's not clear why, it's confusing and it's convoluting but I made sure to get rid of those to not have the analysis compare dist files.</p>
<p>See here:</p>
<ul>
<li><a target="_blank" href="https://github.com/vercel/next.js/tree/canary/packages/next/src/compiled">https://github.com/vercel/next.js/tree/canary/packages/next/src/compiled</a></li>
</ul>
<h3 id="heading-sonarqube-embold">Sonarqube + Embold</h3>
<p>Being well-known, I first initialized Sonarqube CE and analyzed Next.js amongst other repos. Obviously, the next repo consists of more packages than just the framework itself (like <code>create-next-app</code>) so I made sure to focus on the framework each, not the side packages.</p>
<p><em>The only frameworks for which I deleted files to really getting the "best results" are Next.js and React. In all other repos I didn't give a single damn about deleting files. I say this to highlight once more how fair I tried to be towards Next.js - even unfair towards the others. In React I deleted all files unnecessary for the actual core framework (testing files, devtools, etc, mocks, fixtures).</em></p>
<p>Besides Sonarqube, I wanted another measurement tool with maybe even completely different results to have some more numbers - I chose <strong>Embold</strong>. Unfortunately I couldn't remove any test files there as I had been using the raw fork so you have to keep that in mind.</p>
<h3 id="heading-comparing-next-nuxt-remix-sveltekit-react">Comparing Next, Nuxt, Remix, SvelteKit, React</h3>
<p>If you ask "Why React?" - just to have something out of fullstack context but with huge featureset to compare with - it's not suprising React comes with a huge codebase but it is suprising that Next is bigger, core-wise.</p>
<p><strong>Fun fact:</strong> In all (!) analysis processes Next.js took the longest. Even if I used whatever other repositories to compare with. Some Repos take less than a minute to analyze, Next.js, with some tools, allows you knead and bake a pizza meanwhile if the whole repository is analyzed. It's a monorepo after all but so are others.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>Next</td><td>Nuxt</td><td>Remix</td><td>SvelteKit (whole Repo!)</td><td>React</td></tr>
</thead>
<tbody>
<tr>
<td>Lines of Code</td><td><mark>123k</mark></td><td>15k</td><td>72k</td><td>48k</td><td><mark>101k</mark></td></tr>
<tr>
<td>Maintainability Issues</td><td><mark>2.8k / 2,3%</mark></td><td><mark>306 / 2%</mark></td><td>541 / 0,75%</td><td>307 / 0,63%</td><td>3100 / 3%</td></tr>
<tr>
<td>Security Hotspots</td><td><mark>94 / 0,07%</mark></td><td>6 / 0,04%</td><td><mark>51 / 0,07%</mark></td><td>65 / 0,13%</td><td>35 / 0,03%</td></tr>
<tr>
<td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr>
<td><strong>Embold</strong> Ranking (5 is best, -5 is worst) -[whole Repo]</td><td><mark>-1.11/5</mark></td><td>4.48/5</td><td>3.79/5</td><td><mark>2.63/5</mark></td><td>2.46/5</td></tr>
<tr>
<td><strong>Embold</strong> &gt; Medium Code Issues[whole Repo]</td><td><mark>~40k/5.3%</mark></td><td>0/0%</td><td>175/0,6%</td><td><mark>374 / 1%</mark></td><td>5964 / 0,2%</td></tr>
<tr>
<td>Embold Worst File Ratings</td><td>Quite some below -3 but be aware that this is due to it checking the duplicated <code>compiled/</code> files. Besides that many files above 2.0</td><td>2.98 (3 HTML files, other than that, all files above <strong>4.7</strong>)</td><td>-1.37 (entry.server.jsx) but then rapidly most files being at <strong>2.6+</strong></td><td>-0.54, then rapidly most files at <strong>2.0+</strong></td><td>Very few benchmark files below -3, then jsx-dev-runtime below -1.3, Quite some files below 2.0</td></tr>
</tbody>
</table>
</div><p>What I found surprising is the fact that Embold rated <strong>SvelteKit</strong> so bad but when I looked deeper into it, it was mainly due to low-level issues (like snake-case instead of default-case) which is why I added the <strong>\&gt; medium</strong> issues section to accomodate for that voting.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The most important conclusion is: People love numbers but numbers are always as wrong / right as you present them. Even asking AI about the complexity of those framework files, whatever you chose, it responds with 4/5 because AI can't have mental space for mental load (or I wasn't prompting it right, who knows...).</p>
<p>So you might be asking: So was all the effort of getting those numbers worth nothing if they don't mean a thing?</p>
<p>Even if I highly recommend you to not give too much fucks about these numbers (especially stuff like Security Hotspot which is sometimes highlighted when potentially troublesome RegExps are used), numbers can align with other factors.</p>
<p>So what I urge you to do is taking those numbers and trying to understand common things you do in these frameworks. Ask yourself things like "When I create a server route, how is it rendered?"</p>
<p>What the numbers underline at least: Next.js is unnecessarily huge. It's hugeness alone isn't the problem if each file was easy to understand but Next.js has single files like <code>next-server.ts</code> which have only import statements in the first 100 lines and what comes after is nearly impossible to grasp. And I'm not just nitpicking. Sure, next.js has small files as well but there are many files so intertwined (not just "do one thing") and so context-bound, that I find it to stand out complexity-wise as opposed to all of the other frameworks above.</p>
<p>None of these frameworks is super-easy to get into, they're frameworks after all but understanding the files from Nuxt or Remix, was more comforting and less time-consuming as opposed to Next. And I had this feeling all throughout the repo. And this also means that it prevents people from contributing and this is something you want to avoid.</p>
]]></content:encoded></item><item><title><![CDATA[Digging the actual Next.js source code: A nightmare]]></title><description><![CDATA[As expected when posting, some users claim that I picked one specific sample. I can't put all samples here that I found, this blog post would be endless. You are very welcome to check the code yourself, with Code Complexity linters and your own thoug...]]></description><link>https://blog.activeno.de/nextjs-sourcecode-nightmare</link><guid isPermaLink="true">https://blog.activeno.de/nextjs-sourcecode-nightmare</guid><category><![CDATA[Next.js]]></category><category><![CDATA[software development]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Thu, 25 Apr 2024 19:54:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/xI8yk9DwH0Y/upload/d3fc3ed52079512e604094c305911f75.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>As expected when posting, some users claim that I picked one specific sample. I can't put all samples here that I found, this blog post would be endless. You are very welcome to check the code yourself, with Code Complexity linters and your own thoughts enabled - it's OSS.</p>
</blockquote>
<p>Clickbaity title? Maybe. But it gets the gist. And before you think "this is just another hate post / opinion", well read on, I digged the source code deeper than many ever go and I did so for good reason.</p>
<p>Also, I've used tools to prime and underline this with facts. See all of my projects aren't perfect - at all - nor is my code. But, let me tell you that I've been migrating the hugest enterprise stacks with integrated linting in the pipelines from repo to jira, so I know what it means to fix "bad" code - whichever way you define it.</p>
<h2 id="heading-i-care-a-lot">I care, a lot!</h2>
<p>Next.js is my go-to framework for projects. This is why I care so much about it and why I do this blogpost, because the insights honestly scared me.</p>
<p>Funnily there are often many individuals that think I'm "hating" on Next.js. There is in fact no tool I use more than Next.js. I like using it. Even more so it's important to adress it's problems. Also I did write <a class="user-mention" href="https://hashnode.com/@leerob">Lee Robinson</a> a message before posting this but he didn't respond. So did I tag the CEO in a Twitter thread</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/activenode/status/1782415485482520759">https://twitter.com/activenode/status/1782415485482520759</a></div>
<p> </p>
<h2 id="heading-why-did-i-even-check-the-nextjs-source-code">Why did I even check the Next.js source code?</h2>
<p>Well, funnily, I didn't with the intention to write a blog post. In fact, I wanted to find out why setting a value on the <code>request</code> object inside of the middleware won't be available in it's subsequent renderings (like e.g. the page component). This isn't obvious at all. As it's the same request, you can easily expect it to be reused - it's part of the same code logic / loop as I found later so even more so that would be a reason. But it's not. And I wanted to know why.</p>
<p>I often dig into foreign source codes of big projects to understand what happens under the hood. Mostly to provide the correct solution for my projects. Usually, I get a good feeling after like 1 hour. E.g. digging into any repos of the Supabase organization on GitHub, although it's complexity, is always kinda straightforward.</p>
<p>Now with Next.js, it was very very very different. I started digging on a <strong>Sunday,</strong> with a few breaks in between, I found the reason for the behaviour on <strong>Tuesday</strong>. It's not like I didn't have an assumption, but I had to prove that my assumption is correct. I expected a few files to be involved but not this mess only to find out one single thing.</p>
<p>I created a Notion page to keep track of what I'm seeing because the complexity was so high that it was impossible to keep it in my head. The resulting page that didn't even include everything that really happens (I left out a few things that I felt weren't needed to understand the whole thing) had 3300 characters, 430 words. That's a short blog article basically (i would give you a screenshot, but the notes are german and I didn't want to translate all of it).</p>
<p>Edit: Used ChatGPT, here's a screenshot. Please note that this might look "normal" to you by just reading it, but this isn't even complete and pathtracing this took ages, there are extreme amounts of conditions, function calls, etc.</p>
<p><a target="_blank" href="https://cdn.hashnode.com/res/hashnode/image/upload/v1714118018970/8fb3d6b1-ffd9-46fe-8abe-813d8f76e1dd.png"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714118018970/8fb3d6b1-ffd9-46fe-8abe-813d8f76e1dd.png?height=500" alt class="image--center mx-auto" /></a></p>
<h2 id="heading-okay-so-will-you-tell-us-the-result-at-least">Okay, so will you tell us the result at least?</h2>
<p>Sure! NextJS resolves routes all together (interesting, right?) and then loops them (tl;dr version).</p>
<p>The request object is then copied. But not because it makes sense (it does make sense but given the code naming, it's not because of that) but because it needs to be converted and extended with NextJS values. The specific code is <code>const normalizedReq = this.normalizeReq(req)</code> and it's being passed the original, non NextJS-extended <code>req</code> object and if <code>normalizeReq</code> recognizes that it's not a <code>NextRequest</code> it will make one by copying the values of the given <code>req</code>. Weirdly, if one would start contributing that isn't aware of that exact thing, the behaviour could change to sometimes actually being the same object (if it's of the <code>NextRequest</code> instance already). But yeah, let's not discuss this code detail here.</p>
<p>Again: although it makes sense, it is all but obvious.</p>
<h2 id="heading-lets-define-bad-code">Let's define bad code</h2>
<p>In very short terms: Bad code isn't just technically bad code (like code with failures). Bad code can be code that works perfectly but is so convoluted that it's only understandable after hours of digging or after asking someone else who is long enough in the code. Bad code can also be code that only holds on to work because there are tests so as long as no test breaks, we can trust it works, but we don't know why. There's multiple definitions of "bad" but none of the definitions is one you'd like your code to be aligned with.</p>
<h2 id="heading-lets-have-more-source-code-insights-from-nextjs-complexity-dx">Let's have more source code insights from Next.js complexity / DX</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714069728708/ae4d6ccd-b4a8-4045-bee3-22281d5bef6a.png" alt="An extract of NextJS 14 code that has a SonarLint complexity of 279 (15 is the default max)" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714071145747/f9efc231-b7be-4a97-85ec-dabb11e195ce.png" alt class="image--center mx-auto" /></p>
<p>I've installed two rather well-known utilities, the SonarLint (very well-known) and the CodeMetrics (<a target="_blank" href="https://github.com/kisstkondoros/codemetrics">https://github.com/kisstkondoros/codemetrics</a>).</p>
<p>In fact I didn't need those tools to determine that NextJS is a big bunch of "wtf is going on here?". Try it on your own, check out the Next.js repo and make it your task to find e.g. "where does Next.js call server actions". Tell me how much time it took (FYI, I don't know this, i just picked a random question that came to my mind).</p>
<p>Let's take another sample of the same file (which is small with ~800 lines considering the fact that I've seen some with 3k+ lines):</p>
<p><code>resolve-routes.ts#232</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714073869507/ee6d0586-8e7b-4150-b94b-3762ddff974a.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714073854909/3a68b511-6d50-4966-a9c9-12157b55a437.png" alt class="image--center mx-auto" /></p>
<p>Before we get to the <code>checkTrue()</code>, I give you this: As the filename states, resolve routes resolves routes (didn't think of that, right?). What do you think <code>checkTrue()</code> does - besides the fact that it's body has increased complexity? Does it check true? True for what? Can't you tell? Me neither. There's also no comment that explains it. I cannot think of a single use-case, no matter the project, where it would make sense to define a function named <code>checkTrue()</code> . I cannot.</p>
<p>I'll give you a bit more code, just the start:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714074247606/9a635110-943e-40cf-b8cc-a25ab3ae8601.png" alt class="image--center mx-auto" /></p>
<p>How about now? <code>checkLocaleApi</code> ? Didn't we developers agree that a well-readable code with if's should actually state what it does? Like <code>isLocaleaApi</code>? And what does that one do? Can we hover it?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714074321169/532dbc3d-28be-428b-af76-ae96b4031f26.png" alt class="image--center mx-auto" /></p>
<p>Ah, perfect, it returns true or undefined. That clears it up. Not. Given it'd be named <code>isLocaleApi</code> I could at least make some sense of it. Although, a quick function definition comment wouldn't harm, with maybe even references to related files, would it?</p>
<p>I'll stop with this now. Every single file within the Next.js repo that has a few hundred lines has stuff like this all over the place. It's not "one pick". It's everywhere. Go have a look for yourself.</p>
<h2 id="heading-thats-because-nextjs-is-a-complex-project1111">That's because Next.js is a complex project!!!!1111</h2>
<p>There's one thing I can certainly say: Overall Complexity of a project (it being big having many features and dependencies) does never justify bad DX. Never. It explains it, but it doesn't justify it. Never in my career have I noted: Let's make this shit complex as fuck because it's requirements are complex. In fact, quite the opposite. The more complex, the more I try to seperate and structure.</p>
<p>I have contributed in big projects where I didn't know the language even, simply because the code was well-written and well-structured so I could follow along and contribute.</p>
<p>Given semantical complexity in fact is a major, major reason to remove the complexity in the code. This can always be done. By using code quality linters, by defining max file sizes, by having proper, clean code reviews, etc., you name it.</p>
<p>There is one rule that goes way beyond development and more often than never has proven to be effective: Conquer and divide. For instance, team splitting with responsibilites on certain features with splitted repos (can be a monorepo with separated folders, whatever) will lead to teams owning these features and overall often more code but more code that is well structured - if done right.</p>
<h3 id="heading-but-david-do-you-always-create-perfect-projects">But David, do you always create perfect projects?</h3>
<p>In fact, sometimes I do know that my code is shit code (is it a skill to accept ones own shit code?). But in own projects, you can do so. I didn't need superclean code in my landingpage. Who cares? Even more so, sometimes doing an MVP with 100% shitcode and then throwing it away and redoing it is also a thing I've done and it was worth it.</p>
<p>I do however care in a fintech banking application or generally in contexts that need longevity and fast onboarding. So, DX depends very much on the context but I'd say having an open-source framework with millions of end users, that's a good point to think about <strong>awesome</strong> code clarity, not even "okayish" but <strong>awesome</strong>.</p>
<h2 id="heading-whats-your-take-away-now-david">What's your take away now David?</h2>
<p>I don't know really. I'm not hating. I'm desperate due to what I saw. I tried to reach out to some Vercel guys but I'm not sure if they didn't read or just don't wanna hear it. I'd love to hear some thoughts from them, hearing their opinion on the status quo, to see their actually listening.</p>
<p>Like I'd be interested if there is a method within their team that tries to "decrase code complexity" which goes hand in hand with e.g. linters because I'd really be interested if they just said "Okay SonarLint, complexity of 300 is just GREEN all the way".</p>
<p>What's for sure: I won't, no actually I cannot contribute to Next.js in it's current state as it's, without a doubt, the only project I've seen which was so convoluted in so many different places that I consider my old, hated enterprise project with full-blown shitcode easier because it was well-structured and I could migrate it step by step (took about 2 years, but we did it).</p>
]]></content:encoded></item><item><title><![CDATA[Re-evaluating Next.js: Did it go the wrong path?]]></title><description><![CDATA[For any of my projects in the last years, Next.js was the framework of choice. For future projects I'm not sure anymore and I'm in the midst of a Next.js crisis so to say.

Please note: I'm not bashing Next.js here. I'm using Next.js still everyday b...]]></description><link>https://blog.activeno.de/re-evaluating-nextjs-did-it-go-the-wrong-path</link><guid isPermaLink="true">https://blog.activeno.de/re-evaluating-nextjs-did-it-go-the-wrong-path</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[Vercel]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Mon, 18 Mar 2024 10:15:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1710756900935/d1bf1855-e8db-4caa-bc8c-9744d5652122.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For any of my projects in the last years, Next.js was the framework of choice. For future projects I'm not sure anymore and I'm in the midst of a Next.js crisis so to say.</p>
<blockquote>
<p>Please note: I'm not bashing Next.js here. I'm using Next.js still everyday but I really needed to get my thoughts and critics out there to see if people do share my concerns. There is a lot of good stuff about Next.js for sure, otherwise I wouldn't have used it for so long, but right now the bad things seem to overshadow the good.</p>
</blockquote>
<p>Earlier when people asked questions about Next.js (v12, Pages Router), it was quite easy to simply pinpoint to the docs. Most of it was easy to understand, people understood that if they want to embark on server-side data, they can do so using getServerSideProps or getStaticProps. Pretty clear, intuitive.</p>
<p>I am an architect of 23 years development experience now. I can surely state that I am happy I could develop an awesome skillset but I probably spent the most time with JavaScript. That being said if I am thinking about Next.js being not intuitive enough or too complex I can only imagine people with fewer experience struggling.</p>
<p>Since v13, Next.js tries to push Server-Side-Rendering to the fullest but it doesn't feel complementary anymore but instead seems to wanting to replace frontend. I mean this is cool in the use-case where you actually want that, say a contentful news page or an e-commerce shop. But for an extremely fluid, reactive application with beautiful user interaction, things got way more complicated.</p>
<p>The problem isn't SSR, at all. SSR is lovely and important. The problem is, that we are hyperfocused on enforcing SSR and right now it doesn't seem quite reasonable anymore. If Next.js could trigger onClick on the server, they probably would. And this is where it gets ridicolous: Solving a problem that didn't want to be solved. But let's get into actual issues.</p>
<h2 id="heading-caching-without-asking">Caching without asking</h2>
<p>With v13, Next.js started caching your internal API requests implicitly. Means: If you use fetch for grabbing weather data, you'll get old weather data the next time you fetch it. Thanks to automatic caching. Sure, you can disable it. But this is a breaking change and React itself has a known guideline that it's tailored for enterprise not having breaking changes like that (<a target="_blank" href="https://legacy.reactjs.org/docs/design-principles.html#stability">https://legacy.reactjs.org/docs/design-principles.html#stability</a>). So even though React and Next are extremely close, Next didn't seem to adhere to the same design principles. Having such a caching mechanism is surely awesome, if you can enable it - either globally or partially. But I'm confused that amongst all architects, no one said: it shouldn't be enabled by default changing the way your application works in a world where probably most API requests should not be cached.</p>
<p>The whole thing lead to an answer in the form of explaining the caching (<a target="_blank" href="https://www.youtube.com/watch?v=VBlSe8tvg4U">https://www.youtube.com/watch?v=VBlSe8tvg4U</a>). There's a saying though: If you need to explain it, it's not intuitive enough. But the thing is: it was explained in the docs (which makes sense) so rather the saying is: If you need to explain it over and over again, it's definitely not the best solution. (Still, thanks for the video, it's a good one!).</p>
<p>Let me say this: From a technical perspective, seeing what they did, I'm amazed. But as given, it's something no one asked for. I would've rather had something like an explicit caching mechanisms allowing me to CONTROL my app better and not letting my app be controlled by a simple upgrade. Especially because this existing mechanism leads to the problem that I cannot cache requests that I want to cache, e.g. when I'm using Supabase and fetch data from it, it will always skip the cache - which usually makes sense but if Next had explicit caching, I could just put the result to the cache - oh well, in fact, you can, it's just hidden: <a target="_blank" href="https://nextjs.org/docs/app/building-your-application/caching#react-cache-function">https://nextjs.org/docs/app/building-your-application/caching#react-cache-function</a> but, again, comes with pitfalls: <a target="_blank" href="https://react.dev/reference/react/cache">https://react.dev/reference/react/cache</a> . So much to know for a rather simple thing to achieve.</p>
<p>The other problem about that caching is that there are a lot of additional things to consider. For example, if you want to force a page to refresh, you can actually call <code>router.refresh()</code> one the same page or <code>revalidatePath</code> . Unsure? Well that's easy, here's what the docs say:</p>
<blockquote>
<p><code>revalidatePath</code> vs. <code>router.refresh</code>:</p>
<p>Calling <code>router.refresh</code> will clear the Router cache, and re-render route segments on the server without invalidating the Data Cache or the Full Route Cache.</p>
<p>The difference is that <code>revalidatePath</code> purges the Data Cache and Full Route Cache, whereas <code>router.refresh()</code> does not change the Data Cache and Full Route Cache, as it is a client-side API.</p>
</blockquote>
<p>All clear? No? Well you're not alone.</p>
<h2 id="heading-no-instantaneous-ux-feedback-when-loading">No instantaneous UX feedback when loading</h2>
<p>If you're a user of Next.js and just added standard pages, you surely will have noticed, even on localhost, that sometimes it feels like the page is stale. You click a link, nothing happens and then, after a few moments, swoosh. This was less worse in the old PHP times where the browser indicated loading behaviour in the URL bar, then the page went white and users knew "aha the browser is loading a new page". With Next.js it feels like "nothing happens" and then suddenly the moment "ah ok well it did work".</p>
<p>Again, Next.js "solved" this by allowing you to add a loader.js at the level of the page you are loading. The problem is though: The loader has to be loaded - for the sake of performance. I'm not sure if I should laugh about this. The loader's goal, UX-wise, is to be <em>instantaneous</em> to provide user-feedback. Period.</p>
<p>Now <a class="user-mention" href="https://hashnode.com/@leerob">Lee Robinson</a> says that this is by design because otherwise they'd have to preload all the loading spinners because they wouldn't know when to load which and if you got like 100kb spinners this is way too much. This sounds like a "Well, we sat in a cave and thought about unrealistic use cases to prevent instead of asking real people". See, my current loading spinner has 100bytes. Take 10 and make them 10 times the size, that's 10kb in total (!!). Already this is <strong>exaggerating</strong> for my use-case but 10kb additional bundle size for my web application to feel fluid: not a problem at all, it's a joke of additional payload, especially if it was lazy-loaded after the critical content.</p>
<p>Logically, this current idea of how the loading spinner is implemented doesn't make sense at all. So here, Next is basically patronizing the developers in the sense of "I know better what's good for you".</p>
<p>Here are potential solutions:</p>
<ul>
<li><p>Let me choose which loading spinners to include in the bundle (e.g. by exporting a const or setting a global flag like <code>bundleAllLoaders: true</code> in the <code>next.config.js</code>)</p>
</li>
<li><p>Let me prefetch the spinner only, not the page. Here's a very reasonable case: A ticket creation form which will lead to the ticket details page afterwards. The details page cannot be prefetched fully because the ticket is about to be created so there is NO WAY of prefetching the page - but there is sense in prefetching the loader. So, let me call <code>router.prefetchLoader('/ticket/details/[id]')</code></p>
</li>
</ul>
<h2 id="heading-server-actions-were-published-without-security-advise">Server actions were published without security advise</h2>
<p>I don't wanna go too deep into that topic as I've made a video about it: <a target="_blank" href="https://www.youtube.com/watch?v=j0_g8Redd0A&amp;t=404s">https://www.youtube.com/watch?v=j0_g8Redd0A</a>. The thing is: If you use Server Actions as part of your component and load data with credential keys, the credentials are bridged via frontend without you noticing. Some people said in the video: Well, that's obvious because it's a closure. But in fact: It's not a normal closure. This isn't "standard javascript". Nothing here is obvious. Server Actions themselves aren't obvious because they're extracted from the actual component and in times where bundlers do the work and something like Server Actions are presented without further warning or advise considering that, I can expect that they're being extracted without passing my credentials.</p>
<p>Knowing what happens it's easy to bypass but the problem here again is: The upgrade essentially delivered a new problem. Maybe it would've been more clever to not allow the action definition in the component itself.</p>
<h2 id="heading-its-sometimes-painfully-slow">It's sometimes painfully slow</h2>
<p>When an SPA was slow it was pretty easy to debug. It was either the server response that took too long or something in your frontend code was rubbish. With the hybrid approach and the implicit caching mechanisms sometimes you're waiting and you have to dig deep to find the root cause - sometimes without any success.</p>
<p>What I find especially troublesome is the fact that the dev experience doesn't reflect the prod experience at all. It's known that Next produces an optimized output for npm run build but it's not clear on the other hand that sometimes the dev server is kinda hanging itself on a machine that can handle gaming, video cutting and coding at the same time. The worst part is that it's not clear why this happens but it doesn't just happen to me.</p>
<h2 id="heading-serverifying-frontend">Serverifying frontend</h2>
<p>I was thinking about this a lot. I'm a huge fan of SSR. But what worth is SSR for interactive apps like e.g. Canva?</p>
<p>I remember the times when headless was the way to go. We all used SPAs like React or Angular and loaded data from the server. A pretty good approach with the disadvantage of having a lot of code in the frontend. That however was solved by having the option to lazy load. Back then, we noticed we need immediate server-rendered pages especially for search engines so we rendered the SPAs output as well on the server and hydrated it.</p>
<p>Now, I feel like Next pushes hard on people to use server-side features as far as moving frontend functionality to the backend. I like having the option, but using the server for everything might not be the best advise.</p>
<p>Let's take for example <code>useOptimisticUpdate</code>. The sample shows how to trigger adding a Chat Message and how it can be shown in the frontend even before it's done adding on the backend (<a target="_blank" href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#optimistic-updates">https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#optimistic-updates</a>). It sounds good, and it isn't bad by all means. But the problem is, that you're not in control of the state. Once the server is done, it will update the state and re-render the list. Sounds good so far as well but here's my take:</p>
<p>If I wanted to create a ToDo-list that allows adding an entry and immediately being able to drag'n'drop it, this approach would interfere with the interaction and re-render the list. If there was some kind of middleware to the useOptimisticUpdate hook like <code>useOptimisticUpdate(..., { onBeforeUpdateFromServer } =&gt; finalState)</code> as well as a setState to control its content, this would be different. To overcome this, you'd need to complexify it as far as I'd choose another approach anyway e.g. using my <a target="_blank" href="https://unglitch.activeno.de">unglitch</a> library.</p>
<p>But that was just one very specific and, frankly, not limiting example because I'm not forced to use it.</p>
<p>What is a limiting factor however is the fact that every page request is now hitting the server. Transitioning with SPA routers between pages was rather simple because we were just replacing components in a parent wrapper and had the appropriate listeners to do easy transitions. Now, with Next.js this is still possible but it honestly feels like there's more mental load to it (<a target="_blank" href="https://github.com/vercel/next.js/discussions/42658">https://github.com/vercel/next.js/discussions/42658</a>).</p>
<p>Also, as stated, for super-interactive Apps like Spotify, I wouldn't want a frontend page switch to trigger backend at all as my Server Component might've fetched initial data that isn't supposed to be fetched anymore when we're already on frontend (e.g. a user id). Hence, I asked myself if I can trick portions of my Next.js app into acting like it was an SPA. Indeed I could, but I'm not sure about the implications:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SPAPageInsideNext</span>(<span class="hljs-params">{ searchParams }</span>) </span>{
  <span class="hljs-keyword">const</span> p = useSearchParams();
  <span class="hljs-keyword">const</span> r = useRouter();

  <span class="hljs-keyword">const</span> isFooInitially = searchParams.foo === <span class="hljs-string">"snoo"</span>;
  <span class="hljs-keyword">const</span> isFooNow = p.get(<span class="hljs-string">"foo"</span>) === <span class="hljs-string">"snoo"</span>;
  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      Is <span class="hljs-built_in">this</span> an SPA? 
      &lt;div&gt;isFooInitially = {isFooInitially ? <span class="hljs-string">"yes"</span> : <span class="hljs-string">"no"</span>}&lt;/div&gt;
      &lt;div&gt;isFooNow = {isFooNow ? <span class="hljs-string">"yes"</span> : <span class="hljs-string">"no"</span>}&lt;/div&gt;

      &lt;button
        <span class="hljs-keyword">type</span>=<span class="hljs-string">"button"</span>
        onClick={<span class="hljs-function">() =&gt;</span> {
          <span class="hljs-keyword">if</span> (p.get(<span class="hljs-string">"snoo"</span>) === <span class="hljs-string">"foo"</span>) {
            history.pushState({ foo: <span class="hljs-string">"snoo"</span> }, <span class="hljs-string">"foo-snoo"</span>, <span class="hljs-string">"/spa?foo=snoo"</span>);
          } <span class="hljs-keyword">else</span> {
            history.pushState({ snoo: <span class="hljs-string">"foo"</span> }, <span class="hljs-string">"sno-foo"</span>, <span class="hljs-string">"/spa?snoo=foo"</span>);
          }
        }}
      &gt;
        Toggle Param
      &lt;/button&gt;
    &lt;/div&gt;
  );
</code></pre>
<p>So instead of <code>router.push</code>, I'm using the native <code>history.pushState</code>, ensuring on reload it will be the correct URL (deeplinking) and it indeed triggers a re-render without server request (i thought it ignores the re-render part, but it makes sense considering onClick runs in the react lifecycle).</p>
<p><em><s>Is this good? I didn't dig deeper, so I can't tell yet. But it's definitely good to know. However I'm scared it comes with pitfalls I haven't discovered yet.</s></em></p>
<p>This is indeed an official solution as stated in (thanks to a Reddit user pinpointing to it) <a target="_blank" href="https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate">https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate</a> .</p>
<h2 id="heading-vercel-marketing-vs-vercel-target-grouphttpsnextjsorgblognext-14-1windowhistorypushstate-and-windowhistoryreplacestate"><a target="_blank" href="https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate">Vercel Marketing vs Vercel Target group</a></h2>
<p>I know it has been some time since Turbo was announced but the thing is: I feel taken for an idiot if the claims given are far beyond the truth - developers are seeking for things that are of value and if something has just 10% more speed than before, that's cool. But if you're saying that something has like 1000% more speed and in fact it's a lie (<a target="_blank" href="https://github.com/yyx990803/vite-vs-next-turbo-hmr/discussions/8">https://github.com/yyx990803/vite-vs-next-turbo-hmr/discussions/8</a>) it doesn't create a trustworthy bond. Just my two cents on this one.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I was lately seeing some React-only or even Angular apps that went without SSR approach. My first thought: Why not choose SSR, Next.js whatever. But at a second glance I noticed they were extremely speedy. <em>Everybody</em> nowadays says that rather than choosing React one should choose Next. I'm not so sure anymore. If you create a wonderfully splitted SPA with even manually controlled lazy-loading, React can serve you well, depending on your use-case.</p>
<p>Sure, for an e-commerce app with contentful pages, Next.js would probably be my first choice. I'm just re-evaluating the "Next.js for everything" case.</p>
<p>There's too many things in the open that I feel like I really want to try another framework (I love React, so maybe I'll give Remix a shot) to see if there's one that gives me less mental load and a better feeling of confidence.</p>
<hr />
<p>These are just the things that came to my mind recently.</p>
<p>So my primary questions are: What are your thoughts? And would you consider Next.js being an awesome choice for a fluid interaction-rich application like Spotify or Canva?</p>
<p>Cheers</p>
]]></content:encoded></item><item><title><![CDATA[The ultimate Supabase self-hosting Guide]]></title><description><![CDATA[Preamble: This guide is not intended for local / dev installation. Supabase already has a dev setup with the CLI - so use that one. This guide is for actual self-hosting and it's quite some honest work, definitely worth it.
If you don't wanna spend a...]]></description><link>https://blog.activeno.de/the-ultimate-supabase-self-hosting-guide</link><guid isPermaLink="true">https://blog.activeno.de/the-ultimate-supabase-self-hosting-guide</guid><category><![CDATA[supabase]]></category><category><![CDATA[self-hosted]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Fri, 25 Aug 2023 07:49:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1692951518033/ab702003-fd8a-46d8-8ead-046273a18885.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Preamble: This guide is not intended for local / dev installation. Supabase already has a</strong> <a target="_blank" href="https://supabase.com/docs/guides/cli/local-development"><strong>dev setup with the CLI</strong></a> <strong>- so use that one. This guide is for actual self-hosting and it's quite some honest work, definitely worth it.</strong></p>
<p><mark>If you don't wanna spend any money you can use my </mark> <a target="_blank" href="https://m.do.co/c/7b9e7ab65a3f"><mark>DigitalOcean Link</mark></a> - <mark>which gives you 200$ of Free Credit for 60 days, enough to do anything.</mark></p>
<hr />
<p>The standard self-hosting Guide from Supabase, as of this moment (August 2023) cannot be used for production deployments. It is insecure but it's gonna be the pathway for a proper setup.</p>
<p>The concept and the realization of this took me multiple days. Actually, there is a YouTube Video for you to follow up:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=wyUr_U6Cma4">https://www.youtube.com/watch?v=wyUr_U6Cma4</a></div>
<p> </p>
<p>The reason why I am also making it a blog article is simple: The blog article is technical foundation of my YouTube Video and allows you and me to easily copy config data.</p>
<hr />
<h2 id="heading-1-setting-up-storage-beyond-amazon-s3">1. Setting up Storage (beyond Amazon S3)</h2>
<p>Yup, this should be your first move. Simply because changing this afterwards is a Pain in the A**. And the best part: You don't have to decide, you can just leave it as is (1.1)</p>
<h3 id="heading-option-1-volume-storage-you-probably-dont-need-s3">Option 1: Volume Storage 👉🏾 You probably don't need S3</h3>
<p>Supabase-as-a-Service (supabase.com) is using S3 Storage. What that is? Well, basically an API that allows storage of files on a server and that server can replicate and backup those files (that's the short version).</p>
<p>Now everybody goes "Yeah, backups and stuff and scaling, I need that". <strong>Nobody said you don't get that without S3.</strong> If you are using a Server from e.g. <a target="_blank" href="https://hetzner.cloud/?ref=9g20wOxOZzvt"><strong>Hetzner Cloud</strong></a> then you can simply choose their <strong>Volumes</strong> for file storage and activate backups. The same goes for providers such as <a target="_blank" href="https://m.do.co/c/7b9e7ab65a3f"><strong>DigitalOcean</strong></a>. So basically you're all set with normal file storage as well.</p>
<p>In fact: Simply using standard volumes will probably be faster than adding another remote layer with S3, but just saying.</p>
<h3 id="heading-option-2-s3-storage-self-hosted-miniohttpsminio">Option 2: S3 Storage 👉🏾 Self-Hosted <a target="_blank" href="https://min.io">minio</a></h3>
<p>This approach works the same way for all S3 compatible storages, in my case, I just want to show you how easy it is to create one that is self-hosted. Surely, you can use AWS S3 as well.</p>
<p>Create a new Server (we will call it <code>supa-storage</code> for now) on the Provider of your choice (mine -&gt; <a target="_blank" href="https://hetzner.cloud/?ref=9g20wOxOZzvt"><strong>Hetzner Cloud</strong></a>) with Docker. Most Providers have Servers with Docker preinstalled. If not, simply go with Ubuntu and install Docker (I won't cover the installation of Docker).</p>
<p>Step 1: Create a <code>docker-compose.yml</code> file as follows</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.8'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">minio:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">quay.io/minio/minio</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">minio</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./minio-data:/data</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">MINIO_ROOT_USER:</span> <span class="hljs-string">your_root_user</span>
      <span class="hljs-attr">MINIO_ROOT_PASSWORD:</span> <span class="hljs-string">your_root_password</span>
      <span class="hljs-attr">MINIO_SERVER_URL:</span> <span class="hljs-string">https://storage.yourdomain.com</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">server</span> <span class="hljs-string">/data</span> <span class="hljs-string">--console-address</span> <span class="hljs-string">":9090"</span>
  <span class="hljs-attr">nginx:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">'jc21/nginx-proxy-manager:latest'</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'80:80'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'81:81'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'443:443'</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx-data:/data</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx-letsencrypt:/etc/letsencrypt</span>
</code></pre>
<h4 id="heading-run-it">Run it</h4>
<p><code>docker compose up -d</code></p>
<h4 id="heading-manage-nginx">Manage nginx</h4>
<p>Now wait until the containers are started. Once everything is started you will want to open <code>http://your-server-ip:81</code> in your browser. This will open a login window to the <code>nginx proxy manager</code> also called <code>npm</code> (but has nothing to do with npmjs ✌️). You login with <code>admin@example.com</code> and <code>changeme</code>. Once you are logged in you are asked for new credentials, use whatever you want.</p>
<p>The first thing we want to do now is self-protect the proxy manager. Go to Proxy Hosts and click to add a new one. Now, again, you'll need a subdomain to do that: Like <code>proxy.your-domain.com</code> which points to your server.</p>
<p>Then you can enter in the form <code>proxy.yourdomain.com</code>, you leave scheme as <code>http</code> (yes, not https!), as hostname you enter <code>127.0.0.1</code> and choose <code>Port 81</code> and switch on "Block Common Exploits" and in the <code>SSL</code> tab you choose "Request a new certificate" and tick "Force SSL", "HTTP/2" and "HSTS enabled".</p>
<p>Kinda like this</p>
<table><tbody><tr><td><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1692536319727/3094d921-d9b5-4c1c-b26d-bf27e9b2ed06.png" alt class="image--center mx-auto" /></p></td><td><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1692536600898/13e3e99e-a1fe-43ab-9f80-c50bfe481ea8.png" alt class="image--center mx-auto" /></p></td></tr></tbody></table>

<p>Now you can go and open <code>proxy.yourdomain.com</code> in the browser and you'll have the same thing but protected. Okay, nice.</p>
<h4 id="heading-secure-minio">Secure minio</h4>
<p>Within the proxy manager you want to add 2 hosts for minio.</p>
<ol>
<li><p><strong>Storage Host</strong>: Nearly the same settings as above but this time <code>Domain=storage.yourdomain.com</code>, <code>hostname=minio</code>, <code>port=9000</code> and also activate <code>Websocket Support</code>. Also add the following in the Advanced Tab:</p>
<pre><code class="lang-yaml"> <span class="hljs-comment"># Allow special characters in headers</span>
 <span class="hljs-string">ignore_invalid_headers</span> <span class="hljs-string">off;</span>
 <span class="hljs-comment"># Allow any size file to be uploaded.</span>
 <span class="hljs-comment"># Set to a value such as 1000m; to restrict file size to a specific value</span>
 <span class="hljs-string">client_max_body_size</span> <span class="hljs-number">0</span><span class="hljs-string">;</span>
 <span class="hljs-comment"># Disable buffering</span>
 <span class="hljs-string">proxy_buffering</span> <span class="hljs-string">off;</span>
 <span class="hljs-string">proxy_request_buffering</span> <span class="hljs-string">off;</span>
</code></pre>
<p> (yeah it is normal that when you visit in the browser you get an error)</p>
</li>
<li><p><strong>Dashboard Host</strong>: Same as before but <code>hostname=storage-dashboard.yourdomain.com</code> and <code>port=9090</code> and nothing in the Advanced Tab.</p>
</li>
</ol>
<p>Now you can visit <code>storage-dashboard.yourdomain.com</code> and login with the credentials from what you provided in the <code>docker-compose.yml</code></p>
<h4 id="heading-prove-that-minio-is-working">Prove that minio is working</h4>
<p>Login to <code>minio</code> Dashboard and go to Buckets and create a new bucket <code>mytestbucket</code>. Then go to <code>Object Browser -&gt; mytestbucket</code> and try uploading any file. Worked? Nice!</p>
<h4 id="heading-get-your-s3-credentials-from-minio-for-supabase">Get your S3 Credentials from minio for Supabase</h4>
<p>Go to <strong>Access Keys</strong> and click on <strong>Create Access Key</strong>. Write down both the <strong>Access</strong> and the <strong>Secret Key</strong>. You will need it soon.</p>
<h4 id="heading-take-the-minio-dashboard-offline">Take the minio Dashboard offline</h4>
<p>Now after setting it up you don't need the Dashboard anymore. You can disable it's public access in your proxy manager by simply clicking <code>disable</code>. You can reactivate it anytime.</p>
<h2 id="heading-2-setting-up-the-supabase-server">2. Setting up the Supabase Server</h2>
<p>Create a new Server Instance - do not reuse your S3 server for that (if you have that. If you don't have an S3 Server: you're all set with 1 server instance).</p>
<h4 id="heading-first-things-first"><strong>First things first:</strong></h4>
<ol>
<li><p>Log in to that server and run <code>git clone --depth 1</code> <a target="_blank" href="https://github.com/supabase/supabase"><code>https://github.com/supabase/supabase</code></a> .</p>
</li>
<li><p><code>cd supabase/docker</code></p>
</li>
<li><p><code>cp docker-compose.yml docker-compose.yml.bkp</code> (just so you have a backup file to compare)</p>
</li>
<li><p><code>cp .env.example .env</code></p>
</li>
</ol>
<h4 id="heading-secure-the-kong-api-gateway">Secure the Kong API Gateway:</h4>
<p>Search for the <code>kong:</code> section in <code>docker-compose.yml</code> and get rid of the <code>ports:</code> in there.</p>
<h4 id="heading-do-not-route-supabase-studio-dashboard-with-kong">Do not route Supabase Studio (Dashboard) with Kong</h4>
<p>Go to <code>docker/volumes/api/kong.yml</code> and scroll down where it says <code>"Protected Dashboard"</code> . Remove that whole part of <code>- dashboard</code>. Yes, everything. We do not want kong to route there.</p>
<h4 id="heading-add-nginx-and-authelia-to-supabase">Add nginx and authelia to Supabase</h4>
<p>In your <code>docker-compose.yml</code> go to the top where it says <code>services:</code> . From there want to add 2 additional services: <a target="_blank" href="https://www.authelia.com/">authelia</a> and <a target="_blank" href="https://nginxproxymanager.com/setup/">nginx Proxy Manager</a>.</p>
<p>Please note: If you are fine with protecting Supabase Dashboard with HTTPS + Basic Auth (which is completely fine) then you don't need to add <a target="_blank" href="https://www.authelia.com/">authelia</a> and can also skip all of the <code>authelia</code> setup later as you can simply activate Basic Auth with the Proxy Manager. But if you wanna be more flexible in auth terms, e.g. multi-factor, then authelia is for you!</p>
<p>Adapt the <code>docker-compose.yml</code> file with these 2 additional services:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">authelia:</span>
  <span class="hljs-attr">container_name:</span> <span class="hljs-string">authelia</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">authelia/authelia</span>
  <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
  <span class="hljs-attr">expose:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-number">9091</span>
  <span class="hljs-attr">volumes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">./authelia/config:/config</span>
  <span class="hljs-attr">environment:</span>
    <span class="hljs-attr">TZ:</span> <span class="hljs-string">'Europe/Berlin'</span>

<span class="hljs-attr">nginx:</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">'jc21/nginx-proxy-manager:latest'</span>
  <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-comment"># These ports are in format &lt;host-port&gt;:&lt;container-port&gt;</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'80:80'</span> <span class="hljs-comment"># Public HTTP Port</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'443:443'</span> <span class="hljs-comment"># Public HTTPS Port</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'81:81'</span> <span class="hljs-comment"># Admin Web Port</span>
  <span class="hljs-attr">volumes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx-data:/data</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx-letsencrypt:/etc/letsencrypt</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx-snippets:/snippets:ro</span>
  <span class="hljs-attr">environment:</span>
    <span class="hljs-attr">TZ:</span> <span class="hljs-string">'Europe/Berlin'</span>
</code></pre>
<p>We will configure authelia later, first we continue with the Supabase Config.</p>
<hr />
<h3 id="heading-configure-your-custom-s3">Configure your custom S3</h3>
<p><mark>You can skip this if you don't have S3</mark>.</p>
<p>Go to <code>docker-compose.yml</code>, search for <code>storage</code> and go to its <code>environment</code> definitions.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">environment:</span>
    <span class="hljs-comment"># ...other definitions</span>
    <span class="hljs-attr">STORAGE_BACKEND:</span> <span class="hljs-string">s3</span>
    <span class="hljs-attr">GLOBAL_S3_BUCKET:</span> <span class="hljs-string">supabase</span>
    <span class="hljs-attr">GLOBAL_S3_ENDPOINT:</span> <span class="hljs-string">https://storage.yourdomain.com</span>
    <span class="hljs-attr">GLOBAL_S3_PROTOCOL:</span> <span class="hljs-string">https</span> 
    <span class="hljs-attr">REGION:</span> <span class="hljs-string">eu-south</span> <span class="hljs-comment">#whatever your region is</span>
    <span class="hljs-attr">AWS_DEFAULT_REGION:</span> <span class="hljs-string">eu-south</span>

    <span class="hljs-comment">#this next one seems important for minio</span>
    <span class="hljs-attr">GLOBAL_S3_FORCE_PATH_STYLE:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">AWS_ACCESS_KEY_ID:</span> <span class="hljs-string">your_s3_access_key</span>
    <span class="hljs-attr">AWS_SECRET_ACCESS_KEY:</span> <span class="hljs-string">your_secret_s3_access_key</span>
</code></pre>
<h3 id="heading-if-you-do-not-use-s3">If you do not use S3</h3>
<p>You might (or not) want to change the path where files are stored. See <code>volumes:</code> section in <code>docker-compose.yml -&gt; storage.volumes</code></p>
<hr />
<h3 id="heading-configure-credentials">Configure Credentials:</h3>
<p>Go to your <code>.env</code> file.</p>
<ol>
<li><p>Set <code>POSTGRES_PASSWORD=some-very-complicated-database-password</code></p>
</li>
<li><p>Generate and set random <code>JWT_SECRET=...</code> with <code>node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"</code></p>
</li>
<li><p>Us the self-hosting Guide on supabase.com (<a target="_blank" href="https://supabase.com/docs/guides/self-hosting/docker#generate-api-keys">https://supabase.com/docs/guides/self-hosting/docker#generate-api-keys</a>) to generate <code>ANON_KEY=</code> and <code>SERVICE_ROLE_KEY=</code> with your <code>JWT_SECRET</code> and set both in your <code>.env</code></p>
</li>
<li><p>Make sure to also set proper keys for <code>LOGFLARE_LOGGER_BACKEND_API_KEY=</code> and <code>LOGFLARE_API_KEY=</code> (I think they are the same but if anyone else from the Supabase Team or so knows better: Tell me)</p>
</li>
</ol>
<h3 id="heading-configure-studio-and-api">Configure Studio and API:</h3>
<p>Go to your <code>.env</code> file again.</p>
<ol>
<li><p>Set <code>API_EXTERNAL_URL=your-api.yourdomain.com</code> to whatever you want it to be. This is where all Supabase API requests can and will be sent.</p>
</li>
<li><p>Set <code>SUPABASE_PUBLIC_URL=your-api.yourdomain.com</code> to wherever you want to access the Dashboard.</p>
</li>
</ol>
<h3 id="heading-configure-your-page">Configure your Page:</h3>
<p>As always you'll have an app using Supabase. Now that app is running somewhere. And that somewhere we need to configure. Upfront: You can change that later by simply adapting the env variables and restarting the container (see Section 6).</p>
<pre><code class="lang-yaml"><span class="hljs-string">SITE_URL=https://yourapp.com</span>

<span class="hljs-comment"># use whatever additional urls you need</span>
<span class="hljs-string">ADDITIONAL_REDIRECT_URLS=http://localhost:3000,http://localhost:6000</span>
</code></pre>
<h3 id="heading-configure-postgres-access">Configure Postgres Access</h3>
<p>If you want to access the Database directly, from the outside, then you simply have to remove the <code>127.0.0.1</code> from the <code>docker-compose-yml -&gt; db.ports:</code> section</p>
<h3 id="heading-configure-e-mail">Configure E-Mail:</h3>
<p>I won't cover this in detail as it's not helpful for this specific guide and not required. Simply search for <code>email</code> or <code>SMTP</code> in the <code>.env</code> file. You don't even need to configure that, not even in production - if you are anyways generating mails on your own (which most production sites do).</p>
<hr />
<h2 id="heading-3-starting-the-supabase-nginx-server">3. Starting the Supabase + nginx Server</h2>
<p>Since we did the whole configuration part already and since we removed Supabases default security holes we can now start all containers:</p>
<ol>
<li><p><code>docker compose pull</code></p>
</li>
<li><p><code>docker compose up -d</code></p>
</li>
</ol>
<p>Wait for all containers to have started and check if everything is running fine with <code>docker ps</code> -&gt; if any of the containers says something like <code>Restarted X Seconds ago</code> it is most likely that there's some failure happening. Unusual with my setup though 🫶🏾.</p>
<h2 id="heading-4-make-it-run">4. Make it run!</h2>
<p>The containers are running but Supabase isn't accessible - yet. However, our nginx proxy is!</p>
<ol>
<li><p><strong>Protect the Proxy</strong> with itself first, see Section 3.2 where this was already explained including the initial credentials</p>
</li>
<li><p>Now go to your protected proxy on e.g. <code>supabase-proxy.yourdomain.com</code></p>
</li>
<li><p><strong>Setup the API Proxy:</strong> Add a new proxy with <code>your-api.your domain.com</code>, <code>scheme=http</code>, <code>hostname=kong</code> , <code>port=8000</code> . Activate <strong>Websocket Support</strong> and in the SSL Tab request new certificate and activate all checkboxes</p>
</li>
<li><p><strong>Setup the Dashboard Proxy</strong>:</p>
<ol>
<li><p>Same as API Proxy but with <code>studio.yourdomain.com</code> and this time use <code>hostname=studio</code> , <code>port=3000</code> .</p>
</li>
<li><p>Since Studio accesses the storage and auth service directly we need <s>to go to the Custom Locations and </s> (<mark>Update</mark>: Don't do this in Custom Locations Tab as this will not forward everything we need) make sure that the path <code>/storage</code> and <code>/auth</code> is forwarded to <code>hostname=kong</code> and <code>port=8000</code> instead and we also need to make sure that Authorization (if later enabled) is always passed downwards.</p>
<p> How? Easy: Go to <strong><em>Advanced</em></strong> Tab and configure this:</p>
<pre><code class="lang-nginx"> <span class="hljs-attribute">location</span> / {
   <span class="hljs-attribute">proxy_set_header</span>  Authorization <span class="hljs-variable">$http_authorization</span>;
   <span class="hljs-attribute">proxy_pass_header</span> Authorization;
   <span class="hljs-attribute">proxy_pass</span> <span class="hljs-variable">$forward_scheme</span>://studio:<span class="hljs-number">3000</span>;
 }

 <span class="hljs-attribute">location</span> /storage {
   <span class="hljs-attribute">proxy_set_header</span>  Authorization <span class="hljs-variable">$http_authorization</span>;
   <span class="hljs-attribute">proxy_pass_header</span> Authorization;
   <span class="hljs-attribute">proxy_pass</span> <span class="hljs-variable">$forward_scheme</span>://kong:<span class="hljs-number">8000</span>;
 }

 <span class="hljs-attribute">location</span> /auth {
   <span class="hljs-attribute">proxy_set_header</span>  Authorization <span class="hljs-variable">$http_authorization</span>;
   <span class="hljs-attribute">proxy_pass_header</span> Authorization;
   <span class="hljs-attribute">proxy_pass</span> <span class="hljs-variable">$forward_scheme</span>://kong:<span class="hljs-number">8000</span>;
</code></pre>
</li>
</ol>
</li>
</ol>
<p>    }</p>
<pre><code class="lang-plaintext">
    3. When both done: Save it.
</code></pre>
<p>Now your Supabase Studio should be publicly accessible under <code>studio.yourdomain.com</code> / without Protection though - yet.</p>
<h4 id="heading-basic-auth">Basic Auth</h4>
<p>As stated above, you can simply use Basic Auth now and later on upgrade to some more sophisticated stuff like authelia (no issue in upgrading later). To do that, go to <code>Access Lists</code> in the proxy manager, create a new one, give it any name, check <code>Satisfy Any</code>, go to <code>Authorization</code> Tab and enter the username/password combo you'd like.</p>
<p>Now go to your Proxy Host <code>studio...</code> and click <code>edit</code> and select your created Access List there. Save it.</p>
<p>When you now visit <code>studio.yourdomain.com</code> it will be protected and ask for username/pass.</p>
<h2 id="heading-just-a-sidenote-you-could-stop-at-this-point">Just a sidenote: You could stop at this point</h2>
<p>Like, for real. You are having a full-fledged self-hosted setup already. Felt complex, wasn't all too complex in the end, was it?</p>
<p><img src="https://media.giphy.com/media/cMPdlbcUKl3xkMCyD3/giphy.gif" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-5-more-than-basic-auth-authelia">5. More than Basic Auth: Authelia</h2>
<p>Authelia is another Layer running in between nginx to protect specific routes with Authentication - and Authelia can do Multi-Factor, if you want that.</p>
<p>This isn't extremely intuitive, but rather unusually <em>complex</em>. However only if you don't know how to do that. Now since I've put in all the work of researching you can just simply copy and paste. So yeah, I've done it for you.</p>
<p>We want to get rid of our Basic Authentication for Supabase Studio and instead have a nice-looking login screen.</p>
<p>We already installed <code>authelia</code> as part of our Stack (check the <code>docker-compose.yml</code> above) 😎. So we only need to configure it now.</p>
<h3 id="heading-51-adapt-the-default-configuration">5.1 Adapt the Default Configuration</h3>
<p>Open up the file <code>authelia/config/configuration.yml</code> and if you want, you can empty it because we will only use a small portion of it to get started (you can dig deeper into the config in their documentation).</p>
<p>This is my initial config to get started (search for <code>yourdomain.com</code> to see the things you have to change and please also go through it for changing secrets before you actually go to production):</p>
<pre><code class="lang-yaml"><span class="hljs-comment">## The theme to display: light, dark, grey, auto.</span>
<span class="hljs-attr">theme:</span> <span class="hljs-string">light</span>

<span class="hljs-comment">## The secret used to generate JWT tokens when validating user identity by email confirmation. JWT Secret can also be</span>
<span class="hljs-comment">## set using a secret: https://www.authelia.com/c/secrets</span>
<span class="hljs-attr">jwt_secret:</span> <span class="hljs-string">a_very_important_secret</span>

<span class="hljs-attr">default_redirection_url:</span> <span class="hljs-string">https://google.com</span> <span class="hljs-comment"># confusing haxxors</span>
<span class="hljs-attr">default_2fa_method:</span> <span class="hljs-string">""</span>

<span class="hljs-attr">server:</span>
  <span class="hljs-attr">host:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>
  <span class="hljs-attr">port:</span> <span class="hljs-number">9091</span>
  <span class="hljs-attr">path:</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">enable_pprof:</span> <span class="hljs-literal">false</span>
  <span class="hljs-attr">enable_expvars:</span> <span class="hljs-literal">false</span>
  <span class="hljs-attr">disable_healthcheck:</span> <span class="hljs-literal">false</span>
  <span class="hljs-attr">read_buffer_size:</span> <span class="hljs-number">4096</span>
  <span class="hljs-attr">write_buffer_size:</span> <span class="hljs-number">4096</span>

  <span class="hljs-comment">## Server headers configuration/customization.</span>
  <span class="hljs-attr">headers:</span>
    <span class="hljs-comment">## The CSP Template. Read the docs.</span>
    <span class="hljs-attr">csp_template:</span> <span class="hljs-string">""</span>

  <span class="hljs-comment">## Authelia by default doesn't accept TLS communication on the server port. This section overrides this behaviour.</span>
  <span class="hljs-attr">tls:</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">certificate:</span> <span class="hljs-string">""</span>
    <span class="hljs-attr">client_certificates:</span> []

<span class="hljs-attr">log:</span>
  <span class="hljs-attr">level:</span> <span class="hljs-string">debug</span>

<span class="hljs-attr">totp:</span>
  <span class="hljs-attr">issuer:</span> <span class="hljs-string">yourdomain.com</span> <span class="hljs-comment">#your authelia top-level domain</span>
  <span class="hljs-attr">period:</span> <span class="hljs-number">30</span>
  <span class="hljs-attr">digits:</span> <span class="hljs-number">6</span>
  <span class="hljs-attr">algorithm:</span> <span class="hljs-string">sha1</span>
  <span class="hljs-attr">skew:</span> <span class="hljs-number">1</span>

<span class="hljs-attr">authentication_backend:</span>
  <span class="hljs-attr">file:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/config/users_database.yml</span>
    <span class="hljs-attr">watch:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">search:</span>
      <span class="hljs-attr">email:</span> <span class="hljs-literal">false</span>
      <span class="hljs-attr">case_insensitive:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">password:</span>
      <span class="hljs-attr">algorithm:</span> <span class="hljs-string">argon2</span>
      <span class="hljs-attr">argon2:</span>
        <span class="hljs-attr">variant:</span> <span class="hljs-string">argon2id</span>
        <span class="hljs-attr">iterations:</span> <span class="hljs-number">3</span>
        <span class="hljs-attr">memory:</span> <span class="hljs-number">65536</span>
        <span class="hljs-attr">parallelism:</span> <span class="hljs-number">4</span>
        <span class="hljs-attr">key_length:</span> <span class="hljs-number">32</span>
        <span class="hljs-attr">salt_length:</span> <span class="hljs-number">16</span>
  <span class="hljs-comment">## Password Reset Options.</span>
  <span class="hljs-attr">password_reset:</span>
    <span class="hljs-comment">## Disable both the HTML element and the API for reset password functionality.</span>
    <span class="hljs-attr">disable:</span> <span class="hljs-literal">false</span>
    <span class="hljs-comment">## External reset password url that redirects the user to an external reset portal. This disables the internal reset</span>
    <span class="hljs-comment">## functionality.</span>
    <span class="hljs-attr">custom_url:</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">refresh_interval:</span> <span class="hljs-string">5m</span>


<span class="hljs-attr">password_policy:</span>
  <span class="hljs-comment">## The standard policy allows you to tune individual settings manually.</span>
  <span class="hljs-attr">standard:</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">min_length:</span> <span class="hljs-number">8</span>
    <span class="hljs-attr">max_length:</span> <span class="hljs-number">0</span>
    <span class="hljs-attr">require_uppercase:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">require_lowercase:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">require_number:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">require_special:</span> <span class="hljs-literal">true</span>

  <span class="hljs-comment">## zxcvbn is a well known and used password strength algorithm. It does not have tunable settings.</span>
  <span class="hljs-attr">zxcvbn:</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">min_score:</span> <span class="hljs-number">3</span>


<span class="hljs-attr">access_control:</span>
  <span class="hljs-comment">## Default policy can either be 'bypass', 'one_factor', 'two_factor' or 'deny'. It is the policy applied to any</span>
  <span class="hljs-comment">## resource if there is no policy to be applied to the user.</span>
  <span class="hljs-attr">default_policy:</span> <span class="hljs-string">deny</span>
  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">domain:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">"auth.yourdomain.com"</span>
      <span class="hljs-attr">policy:</span> <span class="hljs-string">bypass</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">domain:</span> <span class="hljs-string">"studio.yourdomain.com"</span>
      <span class="hljs-attr">policy:</span> <span class="hljs-string">one_factor</span>

<span class="hljs-attr">session:</span>
  <span class="hljs-comment">## The name of the session cookie.</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">authelia_session</span>
  <span class="hljs-attr">domain:</span> <span class="hljs-string">yourdomain.com</span>
  <span class="hljs-comment">## https://www.authelia.com/c/session#same_site</span>
  <span class="hljs-attr">same_site:</span> <span class="hljs-string">lax</span>

  <span class="hljs-comment">## The secret to encrypt the session data. </span>
  <span class="hljs-comment">## This is only used with Redis / Redis Sentinel.</span>
  <span class="hljs-attr">secret:</span> <span class="hljs-string">insecure_session_secret</span>
  <span class="hljs-comment">## The time before the cookie expires and the session is destroyed if remember me IS NOT selected.</span>
  <span class="hljs-attr">expiration:</span> <span class="hljs-string">1h</span>
  <span class="hljs-comment">## The inactivity time before the session is reset. If expiration is set to 1h, and this is set to 5m, if the user</span>
  <span class="hljs-comment">## does not select the remember me option their session will get destroyed after 1h, or after 5m since the last time</span>
  <span class="hljs-comment">## Authelia detected user activity.</span>
  <span class="hljs-attr">inactivity:</span> <span class="hljs-string">5m</span>
  <span class="hljs-comment">## The time before the cookie expires and the session is destroyed if remember me IS selected.</span>
  <span class="hljs-comment">## Value of -1 disables remember me.</span>
  <span class="hljs-attr">remember_me_duration:</span> <span class="hljs-string">1M</span>

<span class="hljs-comment"># security measures against hackers</span>
<span class="hljs-attr">regulation:</span>
  <span class="hljs-attr">max_retries:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">find_time:</span> <span class="hljs-string">2m</span>
  <span class="hljs-attr">ban_time:</span> <span class="hljs-string">30m</span>

<span class="hljs-attr">storage:</span>
  <span class="hljs-attr">encryption_key:</span> <span class="hljs-string">a_very_important_secret</span>
  <span class="hljs-attr">local:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/config/db.sqlite3</span>

<span class="hljs-attr">notifier:</span>
  <span class="hljs-attr">disable_startup_check:</span> <span class="hljs-literal">false</span>
  <span class="hljs-comment">## File System (Notification Provider)</span>
  <span class="hljs-attr">filesystem:</span>
    <span class="hljs-attr">filename:</span> <span class="hljs-string">/config/notification.txt</span>
</code></pre>
<p>Restart authelia with <code>docker compose stop authelia</code> and <code>docker compose up -d --force-recreate authelia</code> .</p>
<h3 id="heading-52-configure-your-users">5.2 Configure your users:</h3>
<p>In the <code>authelia/config</code> folder you should find now a <code>users_database.yml</code> file.</p>
<p>Open it and you'll find a username (<code>authelia</code>) with a password (also <code>authelia</code> but encrypted with <code>argon2</code>). So if you want to change it, you need to replace those in that file and restart authelia container again. I'll leave it for now with <code>username=authelia/pw=authelia</code> .</p>
<h3 id="heading-53-configure-reusable-snippets">5.3 Configure reusable snippets</h3>
<p>On your actual server you should see <code>./nginx-data</code> folder as well as <code>./nginx-letsencrypt</code> folder in your <code>supabase/docker</code> directory. Now create a new directory next to those with the name <code>nginx-snippets</code> .</p>
<p>Now in this directory you create 2 files (note that in the second file we use <code>auth.yourdomain.com</code> which must be whatever you wanna use):</p>
<h4 id="heading-nginx-snippetsauthelia-locationconf">nginx-snippets/authelia-location.conf</h4>
<pre><code class="lang-nginx"><span class="hljs-comment"># File: nginx-snippets/authelia-location.conf</span>

<span class="hljs-attribute">set</span> <span class="hljs-variable">$upstream_authelia</span> http://authelia:9091/api/verify;

<span class="hljs-comment">## Virtual endpoint created by nginx to forward auth requests.</span>
<span class="hljs-attribute">location</span> /authelia {
    <span class="hljs-comment">## Essential Proxy Configuration</span>
    internal;
    <span class="hljs-attribute">proxy_pass</span> <span class="hljs-variable">$upstream_authelia</span>;

    <span class="hljs-comment">## Headers</span>
    <span class="hljs-comment">## The headers starting with X-* are required.</span>
    <span class="hljs-attribute">proxy_set_header</span> X-Original-URL <span class="hljs-variable">$scheme</span>://<span class="hljs-variable">$http_host</span><span class="hljs-variable">$request_uri</span>;
    <span class="hljs-attribute">proxy_set_header</span> X-Original-Method <span class="hljs-variable">$request_method</span>;
    <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Method <span class="hljs-variable">$request_method</span>;
    <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;
    <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Host <span class="hljs-variable">$http_host</span>;
    <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Uri <span class="hljs-variable">$request_uri</span>;
    <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-For <span class="hljs-variable">$remote_addr</span>;
    <span class="hljs-attribute">proxy_set_header</span> Content-Length <span class="hljs-string">""</span>;
    <span class="hljs-attribute">proxy_set_header</span> Connection <span class="hljs-string">""</span>;

    <span class="hljs-comment">## Basic Proxy Configuration</span>
    <span class="hljs-attribute">proxy_pass_request_body</span> <span class="hljs-literal">off</span>;
    <span class="hljs-attribute">proxy_next_upstream</span> <span class="hljs-literal">error</span> timeout invalid_header http_500 http_502 http_503; <span class="hljs-comment"># Timeout if the real server is dead</span>
    <span class="hljs-attribute">proxy_redirect</span> http:// <span class="hljs-variable">$scheme</span>://;
    <span class="hljs-attribute">proxy_http_version</span> <span class="hljs-number">1</span>.<span class="hljs-number">1</span>;
    <span class="hljs-attribute">proxy_cache_bypass</span> <span class="hljs-variable">$cookie_session</span>;
    <span class="hljs-attribute">proxy_no_cache</span> <span class="hljs-variable">$cookie_session</span>;
    <span class="hljs-attribute">proxy_buffers</span> <span class="hljs-number">4</span> <span class="hljs-number">32k</span>;
    <span class="hljs-attribute">client_body_buffer_size</span> <span class="hljs-number">128k</span>;

    <span class="hljs-comment">## Advanced Proxy Configuration</span>
    <span class="hljs-attribute">send_timeout</span> <span class="hljs-number">5m</span>;
    <span class="hljs-attribute">proxy_read_timeout</span> <span class="hljs-number">240</span>;
    <span class="hljs-attribute">proxy_send_timeout</span> <span class="hljs-number">240</span>;
    <span class="hljs-attribute">proxy_connect_timeout</span> <span class="hljs-number">240</span>;
}
</code></pre>
<p><strong>nginx-snippets/authelia-authrequest.conf</strong></p>
<pre><code class="lang-nginx"><span class="hljs-comment"># File: nginx-snippets/authelia-authrequest.conf</span>

<span class="hljs-comment">## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.</span>
<span class="hljs-attribute">auth_request</span> /authelia;

<span class="hljs-comment">## Set the $target_url variable based on the original request.</span>

<span class="hljs-comment">## Comment this line if you're using nginx without the http_set_misc module.</span>
<span class="hljs-attribute">set_escape_uri</span> <span class="hljs-variable">$target_url</span> <span class="hljs-variable">$scheme</span>://<span class="hljs-variable">$http_host</span><span class="hljs-variable">$request_uri</span>;

<span class="hljs-comment">## Uncomment this line if you're using NGINX without the http_set_misc module.</span>
<span class="hljs-comment"># set $target_url $scheme://$http_host$request_uri;</span>

<span class="hljs-comment">## Save the upstream response headers from Authelia to variables.</span>
<span class="hljs-attribute">auth_request_set</span> <span class="hljs-variable">$user</span> <span class="hljs-variable">$upstream_http_remote_user</span>;
<span class="hljs-attribute">auth_request_set</span> <span class="hljs-variable">$groups</span> <span class="hljs-variable">$upstream_http_remote_groups</span>;
<span class="hljs-attribute">auth_request_set</span> <span class="hljs-variable">$name</span> <span class="hljs-variable">$upstream_http_remote_name</span>;
<span class="hljs-attribute">auth_request_set</span> <span class="hljs-variable">$email</span> <span class="hljs-variable">$upstream_http_remote_email</span>;

<span class="hljs-comment">## Inject the response headers from the variables into the request made to the backend.</span>
<span class="hljs-attribute">proxy_set_header</span> Remote-User <span class="hljs-variable">$user</span>;
<span class="hljs-attribute">proxy_set_header</span> Remote-Groups <span class="hljs-variable">$groups</span>;
<span class="hljs-attribute">proxy_set_header</span> Remote-Name <span class="hljs-variable">$name</span>;
<span class="hljs-attribute">proxy_set_header</span> Remote-Email <span class="hljs-variable">$email</span>;

<span class="hljs-comment">## If the subreqest returns 200 pass to the backend, if the subrequest returns 401 redirect to the portal.</span>
<span class="hljs-attribute">error_page</span> <span class="hljs-number">401</span> =<span class="hljs-number">302</span> https://auth.yourdomain.com/?rd=<span class="hljs-variable">$target_url</span>;
</code></pre>
<h4 id="heading-nginx-snippetsproxyconf">nginx-snippets/proxy.conf</h4>
<pre><code class="lang-nginx"><span class="hljs-comment">## Headers</span>
<span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;
<span class="hljs-attribute">proxy_set_header</span> X-Original-URL <span class="hljs-variable">$scheme</span>://<span class="hljs-variable">$http_host</span><span class="hljs-variable">$request_uri</span>;
<span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;
<span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Host <span class="hljs-variable">$http_host</span>;
<span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Uri <span class="hljs-variable">$request_uri</span>;
<span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Ssl <span class="hljs-literal">on</span>;
<span class="hljs-attribute">proxy_set_header</span> X-Forwarded-For <span class="hljs-variable">$remote_addr</span>;
<span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;
<span class="hljs-attribute">proxy_set_header</span> Connection <span class="hljs-string">""</span>;

<span class="hljs-comment">## Basic Proxy Configuration</span>
<span class="hljs-attribute">client_body_buffer_size</span> <span class="hljs-number">128k</span>;
<span class="hljs-attribute">proxy_next_upstream</span> <span class="hljs-literal">error</span> timeout invalid_header http_500 http_502 http_503; <span class="hljs-comment">## Timeout if the real server is dead.</span>
<span class="hljs-attribute">proxy_redirect</span>  http://  <span class="hljs-variable">$scheme</span>://;
<span class="hljs-attribute">proxy_http_version</span> <span class="hljs-number">1</span>.<span class="hljs-number">1</span>;
<span class="hljs-attribute">proxy_cache_bypass</span> <span class="hljs-variable">$cookie_session</span>;
<span class="hljs-attribute">proxy_no_cache</span> <span class="hljs-variable">$cookie_session</span>;
<span class="hljs-attribute">proxy_buffers</span> <span class="hljs-number">64</span> <span class="hljs-number">256k</span>;

<span class="hljs-comment">## Trusted Proxies Configuration</span>
<span class="hljs-comment"># The next line is very much recommended for security reasons!</span>
<span class="hljs-comment"># It works without the line but this would reduce security.</span>
<span class="hljs-comment"># So what is it and WHICH ip to set here?</span>
<span class="hljs-comment"># Basically you wanna set here the subnet your docker network is using here.</span>
<span class="hljs-comment"># The default subnet is 172.17.0.0/16 HOWEVER in my case</span>
<span class="hljs-comment"># this isn't true and probably for you as well:</span>
<span class="hljs-comment"># Check the ip-adress of your container with docker inspect container_id</span>
<span class="hljs-comment"># all of your containers in this stack probably </span>
<span class="hljs-comment"># have something like 172.x.x.x</span>
<span class="hljs-comment"># now basically put 2 zeros at the end and /16 and you have your subnet.</span>
<span class="hljs-comment"># in my case that was one container with '172.20.0.11'</span>
<span class="hljs-comment"># so it is 172.20.0.0/16</span>
<span class="hljs-comment"># ------</span>
<span class="hljs-comment"># set_real_ip_from 172.17.0.0/16;</span>
<span class="hljs-attribute">real_ip_header</span> X-Forwarded-For;
<span class="hljs-attribute">real_ip_recursive</span> <span class="hljs-literal">on</span>;

<span class="hljs-comment">## Advanced Proxy Configuration</span>
<span class="hljs-attribute">send_timeout</span> <span class="hljs-number">5m</span>;
<span class="hljs-attribute">proxy_read_timeout</span> <span class="hljs-number">360</span>;
<span class="hljs-attribute">proxy_send_timeout</span> <span class="hljs-number">360</span>;
<span class="hljs-attribute">proxy_connect_timeout</span> <span class="hljs-number">360</span>;
</code></pre>
<p>When you have added those snippets then do <code>docker compose stop authelia &amp;&amp; docker compose up -d --force-recreate authelia</code> .</p>
<h3 id="heading-54-setup-the-auth-portal">5.4 Setup the Auth Portal</h3>
<p>Go to your proxy manager web interface and add a new proxy host. This time <code>hostname=authelia</code> and <code>port=9091</code>. Activate "Block Common Exploits" and "Websockets Support", activate all Checkboxes in <strong>SSL</strong> Tab. Then go to "Advanced" Tab and add this:</p>
<pre><code class="lang-nginx"><span class="hljs-attribute">location</span> / {
    <span class="hljs-attribute">include</span> /snippets/proxy.conf;
    <span class="hljs-attribute">proxy_pass</span> <span class="hljs-variable">$forward_scheme</span>://<span class="hljs-variable">$server</span>:<span class="hljs-variable">$port</span>;
}
</code></pre>
<p>Done. You should be able to test it with your username/password (authelia/authelia if you haven't changed it yet.)</p>
<h3 id="heading-55-setup-studio-to-use-authelia">5.5 Setup Studio to use Authelia</h3>
<p>Edit your Supabase Studio Proxy Host and remove Basic Auth by choosing "Publicly Accessible" in the Access List (you can also leave it on but I haven't tried if double protection brings problems). Go to the advanced tab and add this:</p>
<pre><code class="lang-nginx"><span class="hljs-attribute">include</span> /snippets/authelia-location.conf;

<span class="hljs-attribute">location</span> / {
    <span class="hljs-attribute">include</span> /snippets/proxy.conf;
    <span class="hljs-attribute">include</span> /snippets/authelia-authrequest.conf;
    <span class="hljs-attribute">proxy_pass</span> <span class="hljs-variable">$forward_scheme</span>://<span class="hljs-variable">$server</span>:<span class="hljs-variable">$port</span>;
}

<span class="hljs-attribute">location</span> /storage {
    <span class="hljs-attribute">include</span> /snippets/proxy.conf;
    <span class="hljs-attribute">include</span> /snippets/authelia-authrequest.conf;
    <span class="hljs-attribute">proxy_pass</span> <span class="hljs-variable">$forward_scheme</span>://kong:<span class="hljs-number">8000</span>;
}

<span class="hljs-attribute">location</span> /auth {
    <span class="hljs-attribute">include</span> /snippets/proxy.conf;
    <span class="hljs-attribute">include</span> /snippets/authelia-authrequest.conf;
    <span class="hljs-attribute">proxy_pass</span> <span class="hljs-variable">$forward_scheme</span>://kong:<span class="hljs-number">8000</span>;
}
</code></pre>
<p>Done. Save it, load Studio and now Authelia protects your Studio. 🎉🤩</p>
<h2 id="heading-6-changing-configuration-in-a-running-environment">6. Changing configuration in a running environment</h2>
<p>Sometimes you need to just update values, such as when you e.g. updated the Postgres password or needed to change some url config. In that case you can bring the containers down with <code>docker compose down</code> (no worries, since we are using volumes, all data is kept) and then you can do <code>docker compose up -d</code> again loading the updated config.</p>
<hr />
<p>If you liked that, consider buying me a coffee ❤️ <a target="_blank" href="http://ko-fi.com/activenode">ko-fi.com/activenode</a></p>
]]></content:encoded></item><item><title><![CDATA[How to get rich in short time - Truth talk]]></title><description><![CDATA[Read this text. Read it carefully, read it slow. This isn't your usual throw-away text.
Don't be mad. This isn't clickbait. This is me talking facts, me talking truth. None of what I tell in this post is imagination. Everything's based on true storie...]]></description><link>https://blog.activeno.de/how-to-get-rich-in-short-time-truth-talk</link><guid isPermaLink="true">https://blog.activeno.de/how-to-get-rich-in-short-time-truth-talk</guid><category><![CDATA[work]]></category><category><![CDATA[mentalhealth]]></category><category><![CDATA[Mental Health]]></category><category><![CDATA[side project]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Mon, 19 Jun 2023 16:49:39 GMT</pubDate><content:encoded><![CDATA[<p>Read this text. Read it carefully, read it slow. This isn't your usual throw-away text.</p>
<p>Don't be mad. This isn't clickbait. This is me talking facts, me talking truth. None of what I tell in this post is imagination. Everything's based on true stories. Yeah, maybe money makes you happy - if you didn't get severe depression or burnout on the way. But you're different, right? Just built different. YOU are made for hustling.</p>
<p>The world of social media never seized to amaze me with all it's lies. Suicidal rate is up since we have social media (<a target="_blank" href="https://socialmediavictims.org/mental-health/suicide/">https://socialmediavictims.org/mental-health/suicide/</a>), especially, but not only, for teenagers. 99% of social media is a lie and some get rich by lying because you click it.</p>
<p>I've had friends telling me "I've never been to Bali 🫤" and I'm like "Okay". Like that sounds like that's something you must've done in your life. I've never missed not being in Bali but now that friends put those phrases out there it kinda feels like imposing FOMO. It's a social media thing - apparently. You saw on Instagram and on that one Photo, it looked like Paradise. So apparently people assume that will solve their problems, it will bring the golden times to their life. An illusion that holds tight. For many it's a long, stressful flight. And when you're finally there it might be raining and you start being mad. But yeah, sure thing, there's certainly a few good days there. And then what? Stressful flight back and all back to your unhappy life looking again how paradise-like the vacation of others are.</p>
<p>But how come it's so paradise-like they find the time to make photos? How come they don't forget the smartphone because everything is so enjoyable?</p>
<p>There's that Drake song where it says: "I know a girl whose one goal was to visit Rome. Then she finally got to Rome and all she did was post pictures for people at home. 'Cause all that mattered was impressin' everybody she's known"</p>
<p>I have a friend that is actually famous on a certain platform (no it's not porn, I just don't like name calling). She's getting a good amount of money. And then there's friends to her side saying "Oh wow, amazing what she does, I don't know how she does that". And then there's me telling 'em: "She doesn't". They be like "But she does do all of that" and I'm like "Yeah but it's an illusion". It's an addiction. Even more so I am waiting for her to fall apart, either mentally or physically. Not because I want that but because the signs are super-obvious. Exhausted, overly stressed, the body screams for help, the partner mostly a side story. But the outside says "Hey, I have a happy and successful life".</p>
<p>I know a different girl, similiar story, other sector, own company, a successful one. But that basically cost her nearly her hearing "solely" because of stress. She's 30, wearing hearing aids. Could've lost all of it. Now all she seeks is: Less work. Her partner got lost on the way. Not dead, but fell into severe depression because of the pressure.</p>
<p>I know someone that has gotten good money in the painting sector. Until he fell apart. Same thing as with everybody - overestimating oneself going like "I'm not like the others, I am a Hustler grabbing that Money". Well.... Now he's working in an animal shelter. He's happy. He has <strong>way less</strong> money. He can pay his rent. He's happy. He can't afford big and shiny cars. He's happy.</p>
<p>I know a girl that wanted to prove so much she's capable of handling stress that she ended up in psychiatry for nearly a year and was incapable of doing anything afterwards - she was not even close to 30.</p>
<p>I've been the one working as hard as I could to get rich. Not because I like money but because I didn't have much as a kid and people made fun of me - for not having the expensive Nike shoes, for not having the Adidas Jeans. That got deep.</p>
<p>My career was shiny, I started working with Code at the age of 11. With the age of 17 I had my own little company. I got any job pretty much easily, I reached 6 figures, I fell apart. There's too much to say but the silence will be speaking for now to get to the core of it.</p>
<p>I know so many stories I could tell. So, so many. But you don't read em. Because people usually don't tell those. They only give you illusions. Those illusion that lead to more of those stories. "If you work hard enough you can be a millionaire". Yeah, sure. Or dead.</p>
<p>And within that time your grandma died, your grandpa died, your mum died. All those who you said "you will be spending time with when you have more time". You don't.</p>
<p>Or maybe you die and your kids be like "But daddy wanted to be rich, so it's cool".</p>
<p>Do what you like, do what you enjoy, spent time with your loved ones. And if that combination will eventually lead to you being rich because you did what made you happy: Nice. If not then you still had something all people crave for: A happy life.</p>
<p>Enjoy your life, throw away Social Media and don't even respond to assholes on those platforms, just block em. Feel free again, don't get locked into comparison. Feel like brothers and sisters and if someone doesn't feel like adding up to that family just block em. You don't need that. Be alive, don't be social-media dead - or dead-dead.</p>
]]></content:encoded></item><item><title><![CDATA[Design for Failure: Your software should break]]></title><description><![CDATA[I've been preaching Design for Failure for a very long time. And I'm still holding on to it but you gotta be careful what you wish for. Many people overdo it and make their development lifecycle harder than it should be.
What is Design for Failure
DF...]]></description><link>https://blog.activeno.de/design-for-failure</link><guid isPermaLink="true">https://blog.activeno.de/design-for-failure</guid><category><![CDATA[Design]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[UX]]></category><category><![CDATA[Bugs and Errors]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Mon, 09 Jan 2023 14:43:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673276144755/64cbf4b0-4db8-4892-9ae7-14f2bf147a24.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been preaching Design for Failure for a very long time. And I'm still holding on to it but you gotta be careful what you wish for. Many people overdo it and make their development lifecycle harder than it should be.</p>
<h2 id="heading-what-is-design-for-failure">What is Design for Failure</h2>
<p>DFF is a principle that applies to the UI and the Code part likewise: Make sure errors are handled. This principle doesn't originate in Software. Let me give you an example from the automotive sector: It is an option in cars to mechanically decouple your steering wheel so you don't interfere with the driving assistant. Now that sounds scary until you understand how it works: It can only decouple as long as the electronic assistant is alive. If the assistant doesn't work for whatever reason the permanent signal that's holding back (!) the steering wheel is interrupted and the wheel naturally snaps back into place - this is Design for Failure. It doesn't matter why it fails, the only thing that matters is getting control over the car.</p>
<h2 id="heading-my-vacuum-robot-had-bad-dff">My Vacuum Robot had bad DFF</h2>
<p>I don't <a target="_blank" href="https://www.technologyreview.com/2022/12/19/1065306/roomba-irobot-robot-vacuums-artificial-intelligence-training-data-privacy/">like being spied on sitting on the toilet</a>. You don't have to be paranoid, you just have to be realistic. Especially as a Programmer, you should know that things that <em>can</em> spy you <em>will spy on</em> you - every big company has been hacked and they will be in the future. Nowadays I own a <a target="_blank" href="https://valetudo.cloud/">Valetudo</a>-based robot - it's awesome ❤️.</p>
<p>However, before that, I owned an expensive Vacuum Robot from a green, german brand claiming to have proper data protection (and especially not a camera built-in but lasers).</p>
<p>That thing's Error Design drove me nuts - and I mean that. I've even written the company multiple E-Mails offering them consultancy in Software Architecture but I eventually gave up and they didn't even respond to my last mail. It was the definition of "Hardware that is causing Mental Health Problems".</p>
<p>From a coding perspective it was clear that it had to follow some kind of logic like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">if</span> (SENSOR_BOX_FULL) {
  print <span class="hljs-string">"Please clean the container"</span>
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (SENSOR_LASER_BLOCKED) {
  print <span class="hljs-string">"Something is blocking the laser"</span>
}
<span class="hljs-comment">// else if ........ whatever </span>
<span class="hljs-keyword">else</span> {
  <span class="hljs-comment">// finally, the catch-it-all-case!</span>
  print <span class="hljs-string">"I am stuck"</span>
}
</code></pre>
<p>That meant that every time there was an error that wasn't specific enough it just said something like "I am stuck". Yeah, right. Stuck in what, with what? Definitely not stuck-stuck because it is free roaming on the floor.</p>
<p>Imagine this company would build manned space rockets and there is a leak with massive pressure loss: "Space Rocket is stuck".</p>
<p>It gets worse: The Notifications shown on the Smartphone apparently weren't using the same code logic as the App Code (like wtf?). That meant: My Notification Center showed different error messages than when clicking on it (which opens the App) e.g.</p>
<ul>
<li><p>Notification Center: Clean the Brush</p>
</li>
<li><p>App: I am stuck</p>
</li>
</ul>
<p>Needless to say that the brush was new and that it wasn't stuck. I've checked every single micrometer of that piece of ****.</p>
<h3 id="heading-okay-but-where-are-you-going-with-this-vacuum-sample-david">Okay but where are you going with this Vacuum Sample David?</h3>
<p>Error messages are supposed to help. <strong>They are not necessarily supposed to solve a problem</strong>. E.g.: If your internet is down it will not solve your problem knowing that it is down. Or if you have wrong pressure in your space rocket it will also not solve the problem by telling you so. But it will help you trying out the right things and calling the right people.</p>
<p>This also means: If an error cannot be exactly narrowed down to be exactly one thing you must provide enough details - even if they won't help solving the problem immediately. Even if that means showing "Error X1230900 Stack Overflow In Line 33:20". Because even if this reads obscure it is way more helpful than a robot telling you "I am stuck" even though it is not stuck. With the obscure message you can at least tell support the exact error message and they will be able to follow up.</p>
<h2 id="heading-there-is-a-pretty-clear-path-on-how-to-design-for-errors">There is a pretty clear path on how to design for errors</h2>
<p>From my own experience, there are 3 (4) essential types and I will show the software design in human-readable samples.</p>
<ol>
<li><p><strong>Recoverable Errors</strong>: The Vacuum Robot can't clean anymore because the box is full of dirt. This is solvable by emptying the box. The Software should do a couple of things here:</p>
<ul>
<li><p>Notify on the App ("Clean Me")</p>
</li>
<li><p>Wait a reasonable amount of time in place (e.g. 5-8 Minutes) to let the person empty the box.</p>
<p>  Expect the person not to be at home and provide a fallback after the waiting time for the robot to drive back to the station with a full box. This is super-useful because the Robot won't run out of battery and will be ready to continue once the box is emptied.</p>
</li>
<li><p>The aforementioned means also saving the position where the robot was and how much it cleaned right before it drove back to the station.</p>
</li>
<li><p>In Web applications recoverable errors go hand in hand with good UI Design: Couldn't save data? Don't ask the user to retry. Auto-retry 2 to 3 times before annoying the user and if it still fails tell the user (but then it falls into the definition of 2/3).</p>
</li>
</ul>
</li>
<li><p><strong>Unrecoverable but known Errors</strong>: Your coffee machine detects that there is water in the water tank but it can't pull water:</p>
<ul>
<li><p>Assuming there is LCD but no App for the Coffee Machine: Give exact error code.</p>
</li>
<li><p>There is no display? No problem. There certainly are some LEDs somewhere. Abuse one/or more to give indications about the error (e.g. 2 LEDs are permanently blinking)</p>
</li>
</ul>
</li>
<li><p><strong>Unknown Errors</strong>: In our interconnected world it can very well be that your software cannot determine the exact error why something isn't working. When I am building Software as a Service it will most likely use an external provider for Login (e.g. Auth0, Pocketbase, Supabase, ...). Now often such providers again use themselves providers e.g. for hosting data or for connecting to other services (Apple Login, Google Login, ...). So we have a lot of potential places that could fail.</p>
<p> <strong>There is a simple solution to handle these errors</strong>: Your task is to be able to help identifying the issue. This is why it's crucial that your software logs actions to some kind of log-store of what happened until and including the error e.g.</p>
<ol>
<li><p><code>log(loginUserData, currentTime())</code></p>
</li>
<li><p><code>log('Clicked on Start', currentTime())</code></p>
</li>
<li><p><code>log('Caught Error, could'nt start, Error, currentTime())</code></p>
</li>
</ol>
</li>
</ol>
<pre><code>Now some people argue: But what <span class="hljs-keyword">if</span> the internet connection is down and you cannot send logs to the server? You don<span class="hljs-string">'t have to! At the point of error happening you show a modal to the user which says something like:  
"Copy this message and send it to support."

The user doesn'</span>t care. The user just wants help and the user will get help by contacting you <span class="hljs-keyword">with</span> the full error log. We implemented <span class="hljs-built_in">this</span> <span class="hljs-keyword">in</span> a way that <span class="hljs-string">"hides"</span> <span class="hljs-built_in">this</span> <span class="hljs-keyword">from</span> non-technical users by using <span class="hljs-string">`base64`</span> encoding which comes down to something like copying <span class="hljs-string">`=YWZkcztmZGF...`</span> (simply <span class="hljs-keyword">do</span> not confuse the user <span class="hljs-keyword">with</span> an interpretation <span class="hljs-keyword">of</span> messages)
</code></pre><ol>
<li><strong>Errors that aren't errors</strong>: When I opened the lid to clean the box, my vacuum cleaner gave constant annoying beeps. Same when I simply tried to move it to a different place in idle mode. Try to think of a more common sample: Cars. Now imagine when you open the car door the car starts to tell you in very annoying ways that the door is open - Once you open the door: BEEP BEEP. Even if you're just loading or unloading it - it will constantly beep. That makes total sense when I'm trying to drive but not when it's <em>parked</em>. So stop trying to make error handling where error handling isn't needed in the first place. If you feel like you missed something: Give a beta out to real users - #usertests.</li>
</ol>
<h2 id="heading-the-misconception-of-catching-errors">The Misconception of catching errors</h2>
<p>There is a misconception out there: Developers think they have to catch errors and then "gracefully continue" in whichever way - I don't know where people learn this but <strong>this doesn't make sense</strong>.</p>
<p>A concept that is available in many languages is <code>try-catch</code> . Check this pseudo code:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">try</span> {
  basically.all.your.software.runs.here
} <span class="hljs-keyword">catch</span> (Error) { <span class="hljs-comment">// &lt;- this "Error" contains information about what happened (e.g. "Server Error 503")</span>
  whoopsie.something.bad.happened
}
</code></pre>
<p>It makes sure that your application doesn't just quit of failure - which would be even worse because then you cannot do <em>any</em> kind of error handling anymore.</p>
<p>However, inside this <code>catch</code> requires you to consider one of the 3 error scenarios from above. So e.g.:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">try</span> {
  <span class="hljs-comment">// ... blabla</span>
} <span class="hljs-keyword">catch</span> (Error) {
    showErrorDialogToUserSoUserCanCopyPasteAndMailMe(Error);
}
</code></pre>
<h3 id="heading-this-isnt-as-obvious-as-you-think">This isn't as obvious as you think</h3>
<p>Some developers would think now: That's very obvious ain't it? If you think so then please go back to your code and tell me if you <em>handle the error</em> or if you try to gracefully overgo it. Because the latter one is what I usually see. And this is really bad as it implies covering errors until errors happen that are even worse.</p>
<p>Let me give you a sample:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">let</span> someData;
<span class="hljs-keyword">try</span> {
  someData = loadSomeDataFromServer();
} <span class="hljs-keyword">catch</span> {
  someData = []; <span class="hljs-comment">// empty data</span>
}
</code></pre>
<p>This is bad. Any error happening in your <code>try</code> block will just be gracefully caught and you continue the application. To put it very clearly: <strong>This is the exact same thing as when you have a massive leak in your roof and every time it rains you have a lot of water in your room and the only thing you do is removing the water every single time.</strong></p>
<p>The application will still work because you provide it with fallback data. Now where's the benefit for the user? The error is not resolved and the data is empty. How is the user benefiting from empty data (which also makes the application unusable) rather than an error that will at least let the user know why the app is unusable.</p>
<h2 id="heading-the-misconception-of-fallback-data">The Misconception of Fallback Data</h2>
<p><strong>Bad</strong> UX is when you gracefully, silently catch errors because then the user is even more confused about why the app isn't working properly.</p>
<p>I also want to give you another perspective here: Setting default fallbacks with good intentions (shadowing errors that should be errors) can be very harmful and time-consuming. Imagine a few people leaving the company, some new people joining, things seem to work fine, and people start working on the project. They run it on the internal servers: All good.</p>
<p>Now things are supposed to go to production: Nothing works. For the internal servers it worked because somewhere in the project there is a file with something like</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">if</span> (not(config)) {
  <span class="hljs-comment">// fallback</span>
  config = someDefaultConfigThatOnlyWorksOnInternalServers
}
</code></pre>
<p>If you don't have that fallback it would be a more consistent solution: Defining configs per environment. And people would know and learn very early on because it's the same requirement on all Environments, no matter if Production or not.</p>
<p>And now comes the worst part about fallbacks: Partial (unintended) fallbacks. I had that in my own project and it's a severe problem: You set-up your config but you missed 1 value. Congratulations, now you have fallback values mixed up with Production.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Let your software break. It must break. Things must break to be able to get them fixed. Yeah, <strong>let that sink in</strong>. And that doesn't imply at all that it will be a painful experience for the user. Humans deal with errors all their life. But in the non-technical era they hadn't had as much frustrating errors. Your bed broke? Not cool but the error is clear: It's broken. Your pencil is dried out? That's a clear one as well. Water Pipe clogged? Call a plumber.</p>
<p>But Website says "Something wen't wrong, please try again" is the most frustrating thing as there is not a single thing the user is able to do. The user can't give you detailed error information, the user doesn't know if it makes sense to restart/reload, the user knows nothing and is indeed psychologically frozen in that state which can cause extreme frustration.</p>
]]></content:encoded></item><item><title><![CDATA[From Dusk Till Dawn: A Year of Personal Evolution - Dev Retro 2022]]></title><description><![CDATA[tl;dr: Write, Reflect and Take Care and look into the future. This year's been full of change and full of learnings and surely money doesn't make you happy.
Dusk
The year started with a depressive phase in the switch from 2021 to 2022. I was extremel...]]></description><link>https://blog.activeno.de/retro-2022-a-year-of-personal-evolution</link><guid isPermaLink="true">https://blog.activeno.de/retro-2022-a-year-of-personal-evolution</guid><category><![CDATA[#DevRetro2022]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Sat, 31 Dec 2022 15:25:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672500179018/1224fd57-6d27-442d-9a34-24de805912fc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>tl;dr: Write, Reflect and Take Care and look into the future. This year's been full of change and full of learnings and surely money doesn't make you happy.</p>
<h2 id="heading-dusk">Dusk</h2>
<p>The year started with a depressive phase in the switch from 2021 to 2022. I was extremely unhappy in my job back then as I didn't agree with the management mindset. The salary was reasonably good, above average. I got employed as a Frontend Architect in a FinTech Startup of a Bank. We created indeed great architecture - initially (I know it sounds arrogant but it's important to acknowledge good outcomes) and there were a lot of cool things to learn (e.g. from the Backend Architects which I've worked with 🤝). But very clearly when most of the base was considered "done" we moved to the "we should get done ASAP" mindset.</p>
<p>Eventually, my profession moved from <em>being the Architect</em> to <strong><em>ensuring things get done fast and also making sure to code a lot to get things done faster</em></strong>.</p>
<p>This state of seeing the architecture, quality and consistency loosening up whilst not being able to do what I was hired for due to artificially induced time pressure moved me into a state of a <em>well-payed puppet</em>. My opinion didn't matter anymore. Not because people argued against it - but simply because the setup enforced it and there was no room for critical opinions.</p>
<p>I did talk to my boss about being extremely unhappy. I'm not sure if he got the message back then, I felt I communicated very clearly about what I thought I was hired for.</p>
<p>At this time the only thing that gave me happiness was my girlfriend and my side project of "Becoming a Teacher/Mentor somehow" which I kicked off in 2021 with my first official LinkedIn Learning Courses 🎉.</p>
<h2 id="heading-the-night-glooms"><strong>The night glooms</strong></h2>
<p>I quit my FinTech job in Late 2021 but due to contractual agreements, I was only free in April 2022.</p>
<p>It's not perfect anywhere, that's for sure. But after having seen several big players, the latter 2 being automotive and FinTech and having realized that in many of those it isn't about actually making good stuff but it's about making stuff just good enough to make sure a few individuals get a big bonus I felt senseless. <em>Even if</em> you want to make awesome things you'll soon realize that the walls you have to break are not just a few meters thick - rather a few kilometers - you spend more time breaking walls than building things.</p>
<p>I felt enslaved by money-driven companies. Don't get me wrong: It's not that I put disgrace on companies targeting making money - it's the order of priorities. If you go back a few decades you will notice the massive shift from "a product brings massive money because it's a good product" to "a product sells even when it shouldn't because I did a good job in lying". You might call it "marketing". I call bullsh**t and a loss of ethics. And psychology proves that you get unhappy over time if you feel senselessness. More and more money will become your painkiller for unhappiness. Yet, that's not just for products. That's the same for how bigger companies internally work: Just make sure to twist the numbers correctly so that it looks like your company does great.</p>
<p>👉🏾 In the first quarter of 2022 I prepared myself for going back to freelance.</p>
<p>I applied at <a target="_blank" href="http://toptal.com"><strong>Toptal</strong></a> and was accepted. I was told I've made great impressions on the talent scouts - not sure if they tell that to everybody accepted but it was indeed helping my motivation.</p>
<p>Additionally, I pinged a few recruiters in my contact list over LinkedIn to let them know I'll be available for freelance jobs.</p>
<p>And I also didn't want to get too narrow-minded excluding myself from good employment options. I had a few standard interviews with some companies as well but none of them checked all my requirements.</p>
<p>There was a short period as well where I was hyped to join <a target="_blank" href="https://www.sketch.com/">https://www.sketch.com/</a> as I love using the tool ✍️ - the hype turned into disappointment though. The interview process was a bit immature, intransparent and confusing. E.g. they said I should've done certain things in a certain way without discussing my architectural thoughts behind it. And here's something to laugh about: I specifically asked before if the "optional" tasks are <em>really</em> optional or <strong>a rabbit hole</strong>. They said they are optional but I was punished afterward for not doing them 🫠. There's more but the gist is: Disappointment. Sounds bad but there's some good in it: It stopped me from joining a company in which I might've felt too mature / too advanced. But I wanted to join a company in which I don't have to fight for maturity.</p>
<p>tl;dr: I had quite some calls in the first quarter of 2022 - either for employment or freelance. None of them I'd call out wasted time. All of them were helping to see and realize what I want and what I don't - <strong>and the latter one is extremely important</strong>.</p>
<h2 id="heading-i-can-see-sparkles-at-the-horizon">I can see Sparkles at the Horizon</h2>
<p><strong>Toptal</strong> is more than just a Freelancing platform. There is a Slack community with thousands of people. I wanted to get inspired and started to post on some channels that I'm new and that I would like to have remote calls with random people - just to chat.</p>
<p>I think ~4 people were open to it. All of those were indeed nice chats and it's always interesting to hear about the life and work of other people.</p>
<p>With one I kinda got something like a friendship and we decided to make a combo version (v2, in the making) of my short stories Book. V1: <a target="_blank" href="https://www.amazon.de/-/en/David-Lorenz/dp/B099T7STB6/ref=sr_1_1?crid=2I8PRQV2P791X&amp;keywords=entwicklergeschichten+von+hell&amp;qid=1672261029&amp;sprefix=developer+stories+from+hell%2Caps%2C166&amp;sr=8-1">Developer Stories From Hell</a> - so yet another funny side project 🎉.</p>
<p>With all of the calls, and all of the thoughts it became pretty clear that freelance will be my way to go (but read on!).</p>
<p>In one of the calls in February, I was in touch with a University looking for employees to work on their internal Software. They were nice but I felt it isn't a fit for me at that moment. However: I told them if they are searching for freelance adjunct professors I'd be happy to lecture - especially if they got remote options.</p>
<p>In March I saw a Post on Twitter. It said: "If you know React and are open for a job contact me - if you're not an asshole". Ah, how I loved that. Direct, yet funny!</p>
<p>I got in touch with that guy (👋 <a target="_blank" href="https://chaos.social/@pepo">Pepo</a> from <a target="_blank" href="http://wahnsinn.design/">Wahnsinn GmbH</a>) and we had a call. He was cool, the chat was extremely uncomplicated. He judged me by character instead of by "How many space rockets fit into a bus?" and eventually said something like "I like you, next we would set up a call with our boss if you'd like?". "Sure", I said.</p>
<hr />
<p>Worth mentioning I've worked more and more with Supabase at that point in time. I think I was one of the very early users when it was still in Beta. I've even written a lot of issues of which the most important ones are resolved as of today.</p>
<p>Plus, somewhere in between all of that, I got in touch with Paul Copplestone (the CEO from Supabase, btw. very nice Podcast Episode here: <a target="_blank" href="https://open.spotify.com/episode/0RwPlcpIR8tSqqv8Y9o898?si=391ab30d53a04859">https://open.spotify.com/episode/0RwPlcpIR8tSqqv8Y9o898?si=391ab30d53a04859</a>).</p>
<p>Being a very early adopter and convinced of Supabase I would've loved to become a Developer Advocate. Paul indicated that they'd love to but want to grow organically and at that point in time my targeted role was staffed in my timezone already.</p>
<h2 id="heading-the-dawn">The Dawn</h2>
<p>My first freelance job ahead of me: At Wahnsinn.</p>
<p>Meanwhile, we're still in the first quarter of 2022, the University got back to me and said that they would have a job as a part-time lecturer for me 🎉. That was perfect because I love doing different things in small packages over monotony hence there would be enough time left to do freelancing.</p>
<p>The second call with <strong>Wahnsinn</strong> was equally chilled. They made clear that Freelance is an option but that they'd rather have employees. I responded: I can't. I'm burned by being locked in places that force their employees to get <a target="_blank" href="https://www.atlassian.com/agile/agile-at-scale/what-is-safe">SAFE</a> to <em>prove</em> they're agile even though they really are anything but agile. The boss said: "All good, no worries. You do you. We'd love to have you as an employee but you gotta do what fits your needs." That was unusual. Normally, companies are trying to convince you into something even if you state you're not into it - and they normally don't give you the option to do either freelance or being employed. I never felt so much in control about a job decision.</p>
<p>I told them that I wanted to go freelance to have the freedom of also being a teacher on the side and that it helps my overall motivation when I can learn and experiment with other things as well. He said: "That shouldn't be a problem. You'll have a 30-hour job and you can do your other stuff as long as you'll not lose focus on your job." Again: Unusual. Most companies give you some kind of suspicious look. But he was just being honest and expected nothing but honesty from my side.</p>
<hr />
<p>I got an employment offer (still undecided if I wanna be employed at all) with something that might scare off other people: A 2-week notice period. And I loved it. It shows: That company doesn't want to hold people that do not want to be there. That company tries to hold people by trying to be a good company. And as a developer, I only see the benefits as there are lots of open opportunities in the market.</p>
<p>All of the above made me decide to join the company <strong>as an employee 🫢</strong>. No one forced me into anything and I said "if they lied or stuff is going nuts I'll be out in 2 weeks." There was not a single feeling of being locked in 🍻.</p>
<h2 id="heading-the-sun-rises">The Sun rises</h2>
<p>When I joined I was put on a React Design System project. I love Design Systems. I've not only read a lot of books and articles about how companies evolve with Design Systems but I've also worked on a lot of them and helped with the implementation of those - not only from a technical but also from a design and organizational perspective.</p>
<p>My motivation was back - I still felt exhausted though. I wanna point that out (!) because many people could trap their thoughts into "Ah okay, David got a cool new job and now he's happy again, all good." <strong>It's not like that</strong>, so don't even start to think that things change over night 🌝. Things change gradually and that's a hard pill to swallow - for me same as for everybody else.</p>
<p>I loved improving the Design System at work. I revamped a lot of things there. On the side I started a YouTube Channel (<a target="_blank" href="https://youtube.com/@activenode">https://youtube.com/@activenode</a>), eventually had my first 45mins of lecturing at the university and worked on a side project with Supabase: I've rewritten an existing 9-year-old PHP application with Next.js and Supabase.</p>
<p>Since Supabase centers all of the features around Postgres (a Database System, similar to MySQL) I got even more boost as I just love working with databases features (even though I am primarily a Frontend Architect).</p>
<p>The best thing about that was: I got so much into Next.js and Supabase that we found it to be a perfect stack for one of our company projects. This means (Attention please!): My side project work led to the fact of me becoming a Next.js / Supabase Pro which helped reducing costs in the company project. <strong>This is crucial 💪</strong>. Too many companies watch your side actions suspiciously because they think it harms them. But in fact, they harm your bond of trust by stalking your actions.</p>
<p>I also moved my blog from dev.to to Hashnode because I could easily set up my custom domain without any fees and the overall UX was much more inviting. It helped me to write more blog posts and it also provides the feeling of not being trapped as I own the domain.</p>
<h2 id="heading-looking-directly-into-the-sunlight-isnt-healthy">Looking directly into the Sunlight isn't healthy 🥵</h2>
<p>Let's recap: Without reflection it felt like "I didn't do much this year". But in fact: Lots of calls, lots of thoughts, 2 LinkedIn Learning Courses (I underestimate the massive amount of hours it takes to have 1 hour of final video all the time), started lecturing at the University, created a YouTube Channel, became a Supabase Expert and helped on the repository, switched to a company as an employee although having everything prepared for going Freelance, mentored 3 people on their journey of becoming developers, improved my information chaos by using Notion actively, coded <strong>a lot</strong> and learned a lot (too much) of new tech and more...</p>
<hr />
<p>It sounds like it became a perfect year eventually <strong>but there's one thing I didn't do</strong>: Put my health at first place. Over the year, I not only had COVID-19 which was a tough one even after being negative but I even had 2 months in winter of getting sick all the time. And at this point of writing, I'm still struggling with the aftereffects of a rough flu. It's getting better every day but it felt like my immune system had to re-initialize after all of the "being mostly at home".</p>
<p>The weird thing is the thoughts that come up:</p>
<ul>
<li><p>Why am I still sick?</p>
</li>
<li><p>I wanna do stuff.</p>
</li>
<li><p>My body is annoying.</p>
</li>
</ul>
<p>My doctor said something clever:</p>
<blockquote>
<p>"You have to show a little bit more humility to your body. Your telling me that you don't know why you're so tired lately. For me it comes obvious: You've been jumping from one infection to another and the body needs time to regenerate. Not giving it time is not showing enough respect."</p>
</blockquote>
<p>Of course, it is very obvious that our body is the most important thing we have and well all know about the well-known facts about doing sports. Still many of us don't. And we only come to realize that when it seems to be too late.</p>
<p>So here I am reflecting that when I ask myself "Can I skip doing Yoga and making sure to just work a little bit more to finish that task?" I will need to always go for the first one. Because you only notice missing out on it when you're in pain. And when you are it's even harder to do so.</p>
<h2 id="heading-one-more-thing">One more thing</h2>
<p>You might've read my article about my offer from <strong>Amazon</strong> (<a target="_blank" href="https://blog.activenode.de/getting-interviewed-at-amazon">https://blog.activenode.de/getting-interviewed-at-amazon</a>). In 2022 I got <strong>contacted again</strong> by Amazon because I'd made such a good impression the last time. I felt flattered and agreed for interviewing with an even higher position (Level 6). However: This time I didn't get an offer and I was not told exactly why. Unfortunately, it's normal that they don't tell you exact reasons for legal reasons to avoid law suits 🙃. I'm pointing this out because rejection happens on all levels and is part of the process (another good read: <a target="_blank" href="https://blog.activenode.de/mastering-frontend-interviews">https://blog.activenode.de/mastering-frontend-interviews</a>). I felt extremely secure that they'd provide me with an offer but at the end of the day it could've been anything aside from my hard/soft skills e.g. fear of me signing and then going freelance again. I'll never know.</p>
<h2 id="heading-thanks-hashnode">Thanks Hashnode</h2>
<p>Thanks for Hashnode for initiating the Dev Retro 2022. I probably wouldn't have reflected if you hadn't brought it up. It is known to be extremely helpful to reflect more often and this one really helps appreciating the year.</p>
]]></content:encoded></item><item><title><![CDATA[unglitch - Ultra-Simple State Management for React]]></title><description><![CDATA[Imagine this: You create your React or Next.js setup and you need a store to seamlessly share your data across components. This will very likely include some data fetching logic which provides the data to the store.
In the old days everybody would sc...]]></description><link>https://blog.activeno.de/unglitch-ultra-simple-state-management-for-react</link><guid isPermaLink="true">https://blog.activeno.de/unglitch-ultra-simple-state-management-for-react</guid><category><![CDATA[React]]></category><category><![CDATA[state]]></category><category><![CDATA[State Management ]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Sun, 04 Sep 2022 17:33:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662312799787/vVKEcjGtM.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine this: You create your React or Next.js setup and you need a store to seamlessly share your data across components. This will very likely include some data fetching logic which provides the data to the store.</p>
<p>In the old days everybody would scream <code>Redux</code> and you'd check with some kind of state property if fetching data was already done or is currently being done. Nowadays we have <code>Redux</code> + a bunch of other options - same goal, different architectures.</p>
<p>The existing stores are awesome, partially damn easy (e.g. <code>zustand</code>) and do work fine.</p>
<h2 id="heading-but-stores-do-not-solve-side-effect-problems">But (!) stores do not solve side-effect problems</h2>
<p>The problem is that within the React Lifecycle you can have the following situation: 3 components need data, hence 3 components make use of your custom hook <code>useData</code> and that hook checks in the store if data is already available e.g. with </p>
<pre><code class="lang-ts"><span class="hljs-comment">// my custom hook</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useData</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> data = useZustand(<span class="hljs-function"><span class="hljs-params">state</span> =&gt;</span> state.data);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!data) {
     fetchData().then(<span class="hljs-comment">/** some fetching logic **/</span>);
    }
  }, [data]);

  <span class="hljs-keyword">return</span> data;
}
</code></pre>
<p>But this is troublesome - and I am unfortunately seeing this more and more on websites: The data is being fetched multiple times, multiple requests are sent.  The reason is easier explained by providing some visual help in the following diagram:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662363026741/HpgKjJtb7.png" alt="Component Data Flow Diagram" /></p>
<p>All components are using the hook <code>useData()</code>. And <code>useData</code> in that lifecycle will have empty state data. Still the <code>useEffect()</code> of <code>useData()</code> will be called 3 times as we have 3 components using it - reminder: Reused hooks are not <a target="_blank" href="https://en.wikipedia.org/wiki/Singleton_pattern">Singletons</a>. And the problem continues: You cannot really check the <em>provided</em> state data as you get the state of <strong>this</strong> lifecycle so another component might've called the fetching function but the other components will be notified in the next lifecycle run and hence also trigger fetching the data.</p>
<h2 id="heading-this-is-not-a-react-problem">This is not a React problem</h2>
<p>Now it might sound like "Isn't this bad as per architecture?". No. You get a state per lifecycle across your components such that all components will have the same, in-sync-state which is required for your components not to behave weird.</p>
<h2 id="heading-its-your-problem-you-need-to-orchestrate">It's your problem: You need to orchestrate</h2>
<p>At the end of the day you have to avoid that functions running outside of the React lifecycle (such as data fetching methods) will be ran multiple times. This is possible with all major State Management Libraries because they do update the state before components get notified.</p>
<p>E.g. in Redux (with <code>redux-thunk</code>) you'd have your reducer something like:</p>
<pre><code class="lang-ts">dispatch(<span class="hljs-function">(<span class="hljs-params">dispatch, getState</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (getState().isFetchingData === <span class="hljs-literal">false</span>) {
    fetchData().then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> dispatch({
      action: <span class="hljs-string">'UPDATE_DATA'</span>, payload: data
    }));
  }
});
</code></pre>
<p>or in <code>zustand</code> you could build it like this:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> store = create(<span class="hljs-function">(<span class="hljs-params">set, get</span>) =&gt;</span> ({
  isFetchingData: <span class="hljs-literal">false</span>,
  fetchData: <span class="hljs-function">() =&gt;</span> {
   <span class="hljs-keyword">if</span> (get().isFetchingData === <span class="hljs-literal">false</span>) {
     fetchData().then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> set({data}));
   }
  }
}));
</code></pre>
<p>Works but also is additional <code>if</code>-overhead - and you have to remember to do it.</p>
<h2 id="heading-unglitch-provides-lock-or-leave-calls"><code>unglitch</code> provides <em>Lock-or-Leave</em> calls</h2>
<p>I wanted a simple state management solving that problem. I could've adapted <code>zustand</code> but then I went with digging into building an even simpler system: <code>unglitch</code>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/activenode/unglitch">https://github.com/activenode/unglitch</a></div>
<p><code>unglitch</code> is pretty similiar to <code>zustand</code> and it <em>kinda</em> uses the same technology. However built-in with the State Management do come locked calls. </p>
<p>It's easiest explained with the following code snippet:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { useStore, update } <span class="hljs-keyword">from</span> <span class="hljs-string">'./my-store'</span>;

<span class="hljs-keyword">const</span> fetchData(releaseLock: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>, realtimeData) {
  <span class="hljs-comment">// we can check the live data outside of the lifecycle  </span>
  <span class="hljs-keyword">if</span> (realtimeData === <span class="hljs-literal">null</span>) {
    <span class="hljs-comment">// ..fetch some data...</span>
    <span class="hljs-comment">// ...then update it:</span>
    update({ data: [<span class="hljs-comment">/** your data here */</span>]});

    <span class="hljs-comment">// release the lock so it can be called again</span>
    releaseLock();
  }  
}
fetchData.LOCK_TOKEN = <span class="hljs-string">"FETCH_DATA"</span>;


<span class="hljs-keyword">const</span> useData = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [data, lockedCall] = useStore(<span class="hljs-function"><span class="hljs-params">state</span> =&gt;</span> state.data);

  useEffect(<span class="hljs-function">() =&gt;</span> {
   lockedCall(fetchData);
  }, []); 

  <span class="hljs-keyword">return</span> data;
}
</code></pre>
<p>The <code>LOCK_TOKEN</code> is grabbed automatically when you run the <code>lockedCall</code>. If the <code>LOCK_TOKEN</code> is not present you will be facing an error so don't worry about forgetting it. Sure, you could still manually call that function but as long as you run <code>lockedCall</code> it will take care of running it only once.</p>
<p>The function that is being called always receives a function as first parameter that will free the lock again and the second parameter is exactly the provided state data in <code>useStore</code> so here it is <code>state.data</code>.
The difference is however: The function that is being called receives the <code>realtimeData</code> and not the data that is currently available in the lifecycle. This allows you to check if you need to fetch data or not.</p>
<p>Besides this lock mechanism the store works pretty much similiar to <code>zustand</code>. Check it out.</p>
]]></content:encoded></item><item><title><![CDATA[I coded on a Tablet for more than a week]]></title><description><![CDATA[I went on vacation for more than a week and I'm honest upfront: I grew up coding so I'm having a really hard time not doing any coding at all in my vacation. It's my vacation so I do whatever I want. That might be swimming, that might be walking but ...]]></description><link>https://blog.activeno.de/coding-on-a-tablet</link><guid isPermaLink="true">https://blog.activeno.de/coding-on-a-tablet</guid><category><![CDATA[coding]]></category><category><![CDATA[Gitpod]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[remote]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Thu, 25 Aug 2022 16:59:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1661454089572/0XkC1tfMr.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I went on vacation for more than a week and I'm honest upfront: I grew up coding so I'm having a really hard time not doing any coding at all in my vacation. It's my vacation so I do whatever I want. That might be swimming, that might be walking but it might be some coding as well 🧑🏽‍💻 🌞.</p>
<h2 id="heading-is-the-tablet-too-limited-for-doing-any-kind-of-work">Is the Tablet too limited for doing any kind of work?</h2>
<p>Well that's what we are going to evaluate. One thing is for sure: It always depends ... <strong>but</strong> I was more than amazed what was possible when finally things were running smooth.</p>
<p>First of all: No one <em>needs</em> a Tablet if you got a Laptop. I bought one for several reasons: Still lighter (12 inch, 567g) and thinner than basically any Laptop, fits even in the smallest places and runs the on-the-go Apps such as Netflix, Prime, etc. natively. So it can be a pretty cool mobile device, especially when you have a keyboard cover with it. Also I can just grab it and leave. As opposed to my Laptop which is at my desk connected with multiple cables and annoying to reattach honestly. </p>
<p>So tl;dr: It's more convenient to have a device laying around ready-to-go rather than grabbing a Laptop, its protection and potentially forgetting to bring the charger. 
With the tablet I can use my Smartphone charger as well.</p>
<p>Now this was a trip longer than a week and I was kinda scared of the experiment of not bringing a fully-equipped, terminal-running laptop that I am used to but you gotta experiment to find out.</p>
<h2 id="heading-the-hardware">The Hardware</h2>
<p>In this Post I'm referring to my Samsung Tablet S8+, 12 inch (due to its 16:10 form factor it feels rather like an 11 inch iPad). This helps yourself to compare to tablets with weaker specs.</p>
<p>At work I use a 14 inch Macbook Pro. </p>
<hr />
<h2 id="heading-heres-how-it-went-10-days-no-laptop">Here's how it went, 10 days no Laptop</h2>
<h3 id="heading-general-usage-ergonomics">General Usage (Ergonomics)</h3>
<ul>
<li><p>Train, Bus, Plane, etc. are environments that do not have a lot of space. Using the Tablet here is pretty easy. However comparing it to a small laptop (~13 inch) it still looses the comparison because the Tablet is not as adjustable in it's position as the laptop (adjusting the tablet is dependent on the cover and the original keyboard cover from Samsung only has one position). But still, pretty solid and the tablet wins space-wise.</p>
</li>
<li><p>With a thin keyboard cover comes a thin keyboard. That however was surprisingly good. Sure you can't compare it to a MacBook Keyboard or even my Keychron. However I was surprised how good it felt because I expected somehow way less. Benefit: Pretty silent so you won't annoy anyone sitting next to you. So keyboard-wise it's pretty solid as well. And honestly if you bringing a clicky Keyboard on the Bus, Train or Plane is annoying everyone else anyway.</p>
</li>
<li><p>Bring a Mouse: On the train you won't need the mouse. The Pen is really awesome to click stuff and move the cursor to different places. It even supports hovering if you're roundabout 1inch from the tablet away which is neat. But if you wanna have a real comfortable session where enough room is provided the mouse will definitely light up your day, that's a promise. </p>
</li>
</ul>
<h3 id="heading-working-with-direct-sunlight-temperature-and-readability">Working with Direct Sunlight: Temperature and Readability</h3>
<p>Apparently the Tab S8+ seems to go with 420nits Brightness. As a comparison: Most normal monitors go around 250/300 nits, the iPad Pro comes with 600 Nits and the Macbook Pro 2021+ at its peak can have more than 1000nits - compared to that 420 seems to be low for a Highend Tablet, but read yourself:</p>
<ul>
<li><p>The good news is: You <em>can</em> kinda still work in <strong>direct</strong>, non-cloudy sunlight with the Samsung Tablet. But it comes with 2 downsides: It's really exhausting for your eyes and the Tablet will randomly close Apps if it gets really hot (and it does get hot in Summer). I would rather have the tablet stopping to work and telling me to go somewhere else rather than deciding for me to close Apps (mostly Chrome) which I found extremely patronising. </p>
</li>
<li><p>I compared it to an iPad Pro from someone else and the brightness in direct sunlight with the iPad is a huge benefit compared to my tablet (this difference however is barely noticeable in normal, shaded environments). </p>
</li>
</ul>
<h3 id="heading-screen-resolution-multi-window">Screen Resolution / Multi-Window</h3>
<ul>
<li>The Screen Resolution was really good for getting things done. I had a Gitpod window at kinda 70% of screen width and then <em>below</em> and aside another Browser Window at 60-70% as well.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661441305631/jiYVEHuKr.png" alt class="image--center mx-auto" /></p>
<p>Samsungs Tablet has 2 Working Modes: Normal Tablet Mode and the Samsung DeX (=Desktop) Mode. The latter one has a desktop-like look, a higher resolution and a better window management. Honestly I don't get why the resolution differs. Doesn't make sense to me. But either way I was only using DeX. If I recall correctly the reported resolution in the Browser was: 1400x876 Pixels.</p>
<ul>
<li>Samsung DeX is honestly pretty good and unique. You can kinda compare it to a Chromebook I'd say. You can open multiple windows and resize them and you can even make them opaque if you want to to see through. This allows you to make even more use of the constrained space. <strong>However</strong> it's not exactly the same as a Desktop. You cannot have multiple Chrome Windows open. I did find a Workaround however: If you use the Website as a PWA (Chrome: Add to Screen) you can have that Website running in its own Window. <br /><br /> Also I want to point out that as to my knowledge, at this point of time, the iPad does have the option for multiple windows but compared to the Samsung DeX it's way more limited as you can't arbitrarily move and resize windows and minimize or maximize them so I would have been propably frustrated not having Samsung DeX.</li>
</ul>
<h3 id="heading-coding-running-servers-the-terminal-etc">Coding, running Servers, the Terminal etc.</h3>
<h4 id="heading-hello-gitpod">Hello Gitpod</h4>
<p>I don't even know where to start. I don't even know if there is a competitor or not and I don't even care. Gitpod is just working so so well I can't really say a single bad thing about it.</p>
<p>I've known Gitpod for a very long time but I barely had good use-cases for it. Since the Tablet doesn't run a proper Terminal nor can I install <code>npm</code> packages or even start a server or use the <code>git</code> command line you definitely need something like a remote environment that can do this. And hot damn, Gitpod can.</p>
<p>You point to the Git repository on GitHub, GitLab, whatever. Then GitPod fetches it for you in a containerized environment. The spin-up takes like a minute.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661445514675/QKxWdWPR6.png" alt="Screenshot 2022-08-25 at 18.38.14.png" /></p>
<p>Then, on the tablet, you choose "Open in Browser" and it will run the Web Version of VSCode. You can even install Plugins, choose a Theme, etc.. It cloned my Next.js respository, I ran <code>npm run dev</code> in the Gitpod VSCode Terminal and it started the Next.js Server accessible for me on the Web, previewable either inside VSCode or in a new Browser Tab.</p>
<p>So my setup was: Add gitpod.io with "Add to Screen" as standalone Browser App and then have another Browser Window open where the Preview was. </p>
<p>I coded in one window and had a hot-reload preview in the other window. It felt like coding as always. Pure bliss 😍.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661445878165/Vq2nLXiQ4.png" alt /></p>
<p><code>npm</code>, <code>yarn</code>, <code>bun</code>, whatever you install in the remote environment will be available to you. I'm honestly not sure where the limit is but I didn't find it.</p>
<p>Now what I found even more amazing: You save bandwidth. Like a lot. Since <code>npm install</code> etc. runs on the Gitpod servers it doesn't take up any of your bandwidth. The only bandwidth it uses is your input when coding. </p>
<p>There is only 2 things I'd like to point out that aren't perfect:</p>
<ul>
<li><p>The gitpod config yaml file sometimes didn't behave exactly like I expected it to. But maybe I haven't properly read the documentation and also the config yaml is no actual necessity so I'm not really wanting this to devalue the overall experience.</p>
</li>
<li><p>The App seemed to kinda perform well and try to sync when connection was bad when I was writing in the editor. But when I was writing in the terminal and connection was bad it kinda corrected back and forth which felt odd. But probably there's a reason behind it -  I guess. And also this rarely happened.</p>
</li>
</ul>
<h4 id="heading-how-to-inspect">How to inspect?</h4>
<p>Short answer: You don't. </p>
<p>Longer answer:
As opposed to some sources on the internet which claim that you apparently can activate the local inspector in Chrome even on Android I couldn't make it work. I didn't find the options and yes I do have Developer Mode activated.</p>
<p>So instead of just opening the inspector and checking the height of a box I kinda javascripted and alerted it - which worked but felt very inefficient. Which also means: All <code>console.log</code>s happening on the client are missed. The ones on the Server get logged in your Gitpod VSCode terminal.</p>
<p>You could however use a service like lambdatest.com or browserstack.com to open the Preview Link in a Remote Browser and then you'd be able to use the inspector there but that would rather be my last resort for bigger problems. Most often just alerting was okay.</p>
<p>But it's definitely not comforting, that's for sure.</p>
<hr />
<h3 id="heading-big-yikes">Big Yikes</h3>
<ul>
<li><p>I don't get why the Samsung Tablet doesn't compete with the iPad Pro in Terms of Brightness (it can't be so hard to also just provide 600nits). This would've been a Game Changer.</p>
</li>
<li><p>I don't like the Samsung Politics of preinstalling stuff I don't want or need and not even letting me deinstall it. That's annoying as fuck.</p>
</li>
<li><p>Samsungs UX doesn't feel as straightforward as iOS. I love the finder, it's similiar to a Spotlight search in Mac or many Linux distributions. However there is so many more options to manage/open apps that I ask myself if maybe Samsung should hire a new UX Lead. Like there is a sidebar with Apps, then there is the normal App launcher, then the Finder, the Desktop itself obviously and probably more. Someone needs to clean this mess.</p>
</li>
<li><p>The fact that the Samsung Tablet manages heat by closing Apps is a big no-no for me. I mean my solution was to avoid direct heat (e.g. with a book in front of it) which worked well but the first time it was closing my Chrome was pain.</p>
</li>
<li><p>No inspector 😿 (so far, happy about any good tips here!)</p>
</li>
</ul>
<h3 id="heading-big-likes">Big Likes</h3>
<ul>
<li>I had to get used to the "Pro Tablet Usage" at first which took like 3 to 4 days, adapting keyboard shortcuts etc. but thats a one-off so that was definitely worth it since I'm a "Pro User" now</li>
<li>The Tablet Colors and Brightness in normal environments are really convincing.</li>
<li>Switching between Apps with normal CtrlKey+Tab worked like a charm</li>
<li>Multi-Window in Samsung DeX was really good and helpful</li>
<li>Mobility</li>
<li>Battery when it's not hot (I could watch 3 hours of offline Netflix and still have more than 50%)</li>
<li>Gitpod 🫶🏾</li>
</ul>
<h3 id="heading-summary">Summary</h3>
<p>At this very moment, with a tablet, you're bound to good internet. Besides Stuff like Notion that allows you to write stuff when you're offline or downloaded Music and Videos the Tablet without internet is pretty useless IMO as compared to a Laptop where you can have all your stuff on the filesystem.</p>
<p><strong>However</strong> even if many of the above kinda sounded like raging the amazement was between the lines. I was amazed, given internet connection, how far you can get with a Tablet. Especially because it has a few advantages such as screenshotting and drawing with the Pen on the screenshot directly and then pasting that e.g. into a Web Design App such as Canva. Also working in GitPod was just so convenient that I barely noticed I was working on the Web Version. The only time I did was really when the internet connection wasn't stable.</p>
<p>So would I recommend working on a Tablet for Coding? I'd say if you wanna have the tablet convenience and you are not necessarily planning to code for 8 hours every day but rather like 2 or 3 I can definitely say that there is not much speaking against doing so on a tablet.</p>
<p>Would I replace a Laptop with a Tablet? Speaking for myself I don't see this happening in the near future. I want more resolution, I want even better window management (opening new windows in Chrome instead of new Tabs) and having a fully working inspector and full access to the terminal. Still it's awesome to know that I can have a fully working, seamless VSCode without my Laptop.</p>
<p>If you're planning on buying a Tablet for temporarily working I'd say: Wait for one with even better battery and maybe 550+ nits.</p>
]]></content:encoded></item><item><title><![CDATA[Getting interviewed at Amazon]]></title><description><![CDATA[First I declined to write this article but I got the feedback that people are truly interested in interviewing processes about the Big Ones (FAANG = Facebook, Amazon, Apple, Netflix, Google...) so here we go ♥️
tl;dr: This Post explains how I got int...]]></description><link>https://blog.activeno.de/getting-interviewed-at-amazon</link><guid isPermaLink="true">https://blog.activeno.de/getting-interviewed-at-amazon</guid><category><![CDATA[interview]]></category><category><![CDATA[Amazon]]></category><category><![CDATA[frontend]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Mon, 28 Feb 2022 17:36:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646839096027/fsF6aoiAo.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>First I declined to write this article but I got the feedback that people are truly interested in interviewing processes about the Big Ones (FAANG = Facebook, Amazon, Apple, Netflix, Google...) so here we go ♥️</p>
<p>tl;dr: This Post explains how I got interviewed by Amazon and eventually got an offer. </p>
<h2 id="heading-why-i-declined">Why I declined</h2>
<p>Before the question pops up let me reveal upfront: The offer was legit. However I declined the Amazon offer. Simply because I had a counter-offer at the time which was kinda identical and it didn't require me to relocate - which I didn't want to in pandemic times of embracing remote work more and more (only to notice I am going to be in Home Office in a different city).</p>
<h2 id="heading-who-the-f-cares">Who the F cares?</h2>
<p>This is for people that want to understand how interviews in bigger companies work and for people that want to understand if it is "impossible" to get in. It's not. And you got not much too lose for trying. </p>
<h2 id="heading-take-it-with-a-grain-of-salt">Take it with a grain of salt</h2>
<p>Please be aware of the fact that I report all of this from a perspective of someone that's been long time into tech. But that doesn't mean I know all the algorithms. In fact I barely know algorithms - sure it would be easy to re-learn those but I just didn't need to by now (business-wise). I'm certainly not learning any Dijkstra Algorithm by heart for an interviewing process. If they want me to then I'm out, for sure. </p>
<hr />
<h2 id="heading-the-flow">The <strong>Flow</strong></h2>
<ol>
<li><p>I got contacted by a recruiter person for Amazon when I was working for Mercedes-Benz.io so maybe that was the reason, maybe not - who knows. But at the end of the day, the overall process would've been the same if I had applied or if you are recruited - there's no difference.</p>
</li>
<li><p>Having talked to the recruiter I was like "Yeah sure, Amazon, why not give it a try?" (I am never <em>hyped</em> about a job since being hyped upfront might be the mother of disappointment. Amazon and Google are also just companies with people like you and me). </p>
</li>
<li><p>The process explanation:</p>
<ol>
<li>Talk to the recruiter</li>
<li>Have a Codility Test</li>
<li>Run through a Live-Coding Test</li>
<li>Meet the Team ~6 hours</li>
<li>The offer</li>
</ol>
</li>
</ol>
<h2 id="heading-31-the-recruiter-call">3.1. The Recruiter Call</h2>
<p>I don't remember every single bit of the call but the internal Recruiter was a different one than the one that initially got in touch with me. Seems like they do split the work in that regard. The Recruiter was pretty friendly and available for all questions that I had upfront. He explained that there's gonna be a Coding Test and if that succeeds they will continue. I'm honestly not sure anymore if he exactly told me WHAT kind of process will follow but I am certain he would have if I had asked.</p>
<h2 id="heading-32-codility-test">3.2. Codility Test</h2>
<p><a target="_blank" href="https://www.codility.com/">Codility</a> is a famous tool for big companies to pre-select candidates. tldr: If you fail those tests fatally there is a good chance no person will even look at it. The tasks can vary extremely in complexity and type of challenge. But these can be chosen by the company. Codility is a time-limited Challenge. This was indeed stressful for me because knowing that you have limited time and there is no one to explain to what you might be currently stuck at stressed me out. Also there is no pause button. When you start it the time runs. The timespan was 125mins. I think I finished after like 70mins but eventually left everything open to check over and over again every line of code so I stopped at around 90mins or so.</p>
<p>The tasks are extremely clear. There is not much room for interpretation. Of course you can google stuff but that doesn't mean that your code will get better. Take care of your code structure, make it readable and even more so code properly. E.g. if Codility says that you can assume that the input is a number then don't build something like <code>if (isNumber)</code>. You waste your time and probably worsen your score.</p>
<p>Codility DOES provide a little bit of insights if you're completely wrong or not. Because there is a good amount of tests already added that you are free to run - and that really helps reassuring that you didn't code complete bullshit. But: Don't be fooled. Only because the 10 provided Tests run successfully doesn't mean that ALL TESTS will run - and you don't see all tests.</p>
<p>The Tasks I got weren't "out-of-universe" challenges. I felt like they were "real use-cases" which actually made the tasks fun. But still stressful.</p>
<h2 id="heading-33-livecoding">3.3. Livecoding</h2>
<p>Apparently my Codility challenge was very convincing so I got invited to the next step: A Coding Challenge with an actual person - Livecoding. I am not as stressed with those because meeting actual humans means that you are able to explain what you're doing. If you hate Livecoding then you will have a bad time honestly. </p>
<p>I am anxious as well in some situations but you simply have to get over it. There is a company that potentially wants to hire you and you have to learn to not get a blackout. It's a person in front of you, not a killer. Maybe it helps to think of Teddy Bears instead, I don't know. If you do get blackouts with all live-codings then the really only thing I can recommend is: Seek help / consult the internet for tricks to overcome it. There is a lot of lovely tips out there. Seeking help is good and no sign of weakness. Everybody has their own weaknesses. </p>
<p>Back to topic. The Livecoding was open for any language. The person asked me to process a few things and how I would approach certain things programmatically. </p>
<p>I wasn't pinpointed to JavaScript. I could have equally used Python. The online editor provided supported syntax of multiple languages. However it didn't have a "Run" button which was kinda odd for me. So you couldn't confirm if what you're doing is "correct" - but maybe that was intentionally to pinpoint you more to explanations than actual results. You have to talk and explain. Talk a lot! You can't ever talk too much. Every single step of thinking: Say it. This allows the other person to follow your steps. Don't just be quiet and say "Here that's the result".</p>
<p>Also I had been asked a few other questions but the recruiter  prepared me upfront to read the Amazon STAR Technique (Find Info here: https://www.amazon.jobs/en-gb/landing_pages/in-person-interview). So it wasn't anything that came "unexpected" really.</p>
<p><strong>Again</strong>: The Recruiter is your go-to person. See that person as your personal trainer for the processes. There is no "dumb" question - well maybe if you ask them what you should eat today, maybe that would be a "dumb" question.</p>
<h2 id="heading-34-meet-the-team">3.4. Meet the Team</h2>
<p>I was honestly right before backing off. I was told the next step would be to have <em>several</em> interviews in an overall timespan of <strong>6 hours</strong>. I hate long-running interview processes. I appreciate it when companies give you options to talk but I have other stuff to do and lots of other people would've to take vacation days for that. </p>
<p>What did I do? <strong>I told the Recruiter</strong>. Honesty is what's important for you and the company! I said: "I don't wanna be rude but I never in my life was thrown into such a huge process and I feel like I'm backing off after the first hour if I find it intimidating or not helpful".</p>
<p>The Recruiter was again very nice and said "Fair points, David." and continued to explain me why they do this.</p>
<p>The idea behind it is to <em>actually</em> meet the team and have the different people in the team ask questions that might be more related to the field they are working in. Not only that but also it provides the candidate the option to get in touch with more than just the "gatekeepers of the company" (HR people).</p>
<p>Eventually, I found it nice because I liked the idea of meeting the team before making decisions. 
But it wasn't really a meet-and-greet. It was very friendly but serious at the same time (some more serious, some funnier).</p>
<p>In one Interview (I think there were 6 in total, one hour each) I was asked to do a little bit of Systems Design which I found a legit task for the role I was going for (Senior Frontend Engineer).</p>
<p>There were never more than 2 people from Amazon in each session. Mostly one asking, one ghosting (ghosting = learning how to do interviews).</p>
<p>The Team also asked me questions regarding the <a target="_blank" href="https://www.amazon.jobs/en-gb/principles">Leadership Principles</a> of Amazon, especially in relation to my previous experience. Since I have a lot of experience I prepared those questions very well as well as I could.</p>
<p>The exact questions really don't matter because they are different each time anyway but they're in the format of "When was the last time you got a difficult challenge and how did you tackle it?".</p>
<p>But <strong>none</strong> of it like: "Where do you see yourself in 10 billion years?" - fortunately.</p>
<p>If you don't have a lot of work experience it might help to find relatable experiences within your life and answer with those but I would clarify with the internal recruiter upfront. The recruiter will certainly help you out with your concerns.</p>
<p>At the end of the 6-hour session, I was kinda exhausted. Not in a negative way but it's still a long time in which you meet different people that all have questions.</p>
<p>Normally that session would've been onsite. I was lucky enough for it to be online due to the Pandemic. And I also do think they should stick with keeping it online as I find it more convenient and avoid traveling (#environment).</p>
<h2 id="heading-35-the-offer">3.5. The Offer</h2>
<p>The Recruiter wrote me first before sending over the offer like "David when's a good time to call you?". He said something like: "The team really liked you, you made an awesome impression and I would like to explain you the offer we have for you."</p>
<p>The Offer was good - especially if you consider its overall compensation package. I think it was an increase of about 15% salary as compared to my job at Mercedes-Benz.io at that time. It was slightly below my requested salary but that's okay because of what I'll explain now.</p>
<p>The Package included a signing bonus which would definitely cover a good part of moving costs including new furniture.</p>
<p>Far more interesting is the fact how they help you to get started. They want to free your thoughts from "doing all of the organizational stuff" </p>
<p>I would get an individual that will help me find a flat -  so that person will literally take care of that so you can focus on the company - which is pretty neat.</p>
<p>At Amazon, you will often get additional RSUs (Restricted Stock Units). Those are Stocks from the Company which are provided to you in parts over time. So a <em>random example</em> of RSUs is: You get 50 RSUs promised now and you will get 10 after 6 months, another 10 after 12 months, etc. And since one Stock at Amazon is worth a lot it comes down to a very awesome compensation package the longer you stay.</p>
<p>RSUs are also a very good benefit keeping you in the company for a longer time.</p>
<p>Last but not least they will help your significant other to find a new job as well - if required. So they will make sure that you can move carefree together with your family. Which I indeed really appreciated.</p>
<h2 id="heading-summary">Summary</h2>
<p>I hope this helps understanding potential interviewing workflows in big companies. To understand general recommendations about interviewing please also read my other post <a target="_blank" href="https://blog.activenode.de/mastering-frontend-interviews">Mastering Frontend Interviews</a> which is definitely also for non-Frontend people.</p>
<h2 id="heading-questions">Questions?</h2>
<p>Post all your questions here in the comments. I'd also appreciate to connect on Twitter: Find me on Twitter <a target="_blank" href="https://twitter.com/activenode">@activenode</a></p>
<p>Yeah, I think that's it. Hope you liked it.</p>
]]></content:encoded></item><item><title><![CDATA[beamco.de: A new code snippet creator is in town 🌈]]></title><description><![CDATA[When the sun was shining I could hardly read my own Code Snippets shared on Twitter as they lacked contrast and thickness. 
I know there is carbon and ray but those didn't exactly fit my needs of a fancy theme, simplicity and readability.
So I decide...]]></description><link>https://blog.activeno.de/beamcode</link><guid isPermaLink="true">https://blog.activeno.de/beamcode</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[tools]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Developer Tools]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Sun, 13 Feb 2022 20:21:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644783543808/JGcSNjkxa.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When the sun was shining I could hardly read my own Code Snippets shared on Twitter as they lacked contrast and thickness. </p>
<p>I know there is carbon and ray but those didn't exactly fit my needs of a fancy theme, <em>simplicity</em> and readability.</p>
<p>So I decided to go and create a new one and here we go:</p>
<p><strong><a target="_blank" href="https://beamco.de">beamco.de</a></strong></p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/siad9w2hoaldm37q1w98.png" alt="Sample image containing code" /></p>
<p>If you have constructive feedback and ideas for improvement please reach out to me.</p>
]]></content:encoded></item><item><title><![CDATA[`float:left` is not dead 🙇‍♂️]]></title><description><![CDATA[In the Tech Community in the last few years there have been strong opinions on things. But some things are not opinions. Some things can simply be explained by facts.
One of it is the assumption that display: flex or display: grid are alternatives fo...]]></description><link>https://blog.activeno.de/float-left-is-not-dead</link><guid isPermaLink="true">https://blog.activeno.de/float-left-is-not-dead</guid><category><![CDATA[Frontend Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Web Design]]></category><category><![CDATA[CSS]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Thu, 23 Dec 2021 10:07:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646838705925/E73P-Iakk.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the Tech Community in the last few years there have been strong opinions on things. But some things are not opinions. Some things can simply be explained by facts.</p>
<p>One of it is the assumption that <code>display: flex</code> or <code>display: grid</code> are alternatives for floating. <strong>They are not</strong>. Period.</p>
<p><img src="https://media.giphy.com/media/l41lIsOThQpma23wA/giphy.gif" alt /></p>
<hr />
<p>At this point you <em>might</em> think that I am crazy but let me elaborate before you agressively type comments destroying your keyboard.</p>
<p><code>display: flex</code> is born out of a requirement that CSS hasn't solved before: Proper Layouting. In other words: Using <code>display: block; float: ...</code> for layouts always was a workaround. Same as in the old times when we used <code>&lt;table ..&gt;</code> for layouting.</p>
<h2 id="heading-okay-but-then-why-should-i-still-use-float">Okay but then why should I still use <code>float: ...</code> ?</h2>
<p>As I said: It was never built for "layouting" in the sense of what we understand as "layouting" today. Such that we have to get back to what it's actually supposed to do.</p>
<p>As the term <code>float</code> already states it is supposed to let things <code>float</code> (no shit sherlock) as opposed to <code>display: flex</code> which <strong>cannot</strong> let things float. So it's an awesome helper to wrap text around any kind of <code>figure</code> which makes up for a very awesome reading experience if used correctly.</p>
<p>The pragmatic explanation will be done via my <a target="_blank" href="https://codepen.io/activenode/pen/XWeeZGe">CodePen</a> where Drake will help you to understand better:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/activenode/pen/XWeeZGe">https://codepen.io/activenode/pen/XWeeZGe</a></div>
<p>Reach out to me via Follow-Up Questions if you have any. 🔥</p>
]]></content:encoded></item><item><title><![CDATA[Mastering Frontend Interviews - For real]]></title><description><![CDATA[Why should you even listen to me?
I am a Frontend Architect with People Management Experience (So besides the technical experience I was happy to be working together with People Management, leading peers, building up interview processes etc.)
Amazon,...]]></description><link>https://blog.activeno.de/mastering-frontend-interviews</link><guid isPermaLink="true">https://blog.activeno.de/mastering-frontend-interviews</guid><category><![CDATA[interview]]></category><category><![CDATA[recruitment]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[frontend]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Tue, 21 Dec 2021 21:07:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646838279075/fF6yNiApp.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-why-should-you-even-listen-to-me">Why should you even listen to me?</h2>
<p>I am a Frontend Architect with People Management Experience (So besides the technical experience I was happy to be working together with People Management, leading peers, building up interview processes etc.)</p>
<p>Amazon, Mercedes-Benz.io, JvM, nodus medical and many more gave me the opportunity to work for them (meaning: I got an actual offer). Besides those few mentioned there were <strong>innumerable</strong> amounts of other interviews I was allowed to be part of - both as candidate as well as interviewer. I don't like to have tunnel vision when it comes to jobs. I do like to check opportunities from time to time because that helps me understanding the options out there as well as helps me staying in the routine of interviews.</p>
<h2 id="heading-what-this-post-is-about">What this post is about</h2>
<p>This post is not about which exact weird technical challenge you should prepare for (No you don't have to learn the Quicksort implementation in 10 languages by heart other than if you are applying for a company that's name is "Quicksort in 10 languages Inc"). This is about understanding what's behind the curtains of every good interview. I won't be talking about salary in this post because salary is just something so unique that it wouldn't fit the overall context of this post.</p>
<h2 id="heading-the-cv-and-your-application-letter">The CV and your application letter</h2>
<p>I appreciate your effort but honestly no one is special enough for anyone to read a book load of pages about what you've been doing and what kind of food you eat at 5am in the morning.</p>
<p>Most of the companies love a one page CV, one page application letter. If you say "that doesn't fit on simply one page" then you are showing your incapability of prioritisation. I know you want to show everything but the company just doesn't have the time to read the story of your life.</p>
<p>So if you have worked with 30 different stacks and technologies then you are very much kicking yourself out of being even invited if you list all of those next to each other. Being a FE Developer you should be highlighting your primary FE Skills. If you have worked with Cloud Technologies and Backend then that's cool but keep it short e.g. "Also I have worked with a lot of cloud and backend technologies and I love getting my hands dirty at databases". </p>
<p>Also don't send the exact same letter for every single position. If the role you are applying for states "You will be working on an Angular 9 Product" then it is helping you a lot if you highlight that technology first. This can obviously lead to the fact that you <strong>should</strong> mention your cloud technologies if the role specifically states that this is beneficial - if not, leave it out.</p>
<h2 id="heading-prepare">Prepare</h2>
<h3 id="heading-prepare-structurally">Prepare structurally</h3>
<p>If you get invited to an interview and the interview process is professional then the responsible person is superhappy to tell you how the interview will be structured - if you ask for it. If you don't ask for it you will literally be expecting anything.</p>
<p>Send them a nice mail or call them and ask "Could you tell me how the interviewing process is structured? Will there be time for questions and will there be a live challenge?" etc.</p>
<p>There is nothing wrong with asking how the interview will be processed and what to expect - every client can be different so every interview can have different workflows.</p>
<h3 id="heading-prepare-contentual">Prepare contentual</h3>
<p>I remember those times of "inform yourself about what the company does". IMO this is not necessary anymore. No one will reject to hire you because you didn't know that the company has 120 employees - so forget about that stuff.</p>
<p>But you should still prepare and inform yourself about the company to be able to ask proper questions and hence impressing with showing your underlying motivation.
This allows both of you to see if it is a fit or not. You don't necessarily have to "lie" that you "love" the products the company creates. It is sufficient if you like its process around the development part that is part of the products - on which you will work.</p>
<p>If you read on the roles description: "We are a high performing team" and you feel that this sounds like "we are doing a lot of over-hours" then write it down and prepare to ask if they can clarify what "high performing team" means. </p>
<p>But not just that. Ask what <strong>exactly</strong> you'd be doing. That is a completely valid question. As in "So I read you are working for multiple clients here, how does a typical Frontend Coders Day/Week look like in your company?".</p>
<p>Also ask about the culture which helps both of you identifying if this is what you are searching for / they are searching for.</p>
<p>But first and foremost: Don't start asking questions in the beginning like "Ok before we start I got some questions". I did that sometimes if I felt the urge of importance but I still do not recommend it as it can have an impression of being rude if you are not being very diplomatic. So rather don't and wait for the interviewer to give you space for questions. </p>
<p>If the interviewer does not give you space for questions it feel encouraged to say: "Thanks for this interviewing process so far. [...] May I ask some questions about the company and the job role?".</p>
<p>No question is a "dumb" question if stated friendly and with honest interest.</p>
<h2 id="heading-lets-talk-about-interviewing">Let's talk about Interviewing</h2>
<p>Coders be like "Oh shit, what if I can't answer this?". And then they might fall into a deep black hole if there was a question that they felt uncomfortable with and at that point I have seen many interviews failing.</p>
<p>The Problem is that many don't understand what the point of interviewing is. It is checking your capabilities of solving problems at the base of your current level given the expectations that you set. That means: I can ask the EXACT same questions in an interview to a senior as to a junior but I'd be expecting completely different outcomes and both could be hired respectively.</p>
<p>What's the trick? Act curious instead of being challenged. Try to imagine all of it less as a "test" and more like a "tell me more discussion". And not only that. Think and explain in pseudocode if you can't provide legit facts.
Literally the worst thing you can say is "I don't know". A few "I don't know"'s and you are out. And not because you didn't know but because you showed that you aren't even trying to solve the problem - not even slightly.</p>
<h3 id="heading-scenarios">Scenarios</h3>
<h4 id="heading-scenario-1-sorting-algorithm-question">Scenario 1: Sorting Algorithm Question</h4>
<p><strong>Interviewer</strong>: "Do you know which is the fastest Sorting Algorithm?" <br />
<strong>You</strong>: "Sorry, no" - Awkward silence 🙅🏽‍♀️😐</p>
<p>This is close to ending the meeting soon. Here is a proposal of being curious instead:</p>
<p><strong>You</strong>: "I don't have that at hand but I would love to know where the answer to this would help within your products scope if I may. I'd be assuming that JS engines would to their best to have a fast sorting algorithm. If that wouldn't be enough I would make sure to research properly how to improve the performance if there is a need detected." - 🤗</p>
<h4 id="heading-scenario-2-typeof-null-question">Scenario 2: <code>typeof null</code> Question</h4>
<p><strong>Interviewer</strong>: "Do you happen to know what <code>typeof null</code> is?"</p>
<p>Even if you know the answer to this question (it is 'object') then be rest assured that this is not a key-value test. These questions normally come with a follow up question. There is always "context" around a question.</p>
<p>So say you didn't know that <code>typeof null</code> equals <code>object</code>. Then the worst thing you can do is random guessing. This is not playing lotto and the interviewer doesn't like to be played. They will notice.
If you have a really good guess or you slightly remember something then explain your guess and let the interviewer follow your thoughts: <strong>Think out loud</strong>! Nothing worse than awkward silence because you think you need to think silently.</p>
<p>If you have no clue then simply say something like: "I am pretty sure there is a good reason you asked this. Would you mind to tell me the solution and eventually have a follow-up question on this?"</p>
<p>Even though not knowing you are showing your willingness to go with further questions in this context after being told the solution. A very much follow-up question probably is: "Can you imagine this check being problematic?" - Now, same rules: Start to think loud. Speak up what you think - as if you were googling. Start one by one: "Okay so if <code>typeof null</code> is <code>object</code> then that implies that a nullish/falsy value can be seen as object if checked with <code>typeof</code>. That means that one shouldn't check for something being an object only with <code>typeof</code> because it could be also <code>null</code>." - You are literally explaining it to yourself AND to the interviewer and hence showing your skills to solve problems at hand.</p>
<h2 id="heading-seniors-seniors-seniors">Seniors, Seniors, Seniors</h2>
<p>There is some addendum that is important for Senior Frontend Engineers. The huge difference between Juniors and Seniors is that a Senior actually should be able to answer most of the questions asked at the expert level they <em>present themselves with</em>. And by that I am not saying "They must know every single property / function by heart".</p>
<p><strong>What does that mean?</strong></p>
<p>With Juniors I mostly ask the same kind of questions. With Seniors that's different. I know you cannot keep up with every single technology but you must be extremely proficient in a specific technology and the basics (HTML, JS, CSS) so tldr: Your primary skill of the last project + Basics.</p>
<p>That is why I completely adapt interviews with Seniors <em>on-demand</em>. I do ask the Seniors beforehand about their proficiencies. If the person is being honest saying "I think I missed out one some CSS in the last 2 years but I am really good at XYZ" then I am happy to be gentle with CSS questions and focus more on XYZ (as stated above, it's hard to keep up with everything). If a senior tells me that the proficiency lies in Angular I will focus on asking Angular-specific questions. Even if it is a position as a React Developer. The reason is simple: If the Senior can deeply elaborate on my questions considering the provided proficiency on expert level then I have no doubt that this person has the capability of understanding the architecture of another framework.</p>
<p><strong>And now comes the pitfall</strong>: Seniors often don't expect me to ask <em>basic</em> questions which is honestly shocking for me every single time. And with <em>basic</em> I don't mean "Which exact CSS property will let boxes to be aligned next to each other" -  it is sufficient to know that <code>display: flex</code> exists and that you can do a lot of alignments whichever way with it. Details: Google.</p>
<p>But if a senior starts telling me that <code>float: left</code> is a good way nowadays to align boxes then it shows that that person must've ignored every single news on the internet in the last past years.</p>
<p>Also one of my favourite questions for seniors is to explain me the arrow function. And if a senior says "It's a function but with a different syntax" then this is a <strong>definite reason to be rejected</strong>. For good reason: The arrow function binds context - and it binds it in a way that is unchangeable. So even the functions <code>.bind</code>, <code>.apply</code> and <code>.call</code> cannot change that context. But they also wouldn't throw an error. So if a senior does not know that an arrow function changes context immutably then that Senior would've a hard time debugging if there was a legacy library that would be making use of older functions but now providing arrow functions leads to problems - without throwing errors.</p>
<p>In my experience Seniors often oversell. So if you are insecure about being Senior then rather sell as Intermediate and surprise with potential Senior knowledge than sell as Senior and surprise with disappointment. When I do ask "How would you rank your JS knowledge on a scale from 1 to 10" they often go to 8 or 9. Because they don't do much of self-reflection anymore. That backfires. And this happens in a <strong>lot</strong> of interviews. And this is something that really only happens with Seniors, rarely with Intermediates or Juniors. The problem is that seniors are often doing something very specific in a project. And more often than never they are solving the product needs with that specific solution and that might be perfectly fine and in a way that is senior'ish. The problem is that they forget that they are often "living in a technology tunnel" without learning new things and keeping up with how JS evolves. But they <strong>must</strong> make sure to keep up with the basics. 
And not just that. They also must ensure to not forget the basics. Because if they need to dig deeper (not every 3d-party library is perfectly working) they might need to be working outside of the scope of the framework with pure JavaScript. And that shouldn't be a huge challenge for them.</p>
<p>My suggestion here is simple: Stay humble and at least subscribe to 1 JavaScript Newsletter. That should already be a good start.</p>
<h2 id="heading-rejection-handling">Rejection Handling</h2>
<p>Rejections are hard. As always in life. And you must prepare for being rejected. Expect to be rejected. 
And if you get rejected then see it as just one step of a <em>potentially large</em> but definitely <em>finite</em> ladder. Because every single rejection can be seen as "a practice step for the next interview". This is hard but crucial for your mental wellbeing and for getting better.</p>
<p>Also don't just be mad. Answer all rejections with the question for feedback: "Thank you for having invited me. Although it wasn't a fit I would be extremely happy if you could provide me more insights and feedback that will allow me to improve". You'd be surprised how much feedback you will get - Sure, there's exceptions but the worst thing that can happen is that you don't get an answer.</p>
<p>Feedback gives you useful insights what exactly was wrong. 
Many don't ask for feedback and simply lower their self-esteem with the implication of "simply not being good enough" instead of acknowledging that it's only a step of becoming better.</p>
<h2 id="heading-a-last-note">A last note</h2>
<p>Try to be yourself. Yes it can happen to "struggle" oneself into a position but that doesn't come with a bunch of happiness. </p>
<p>Sometimes it just isn't a fit. Everyone's different, everyone's special. Just like Friends and Relationships: Not all people bond well together. That's fine.</p>
<hr />
<p>Phew. That was a bunch of text. I hope it helps.</p>
]]></content:encoded></item><item><title><![CDATA[JavaScript Promises tl;dr]]></title><description><![CDATA[Stop the talk, let's start getting into it.
Promises always chain
If then or catch return a value that is NOT a promise then it will be wrapped into a new promise and chained and forwarded to the next one. That means starting from a catch you can ret...]]></description><link>https://blog.activeno.de/javascript-promises-tldr</link><guid isPermaLink="true">https://blog.activeno.de/javascript-promises-tldr</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[promises]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Mon, 20 Dec 2021 10:45:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646821375360/OzVeXv-1J.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Stop the talk, let's start getting into it.</p>
<h2 id="heading-promises-always-chain">Promises always chain</h2>
<p>If <code>then</code> or <code>catch</code> return a value that is NOT a promise then it will be wrapped into a new promise and chained and forwarded to the next one. That means starting from a <code>catch</code> you can return a value and <code>.then</code> it.</p>
<p>All of the samples here will output <code>Hello World1</code></p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> appendWorld = <span class="hljs-function"><span class="hljs-params">s</span> =&gt;</span> <span class="hljs-string">`<span class="hljs-subst">${s}</span> World`</span>;
<span class="hljs-keyword">const</span> appendOne = <span class="hljs-function"><span class="hljs-params">s</span> =&gt;</span> <span class="hljs-string">`<span class="hljs-subst">${s}</span>1`</span>;
<span class="hljs-keyword">const</span> log = <span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(v);

<span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-string">'Hello'</span>).then(appendWorld).then(appendOne).then(log);
<span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-string">'Hello'</span>).then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> <span class="hljs-built_in">Promise</span>.resolve(appendWorld(v))).then(appendOne).then(log);
<span class="hljs-built_in">Promise</span>.reject(<span class="hljs-string">'Hello'</span>).catch(appendWorld).then(appendOne).then(log);
<span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-string">'Blogging'</span>).then(<span class="hljs-function">() =&gt;</span> <span class="hljs-string">'Hello'</span>).then(appendWorld).then(appendOne).then(log)
</code></pre>
<h2 id="heading-finally">finally</h2>
<p><code>finally</code> cannot return a value that can be chained. Kind of implied by it's name. It is called no matter if another <code>.then</code> or <code>.catch</code> was called before. When the Promise was fulfilled in any way then <code>.finally</code> is called. Good for cleanup work.</p>
<p>E.g. </p>
<pre><code class="lang-js"><span class="hljs-built_in">Promise</span>.reject()
  .catch(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Catch is called'</span>))
  .finally(<span class="hljs-function">(<span class="hljs-params">s</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'finally called'</span>))
</code></pre>
<p>outputs </p>
<pre><code class="lang-text">Catch is called
finally is called
</code></pre>
<h2 id="heading-errors-inside-a-promise-are-forwarded-to-catch">Errors inside a promise are forwarded to <code>.catch</code></h2>
<pre><code>Promise.resolve()
  .then(() <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {})
  .then(() <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'hey'</span>) })
  .then(() <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> console.log(<span class="hljs-string">'i am never called'</span>))
  .catch(() <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> console.log(<span class="hljs-string">'error'</span>));
</code></pre><h2 id="heading-multiple-catch-statements-are-useful">Multiple <code>.catch</code> statements are useful</h2>
<pre><code class="lang-js"><span class="hljs-built_in">Promise</span>.resolve()
  .then(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">Promise</span>.reject())
  .catch(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'much rejection'</span>))
  .then(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'i can continue doing stuff'</span>))
  .then(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">Promise</span>.reject(<span class="hljs-string">'another one'</span>))
  .catch(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'catching the second chain'</span>))
</code></pre>
<h2 id="heading-async-functions-are-promise-wrappers"><code>async</code> functions are Promise Wrappers</h2>
<p>The following code statements have the same effect:</p>
<pre><code><span class="hljs-comment">// async</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foobar</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-string">'foo'</span>;
}

<span class="hljs-comment">// non-async</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">foobar</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-string">'foo'</span>);
}
</code></pre><h2 id="heading-awaiting-promises-must-be-done-carefully"><code>await</code>ing promises must be done carefully</h2>
<p>If you <code>await</code> a promise then you need to be careful when checking for "success" because errors can be hidden.</p>
<p>See the following example:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> foobar = <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.reject(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'error thrown'</span>)).catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> error);

<span class="hljs-keyword">if</span> (foobar) {
  <span class="hljs-comment">// This does not imply success ⚠️👩‍🚀</span>
} <span class="hljs-keyword">else</span> {
 <span class="hljs-comment">// This does not imply an error case</span>
}
</code></pre>
<p>The problem is that the provided promise is properly caught. Referring back to promise-chaining now the result of the <code>catch</code> statement can be chained, hence <code>new Error...</code> is the resulting object if you'd call <code>.then</code> on it. And that is simply the same as calling <code>await</code> on it. So here <code>foobar</code> contains <code>new Error...</code> which is an object which when checking for <code>if(foobar)</code> returns true although an error was thrown. So you need to be aware of what your promises return.</p>
<h2 id="heading-promiserace-and-promiseany"><code>Promise.race</code> and <code>Promise.any</code></h2>
<p>Both <code>race</code> and <code>any</code> complete with the Promise whichever is first. <strong>But</strong> there is a big difference: <code>race</code> finishes  with the first Promise to <strong>EITHER</strong> resolve <strong>OR</strong> reject whilst <code>any</code> finishes only with the first actually resolved Promise.</p>
<p>In this <code>Promise.race</code> sample the error Promise wins because it is first:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> promise1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(reject, <span class="hljs-number">100</span>));
<span class="hljs-keyword">const</span> promise2 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">300</span>));
<span class="hljs-built_in">Promise</span>
  .race([promise1, promise2])
  .then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'resolved'</span>, v))
  .catch(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'error'</span>, v));
</code></pre>
<p>In this <code>Promise.any</code> sample the resolved Promise wins because it is the first to actually resolve:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> promise1 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(reject, <span class="hljs-number">100</span>));
<span class="hljs-keyword">const</span> promise2 = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(resolve, <span class="hljs-number">300</span>));
<span class="hljs-built_in">Promise</span>
  .any([promise1, promise2])
  .then(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'resolved'</span>, v))
  .catch(<span class="hljs-function"><span class="hljs-params">v</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'error'</span>, v));
</code></pre>
<h2 id="heading-promiseall">Promise.all</h2>
<p>This one is pretty intuitive: It either resolves when ALL promises are resolved OR it rejects when one of the promises is rejected.</p>
<pre><code class="lang-js"><span class="hljs-comment">// outputs ['one', 'two']</span>
<span class="hljs-built_in">Promise</span>.all([<span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-string">'one'</span>), <span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-string">'two'</span>)])
.then(<span class="hljs-function">(<span class="hljs-params">resultArray</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(resultArray))
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// outputs 'error'</span>
<span class="hljs-built_in">Promise</span>.all([<span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-string">'one'</span>), <span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-string">'two'</span>), <span class="hljs-built_in">Promise</span>.reject()])
.then(<span class="hljs-function">(<span class="hljs-params">resultArray</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(resultArray))
.catch(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'error'</span>))
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Don't be a pr*ck: Frontend Engineers and Accessibility]]></title><description><![CDATA[The following code will upset you
You are a Frontend Developer. You start in a new company and you find code like this:
const data = await fetchData();
const a = [];

data.map( item => a.push({ t: item.subject, i: item._id })

Probably your first tho...]]></description><link>https://blog.activeno.de/frontend-a11y-primer</link><guid isPermaLink="true">https://blog.activeno.de/frontend-a11y-primer</guid><category><![CDATA[a11y]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[David Lorenz]]></dc:creator><pubDate>Sun, 19 Dec 2021 15:55:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646822660710/skec8uA5A.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-following-code-will-upset-you">The following code will upset you</h2>
<p>You are a Frontend Developer. You start in a new company and you find code like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> fetchData();
<span class="hljs-keyword">const</span> a = [];

data.map( <span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> a.push({ <span class="hljs-attr">t</span>: item.subject, <span class="hljs-attr">i</span>: item._id })
</code></pre>
<p>Probably your first thought is: <em>WTF is this 💣</em>.</p>
<p>Let's make it nice:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> listOfTasks = <span class="hljs-keyword">await</span> fetchTasks();
<span class="hljs-keyword">const</span> idAndTitleList = listOfTasks
     .map(<span class="hljs-function">(<span class="hljs-params"> { subject, _id } </span>) =&gt;</span> ({ <span class="hljs-attr">title</span>: subject, <span class="hljs-attr">id</span>: _id }));
</code></pre>
<h3 id="heading-did-you-feel-the-anger-in-the-first-sample">Did you feel the anger in the first sample?</h3>
<p>You felt it! You felt it because it would've been so damn easy to make it clean and readable. Hence it doesn't matter "why it came to be there". It matters that obviously no one prevented this code to be merged (missing guidelines or what not) and that you suffer in a sense of Developer Experience.</p>
<p>Developer Experience to you is comparable to accessibility features to people that depend on it.</p>
<p><strong>This is still a very much harmless example comparing how'd you feel if you were dependent on accessibility features because it wouldn't take much time on an atomic basis to improve the sites accessibility but you decided to not do it. And when the app/site is done it'd be a huge thing to adapt so you never do.</strong></p>
<h2 id="heading-accessibility-is-not-hard">Accessibility is <em>not</em> hard</h2>
<p>And often not a choice because:</p>
<blockquote>
<p>If you do recommend to NOT implement proper accessibility in your application you are actually consulting for something that has legal impact in a lot of countries now. So that's, first and foremost, a very very good reason to inform yourself and your colleagues about accessibility even more. 
 Source: https://www.w3.org/WAI/policies/</p>
</blockquote>
<p>So if you are not developing on / in / for a lonely island then there is a good chance there is legal rules for it.</p>
<p>I have heard this iffy saying so often. From Frontend Engineers, from Designers but especially from Product Owners and Managers trying to intrigue the engineers to "save time":</p>
<h3 id="heading-we-can-do-it-later">"We can do it later"</h3>
<p>Technically I don't see a problem in "doing it later". But let me take a metaphor for it: A fork lies on your table. You can put it in the shelf right now and your room looks amazingly clean. A rush of endorphines hits your body as it comforts with the tidyness. Easygoing. Now imagine you leave everything laying around in your room for a year. Now start cleaning the room - start even finding anything. You get the point...</p>
<p><img src="https://media.giphy.com/media/8csqNT29i6JTAGMrFT/giphy.gif" alt /></p>
<h3 id="heading-people-with-disabilities-are-not-the-target-group-anyways">"People with disabilities are not the target group anyways"</h3>
<p>This statement never holds true. Never. Not in any single case for any application that is used by more than 1 person.</p>
<p>I have heard this in an automotive sector often saying "blind people cannot drive so how would a11y help?". </p>
<p>Ehm well a blind person can still be controlling the digital sales part of the automotive sector. Just to have a very, very clear example. I could add thousands more if you want.</p>
<p>Also bad accessibility always impacts pro users because it often comes with bad keyboard usage.</p>
<h3 id="heading-okay-ill-add-an-aria-label-and-some-alt-attributes">"Okay I'll add an <code>aria-label</code> and some <code>alt</code> attributes"</h3>
<p>Fk no. No no no. Don't just start adding random <code>aria-*</code> attributes or alt/title tags if you do not know the impact. Start with the basics of understanding. Understanding is the crucial point of effortlessly using it and implementing it whilst coding. I would recommend myself but the problem is that I don't have any public sources on my own so I would need to lend you my brain. </p>
<ul>
<li>There is an <strong>extremely</strong> good free udacity course from Google (I really, really can recommend this):  https://www.udacity.com/course/web-accessibility--ud891</li>
<li>Read this: https://developers.google.com/web/fundamentals/accessibility/semantics-builtin/the-accessibility-tree</li>
<li>Also you can start off at  <a target="_blank" href="https://twitter.com/SaraSoueidan">Sara Soueidan</a>. She also has published a new course which you will find a link on her Twitter account to.</li>
<li>A good read is always MDN e.g. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/heading_role </li>
</ul>
<h2 id="heading-let-me-prove-how-easy-it-can-be-to-improve-accessibility">Let me prove how easy it can be to improve Accessibility</h2>
<ul>
<li>Understand that CSS impacts a11y: If you do <code>display: none</code> on an element it is hidden both visually as well as in the <strong>Accessibility Tree</strong> so your <code>&lt;div style="display: none" aria-label="additional info only for screen readers"&gt;...</code> is useless.</li>
<li>Ensure good ratio on your designs (built-in in the chrome inspector; there is also a lot of Sketch plugins for Designers e.g.) ; https://webaim.org/resources/contrastchecker/</li>
<li>Using a proper HTML structure is a very good start. HTML by definition (without adding CSS etc.) is perfectly accessible if correctly used. https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML</li>
<li>If you have fancy elements on your side that literally have no effect but looking cool (so content-wise not relevant) then simply hide em' semantically with <code>aria-hidden="true"</code> </li>
<li>The <code>alt</code> attribute on a <code>img</code> tag is nothing that necessarily needs content. It needs content if the image shown is connected to the content. E.g.: You have a news article and you report about "More and more people visit the new shopping center". Now imagine there is an <code>img</code> tag with a photo showing a lot of people in the shopping center. Then a good alt tag would be <code>alt="A lot of people standing in the new Shopping Center the city"</code> . If however the image is just a random stock picture then it doesn't provide additional information and you should have <code>alt=""</code> (in this case the content lives for itself and the image is just a visual addendum).</li>
<li>If you use modals, make sure to "Lock In". If you cannot click elements below the Modal with your mouse then you shouldn't be able to tab with your keyboard below it. But many modals do that and it's horrible for people working with screen readers because they often cannot get back to the modal once they left it. I also built one React Library to help with that: https://github.com/activenode/react-use-focus-trap</li>
</ul>
<h2 id="heading-now-stop-being-a-prick-and-at-least-inform-yourself-a-little-bit">Now stop being a prick and at least inform yourself a little bit.</h2>
<p>Providing a good semantic HTML structure, knowing how and when to properly set <code>alt</code> attributes (most FE Developers think they know this but in fact they don't) and the impact of using <code>aria-*</code> attributes can be a very good start for having basic a11y. That doesn't sound like a huge effort, does it?</p>
<p><img src="https://media.giphy.com/media/26FPOogenQv5eOZHO/giphy.gif" alt /></p>
]]></content:encoded></item></channel></rss>