- json 2017-03-19T16:58:15+01:00 https://fadeit.dk/blog/tag/json.html Firebase advanced data modelling and role based authorization 2016-08-25T16:10:30+02:00 https://fadeit.dk/blog /2016/08/25/firebase-advanced-data-modelling-and-role-based-authentication-authorization <p>It’s been almost 5 years since Firebase was released. It looked cool from the beginning, but I never got to use it in a project. <br /> That’s going to change today, because I am set on using it and there’s no turning back.</p> <p>I’m not experienced with it, so this is just my idea of how to handle things. If you have a better idea, feel free to share so we can all become better at Firebasing!</p> <h2 id="project-requirements">Project requirements</h2> <p>Before diving deep into how I modelled my data, it’s important to understand what are the entities involved, how they relate to each other &amp; what kind of roles I have to deal with.</p> <p>I’ll try not to go into details; they shouldn’t affect my general approach (the stupid devil is <strong>*always*</strong> in the detail).</p> <h4 id="entities">1. Entities</h4> <p>I called the main objects in my real-time database entities: name them as you wish, they are just top-level creatures. The main rule I followed when designing these entities: avoid deep nesting (<a href="https://www.youtube.com/watch?v=9gnvC-xIDgs">related</a>, by <a href="http://twitter.com/chrisesplin">Chris Esplin</a>).</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">Auth</span><span class="err">:</span> <span class="p">{</span> <span class="cm">/* Model designed by Firebase, we can't touch this tanana */</span> <span class="p">}</span> <span class="nl">Users</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* $uid from Firebase Auth */</span> <span class="nl">$uid</span><span class="p">:</span> <span class="p">{</span> <span class="nl">role</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="c1">// i.e. 'admin', more on that later</span> <span class="p">}</span> <span class="p">}</span> <span class="nl">Projects</span><span class="p">:</span> <span class="p">{</span> <span class="nl">$pid</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* Metadata */</span> <span class="p">}</span> <span class="p">}</span> <span class="nl">Files</span><span class="p">:</span> <span class="p">{</span> <span class="nl">$pid</span><span class="p">:</span> <span class="p">{</span> <span class="nl">$fid</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* File */</span><span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="nl">Tasks</span><span class="p">:</span> <span class="p">{</span> <span class="nl">$pid</span><span class="p">:</span> <span class="p">{</span> <span class="nl">$tid</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* Task */</span><span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="nl">Messages</span><span class="p">:</span> <span class="p">{</span> <span class="nl">$pid</span><span class="p">:</span> <span class="p">{</span> <span class="nl">$mid</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* Message */</span><span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p><br /></p> <h4 id="user-roles--relationships">2. User roles &amp; relationships</h4> <p>Once more, I might be omitting some details here and there. Here are the main roles &amp; their permissions:</p> <ul> <li> <p>Admin</p> <ul> <li><strong>Everything</strong>: Create, Read, Update, Delete</li> </ul> </li> <li> <p>Manager</p> <ul> <li><strong>Projects</strong>, <strong>Tasks</strong>, <strong>Files</strong>: Create, Read, Update, Delete</li> <li><strong>Files</strong> Create, Read, Update, Delete</li> <li><strong>Messages</strong>: Create, Read, Update, Delete</li> <li><strong>Tasks</strong>: Create, Read, Update, Delete</li> <li><strong>Users</strong>: Create, Read</li> <li><strong>Self</strong> Read, Update, Delete (users/{$ownUid})</li> </ul> </li> <li> <p>User</p> <ul> <li><strong>Projects</strong>, <strong>Tasks</strong> Read</li> <li><strong>Files</strong> Create, Read, Update, Delete</li> <li><strong>Messages</strong> Read</li> <li><strong>Own Messages</strong> Create, Update, Delete</li> <li><strong>Tasks</strong> Read, Update</li> <li><strong>Self</strong> Read, Update, Delete (users/{$ownUid})</li> </ul> </li> <li> <p>External users / collaborators</p> <ul> <li><strong>Some Projects</strong> Read</li> <li><strong>Some Tasks</strong> Read</li> <li><strong>Files</strong> Create, Read</li> <li><strong>Own Files</strong> Update, Delete</li> <li><strong>Self</strong> Read, Update, Delete (users/{$ownUid})</li> </ul> </li> </ul> <h2 id="general-approach">General approach</h2> <p>As you can see, it’s quite messy. Expressing this with conditionals (i.e. <code class="highlighter-rouge">if(user.admin === true){...}</code>) would be a terrible idea. Here’s why:</p> <ul> <li>hard to document</li> <li>hard to maintain</li> <li>hard to change / scale</li> <li>code coupling</li> <li>a pain to express as Firebase’s JSON rules</li> </ul> <p>I’d like to avoid all this so I ditched the idea quickly. In fact, I can’t see how this approach would work with most tools / systems, not only with Firebase, but that’s another story. <br /> What I do want to go for instead is an activity-based authorization system.</p> <p>Here’s how that’ll work: By default no user has any permission. Then, I create a new entity in my real-time database, called <strong>Permissions</strong> where I can define roles (that give certain permissions). I’ll key by role name and write all permissions for a certain role. Example: <br /></p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">Permissions</span><span class="err">:</span> <span class="p">{</span> <span class="nl">user</span><span class="p">:</span> <span class="p">{</span> <span class="c1">// User role</span> <span class="nl">users</span><span class="p">:</span> <span class="p">{</span> <span class="nl">own</span><span class="p">:</span> <span class="p">{</span> <span class="c1">// can read, update, delete own data</span> <span class="nl">r</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">u</span><span class="err">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">d</span><span class="err">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="nx">other</span><span class="err">:</span> <span class="p">{}</span> <span class="c1">// can't read/write 'other' (not owned) data; we can even omit this field</span> <span class="p">},</span> <span class="cm">/* * ... * skipped some entities because 'messages' are more interesting * ... */</span> <span class="nx">messages</span><span class="err">:</span> <span class="p">{</span> <span class="nl">c</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// can create messages</span> <span class="p">},</span> <span class="nx">message</span><span class="err">:</span> <span class="p">{</span> <span class="nl">own</span><span class="p">:</span> <span class="p">{</span> <span class="c1">// can update &amp; delete own messages</span> <span class="nl">r</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">u</span><span class="err">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">d</span><span class="err">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="nx">other</span><span class="err">:</span> <span class="p">{</span> <span class="c1">// can read other messages</span> <span class="nl">r</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>A small note here, my permissions mirror CRUD, but they can be anything really. I also grouped them in ‘own’ &amp; ‘other’ for readability, but it might as well be <code class="highlighter-rouge">canReadOwn</code> or <code class="highlighter-rouge">canReadOther</code>. For my use case the above is good enough, but you might choose to have more fine-grained control in yours.</p> <p>The way this will work is that upon creation, users are assigned a role. It’s important to note that only admins can add users to this app (there’s no open registration… kind of). Because I don’t want to use a back-end, there’s an immediate problem with this: there’s actually no way to restrict Firebase registration through the console. With some nifty scripts clever people could create an account for themselves, but the joke’s on them. <br /> Firstly, I’ll make sure a <code class="highlighter-rouge">changeRole</code> permission exists for writing to <code class="highlighter-rouge">/users/$uid/role</code>, which only admins have (they’ll have permission to do everything, including this). <br /> Secondly, if there’s no <code class="highlighter-rouge">/users/$uid/role</code> set, I restrict all read &amp; write access. That should keep those pesky clever users from messing with my database.</p> <p>Now that’s fine and dandy, but how to actually implement this?<br /> Don’t worry friend, we got this.</p> <h2 id="bolt-ftw">Bolt FTW</h2> <p>If you didn’t come across it yet, <a href="https://github.com/firebase/bolt">Firebase Bolt</a> is a compiler for Firebase rules (it might be illegal, but could we call it a Firebase rules transpiler?!). It’s supposedly experimental, so make sure to double-check the output after compiling (unit test!).</p> <p>Now, even with Bolt, I’ve noticed I’d be in trouble already. I want to have CRUD-like rules, but Firebase only knows about Read / Write. How to restrict deletion then? <br /> I had to choose between (I believe) two options:</p> <ul> <li><strong>A.</strong> build objects such that they have <code class="highlighter-rouge"><span class="p">{</span><span class="w"> </span><span class="err">public:</span><span class="w"> </span><span class="err">{/*</span><span class="w"> </span><span class="err">what</span><span class="w"> </span><span class="err">I</span><span class="w"> </span><span class="err">have</span><span class="w"> </span><span class="err">so</span><span class="w"> </span><span class="err">far</span><span class="w"> </span><span class="err">*/</span><span class="w"> </span><span class="p">}</span><span class="err">,</span><span class="w"> </span><span class="err">private:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="err">deleted:</span><span class="w"> </span><span class="err">Boolean</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="err">}</span></code> paths &amp; add a field named ‘deleted’ on <code class="highlighter-rouge">private</code>, for which I add stricter write rules, i.e. only admins will have permissions to write (NB: this way data will never actually be deleted). Then, I’d tweak the rules to prevent access for items with <code class="highlighter-rouge">private.deleted === true</code></li> <li><strong>B.</strong> use <code class="highlighter-rouge">newData.exists()</code> together with my permissions, which will prevent deletion for users that are not authorized to delete</li> </ul> <p>I went with <strong>B.</strong>, but some might find <strong>A.</strong> to be suitable: that way data can be restored later if needed. After I’ll deploy the first version of the app, the only way to change my decision is via (probably) a complicated migration script.</p> <p>I’ll have a similar issue with ‘create’. There’s no create rule in Firebase, but there are ways to make that work too.<br /> Long story short, Bolt supports splitting the <code class="highlighter-rouge">write</code> rule into <code class="highlighter-rouge">create</code>, <code class="highlighter-rouge">update</code>, <code class="highlighter-rouge">delete</code> (<a href="https://github.com/firebase/bolt/blob/master/docs/language.md#write-aliases">see here</a>). It’s a matter of preference in the end, but I’ll stick with it for the sake of simplicity. You can achieve the same in a more explicit manner.</p> <h2 id="bolt-implementation">Bolt implementation</h2> <p>Finally, the fun part. I’m going to sketch out how to implement this in Bolt, but take it with a pinch of salt. Most likely I’ll write up a quick new post on what changes I did to it before actually deploying. For now, here goes:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="cm">/* * Checks if a given action is allowed. * * @param {object} obj The object to check. * @param {string} entity The entity to check. * @param {string} permission The permission to check for. * @param {boolean} ownResources A flag that disables 'own' data checks if set (if false, users can perform an action if they own the resource) * */</span> <span class="nx">isAllowed</span><span class="p">(</span><span class="nx">obj</span><span class="p">,</span> <span class="nx">entity</span><span class="p">,</span> <span class="nx">permission</span><span class="p">,</span> <span class="nx">ownResources</span><span class="p">)</span> <span class="p">{</span> <span class="nx">auth</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="nx">auth</span><span class="p">.</span><span class="nx">uid</span> <span class="o">&amp;&amp;</span> <span class="c1">// has to be authenticated</span> <span class="nx">root</span><span class="p">.</span><span class="nx">users</span><span class="p">[</span><span class="nx">auth</span><span class="p">.</span><span class="nx">uid</span><span class="p">].</span><span class="nx">role</span> <span class="o">&amp;&amp;</span> <span class="c1">// has to have a role set</span> <span class="p">(</span> <span class="nx">auth</span><span class="p">.</span><span class="nx">uid</span> <span class="o">!==</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">owner</span> <span class="c1">// user is not owner of the entity</span> <span class="p">?</span> <span class="nx">root</span><span class="p">.</span><span class="nx">permissions</span><span class="p">[</span><span class="nx">root</span><span class="p">.</span><span class="nx">users</span><span class="p">[</span><span class="nx">auth</span><span class="p">.</span><span class="nx">uid</span><span class="p">].</span><span class="nx">role</span><span class="p">][</span><span class="nx">entity</span><span class="p">].</span><span class="nx">all</span> <span class="o">===</span> <span class="kc">true</span> <span class="o">||</span> <span class="c1">// allow everything</span> <span class="cm">/* entity w/o owner: doesn't require extra nesting in the permissions obj (.other) */</span> <span class="nx">root</span><span class="p">.</span><span class="nx">permissions</span><span class="p">[</span><span class="nx">root</span><span class="p">.</span><span class="nx">users</span><span class="p">[</span><span class="nx">auth</span><span class="p">.</span><span class="nx">uid</span><span class="p">].</span><span class="nx">role</span><span class="p">][</span><span class="nx">entity</span><span class="p">][</span><span class="nx">permission</span><span class="p">]</span> <span class="o">===</span> <span class="kc">true</span> <span class="o">||</span> <span class="nx">root</span><span class="p">.</span><span class="nx">permissions</span><span class="p">[</span><span class="nx">root</span><span class="p">.</span><span class="nx">users</span><span class="p">[</span><span class="nx">auth</span><span class="p">.</span><span class="nx">uid</span><span class="p">].</span><span class="nx">role</span><span class="p">][</span><span class="nx">entity</span><span class="p">].</span><span class="nx">other</span><span class="p">[</span><span class="nx">permission</span><span class="p">]</span> <span class="o">===</span> <span class="kc">true</span> <span class="p">:</span> <span class="p">(</span><span class="o">!</span><span class="nx">ownResources</span> <span class="p">?</span> <span class="nx">root</span><span class="p">.</span><span class="nx">permissions</span><span class="p">[</span><span class="nx">root</span><span class="p">.</span><span class="nx">users</span><span class="p">[</span><span class="nx">auth</span><span class="p">.</span><span class="nx">uid</span><span class="p">].</span><span class="nx">role</span><span class="p">][</span><span class="nx">entity</span><span class="p">].</span><span class="nx">own</span><span class="p">[</span><span class="nx">permission</span><span class="p">]</span> <span class="o">===</span> <span class="kc">true</span> <span class="p">:</span> <span class="kc">false</span><span class="p">)</span> <span class="p">)</span> <span class="p">}</span> <span class="cm">/* Nobody can access anything by default */</span> <span class="nx">path</span> <span class="o">/</span> <span class="p">{</span> <span class="nx">read</span><span class="p">()</span> <span class="p">{</span> <span class="kc">false</span> <span class="p">}</span> <span class="nx">write</span><span class="p">()</span> <span class="p">{</span> <span class="kc">false</span> <span class="p">}</span> <span class="p">}</span> <span class="cm">/* Example of permission check: Projects &amp; Project */</span> <span class="nx">type</span> <span class="nx">Projects</span> <span class="p">{</span> <span class="nx">create</span><span class="p">()</span> <span class="p">{</span> <span class="nx">isAllowed</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s1">'projects'</span><span class="p">,</span> <span class="s1">'c'</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="nx">type</span> <span class="nx">Project</span> <span class="p">{</span> <span class="nx">read</span><span class="p">()</span> <span class="p">{</span> <span class="nx">isAllowed</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s1">'project'</span><span class="p">,</span> <span class="s1">'r'</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span> <span class="p">}</span> <span class="nx">update</span><span class="p">()</span> <span class="p">{</span> <span class="nx">isAllowed</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s1">'project'</span><span class="p">,</span> <span class="s1">'u'</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span> <span class="p">}</span> <span class="k">delete</span><span class="p">()</span> <span class="p">{</span> <span class="nx">isAllowed</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s1">'project'</span><span class="p">,</span> <span class="s1">'d'</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="nx">path</span> <span class="o">/</span><span class="nx">projects</span> <span class="nx">is</span> <span class="nx">Projects</span> <span class="p">{}</span> <span class="nx">path</span> <span class="o">/</span><span class="nx">projects</span><span class="o">/</span><span class="p">{</span><span class="nx">$pid</span><span class="p">}</span> <span class="nx">is</span> <span class="nx">Project</span> <span class="p">{}</span> <span class="nx">path</span> <span class="o">/</span><span class="nx">users</span><span class="o">/</span><span class="p">{</span><span class="nx">$uid</span><span class="p">}</span><span class="sr">/role { /</span><span class="o">*</span> <span class="nx">Only</span> <span class="nx">admins</span> <span class="nx">should</span> <span class="nx">be</span> <span class="nx">able</span> <span class="nx">to</span> <span class="nx">write</span> <span class="nx">the</span> <span class="nx">role</span><span class="p">.</span> <span class="o">*</span><span class="sr">/</span><span class="err"> </span> <span class="nx">read</span><span class="p">()</span> <span class="p">{</span> <span class="nx">auth</span><span class="p">.</span><span class="nx">uid</span> <span class="o">==</span> <span class="nx">$uid</span> <span class="p">}</span> <span class="cm">/* Users can read their own role */</span> <span class="nx">write</span><span class="p">()</span> <span class="p">{</span> <span class="nx">isAllowed</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s1">'user'</span><span class="p">,</span> <span class="s1">'changeRole'</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="nx">path</span> <span class="o">/</span><span class="nx">projects</span><span class="o">/</span><span class="p">{</span><span class="nx">$uid</span><span class="p">}</span><span class="sr">/owner { /</span><span class="o">*</span> <span class="nx">Users</span> <span class="nx">can</span> <span class="nx">only</span> <span class="nx">write</span> <span class="nx">to</span> <span class="nx">their</span> <span class="nx">own</span> <span class="s1">'owner'</span> <span class="nx">fields</span><span class="p">.</span> <span class="o">*</span><span class="sr">/</span><span class="err"> </span> <span class="nx">write</span><span class="p">()</span> <span class="p">{</span> <span class="nx">auth</span><span class="p">.</span><span class="nx">uid</span> <span class="o">==</span> <span class="nx">$uid</span> <span class="p">}</span> <span class="p">}</span> <span class="cm">/* * ... * Other entities omitted; they would look similar. * Each entity with an owner will have a `/owner` rule. * That prohibits anybody from changing it, like we have for `/projects/{$uid}/owner` */</span><span class="sr">/</span><span class="err"> </span></code></pre> </div> <p>That’s about it. It compiles via Bolt, so it should work… maybe with some tweaks here &amp; there. With proper unit tests, this baby’s gonna be able to handle whatever you throw at it.</p> <p>To give one more example, here’s how the admin role would look:</p> <div class="language-javascript highlighter-rouge"><pre class="highlight"><code><span class="nx">Permissions</span><span class="err">:</span> <span class="p">{</span> <span class="nl">admin</span><span class="p">:</span> <span class="p">{</span> <span class="c1">// Admin role: allow everything</span> <span class="nl">users</span><span class="p">:</span> <span class="p">{</span> <span class="nl">all</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="nx">projects</span><span class="err">:</span> <span class="p">{</span> <span class="nl">all</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="nx">project</span><span class="err">:</span> <span class="p">{</span> <span class="nl">all</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="nx">tasks</span><span class="err">:</span> <span class="p">{</span> <span class="nl">all</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="nx">task</span><span class="err">:</span> <span class="p">{</span> <span class="nl">all</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="nx">files</span><span class="err">:</span> <span class="p">{</span> <span class="nl">all</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="nx">file</span><span class="err">:</span> <span class="p">{</span> <span class="nl">all</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="nx">messages</span><span class="err">:</span> <span class="p">{</span> <span class="nl">all</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="nx">message</span><span class="err">:</span> <span class="p">{</span> <span class="nl">all</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}</span> <span class="p">}</span> <span class="cm">/* ... */</span> <span class="p">}</span> </code></pre> </div> <h2 id="front-end-implementation">Front-end implementation</h2> <p>Firebase doesn’t require a back-end, but you can use one if you want to (or absolutely need it). For this project, I chose not to use a back-end. That means that I’ll handle the UI rendering 100% in the front-end. In turn, that will require that I know what a certain user is allowed to do upon authentication. This way I can show / hide relevant parts of the UI (i.e. a delete button).</p> <p>No matter if you are spinning up a React, Angular, etc. app or even cowboying your way through with a Vanilla JS app, the implementation shouldn’t differ much. <br /> One way to do it is load permissions when a resource is accessed (in the context of the current auth. user). Similarly to the Bolt implementation, a generic <code class="highlighter-rouge">isAllowed()</code> method should check if certain actions can be performed. Another way is to make use of the real-time database and keep an eye out for permission changes. Of course, that means all permissions will be loaded upon login.</p> <h2 id="caveats">Caveats</h2> <p><small>(yes, I used this word because I don’t like pitfalls. do you?!)</small></p> <ol> <li>Don’t take unit testing lightly, you need to do it properly (Bolt == experimental)</li> <li>It’s a bit hard to see the full picture; there might still be some security issues, but hey, it’s a complex app with many user roles (see 1.).</li> <li>Not being able to restrict registration makes me feel a bit uneasy, so perhaps a back-end would be necessary somewhere down the road.</li> </ol> <p>I want to believe that this strategy will work all the way &amp; I won’t need any server logic. Is that maybe too ambitious?<br /> Would love to get some feedback from experienced Firebasing guru-ninjas.</p> We've gone static (again) 2016-08-09T09:22:30+02:00 https://fadeit.dk/blog /2016/08/09/from-JSON-to-markdown <p>It’s a big day today: we’ve gone Jekyll - the state of the union static site generator.</p> <p>Yes folks, the fadeit blog has been dismembered. It is not more but a puny appendage, isolated from the rest of the fadeit universe. Call it the great fadeit schism if you will. But why did we do it?<br /> More on that in a second.</p> <p>Let me tell you something about JSON first. It was created for humans, you know, like you and me. When specified by <a href="https://en.wikipedia.org/wiki/Douglas_Crockford">Mr. Douglas Crockford</a>, it was supposed to be:</p> <ul> <li>easy to read &amp; write</li> <li>easy for machines to parse &amp; generate</li> </ul> <p>All the more so we thought we can write blog posts in it &amp; get on with our lives. Oh how naive we were!<br /> Easy to read &amp; write? - not if you write a complicated blog post with tables, diagrams and code blocks.</p> <p>It wasn’t obvious to us from the start though, so this is why we’ll share this little story with you. Fortunately, you won’t have to go through the same struggle as we did. Sit back, relax and take a deep breath. You’re safe now.</p> <h2 id="our-little-static-site-that-could">Our little static site that could</h2> <p>For various reasons, we had (and still have) an Angular site running on <a href="https://fadeit.dk">fadeit.dk</a>. Without getting into details, our decision was to write a small Angular blog module that we’d feed JSON to. We all do our fair share of console cowboying, so we weren’t afraid of JSON. Doing so we got many advantages out of the box:</p> <ul> <li>design / UX consistency with the rest of the site</li> <li>complete control over rendering / plugins (i.e. code highlighting)</li> <li>decent performance</li> <li>some SEO</li> <li>a JSON linter!</li> <li>i18n (which we didn’t really use)</li> </ul> <blockquote> <p>Most of all, we saved time. Design aside, the actual implementation took a couple of days.</p> </blockquote> <p>We were happy, so we wrote a few blog posts. After a while, the writing frequency went down. Then, we started to forget how the JSON should be structured. We had to refresh our memory with each blogpost. When we did want to go all in &amp; create some crazy custom layout we’d add piles of code that we’d forget how to use later. Let’s face it, they weren’t properly documented either. Why would they be? We just wanted to write a blog post, not document how elements are rendered.</p> <p>To avoid going full mental jacket here, let’s just say it became a burden to write each blog post. None of us were motivated enough to start on a new post, a mental block was in place and we slowly stopped writing anything.</p> <blockquote> <p>It wasn’t strict enough in the right places, but too strict in the wrong places. We couldn’t just write a blog post anymore.</p> </blockquote> <h2 id="why-use-a-static-site-generator-anyway">Why use a static site generator anyway?</h2> <p>For us here’s what made it attractive:</p> <ul> <li>progressive enhancement: no need for JavaScript</li> <li>better performance: easier to handle traffic spikes, no db queries, server-side rendering &amp; the list goes on</li> <li>super easy to host &amp; run, inexpensive too (you can even do Github pages!)</li> <li>version control for content</li> <li>it’s safer, too!</li> </ul> <p>We were happy users of a static site generator (kinda) in the first place, but that was mostly client-side. While we did get an UX bonus, it was rather slow on mobile devices. That’s partly because it required downloading large scripts at first load. We weren’t taking advantage of server-side rendering. <br /> It still made a lot of sense to switch to a proper static site generator - and so we did.</p> <h2 id="why-jekyll">Why Jekyll</h2> <p>There are plenty site generators to choose from (more on that later), but Jekyll caught our eye because of:</p> <ul> <li>fewer scripts, no Angular, fast first load</li> <li>markdown! Easy to write, easy to migrate</li> <li>community, maturity &amp; (plugins)[https://jekyllrb.com/docs/plugins/]</li> <li>easy to get started</li> <li><a href="https://www.ampproject.org/">AMP</a> support</li> </ul> <p>Granted, the first 2 items in the list are true for many static site generators. However, the size and dedication of the community just blew our mind.</p> <h2 id="to-wrap-it-up">To wrap it up</h2> <p>Don’t take our word for it, give it a try for your next project. We’re happy now, but we were also happy with our Angular-JSON static site. We’re also running Wordpress, Joomla, Squarespace and even Medium blogs. There are many, many others you can try too. How many? About <a href="https://staticsitegenerators.net/">437 of them</a> to be precise (at the time of this post).</p> <p>There’s no silver bullet, but Jekyll is a damn good solution for techies to produce content.</p>