<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet href="/rss-styles.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>hdoro.dev</title>
        <link>https://hdoro.dev/</link>
        <description>Cultivating my thoughts on web development, content modelling, happiness, productivity, mindfulness and more</description>
        <lastBuildDate>Wed, 29 Mar 2023 13:52:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>All rights reserved 2026, henrique doro</copyright>
        <item>
            <title><![CDATA[I’m letting go of the “green web” thing]]></title>
            <link>https://hdoro.dev/letting-go-green-web</link>
            <guid>https://hdoro.dev/letting-go-green-web</guid>
            <pubDate>Tue, 28 Mar 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Why I think obsessing with making web services "sustainable" is a trap]]></description>
            <content:encoded><![CDATA[<div class=""><p>For a while I was interested in the idea of &quot;sustainable websites&quot; - in 2020-21 <a href="http://web.archive.org/web/20210730072334/https://hdoro.dev/" target="_blank" rel="noopener">I even built my website around it</a> and was betting on that as a my career going forward! I felt relieved to be able to &quot;do something for the climate&quot; inside my work as a web developer.</p><p>That’s no longer the case.</p><h2 id="heading-35c5350a76cd"><a href="#heading-35c5350a76cd" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Building a green site! (w<em>hat’s the impact?</em>)</h2><p>In January 2021, I built the website for a startup building a carbon footprint tracker for corporations. They reached me because they were interested in my approach to a low-carbon web - <a href="https://www.youtube.com/watch?v=zzE1vXIy7Xs" target="_blank" rel="noopener">after doing a talk on the topic</a>, this was the perfect opportunity to turn theory into practice, and I jumped eagerly at it.</p><p>I obsessed over optimizing every nook and cranny of a tiny static site for a starting company with unvalidated product, making tech choices that became obsolete and hard to maintain the <em>second</em> I shipped. I’ve wasted time &amp; resources of a company in dire need of them: what could’ve been a 2-day project on Webflow, became 3 weeks of intense programming, with thousands of lines of code and a weird headless CMS to manage it.</p><p>Needless to say, I felt void of meaning when I shipped.</p><p><strong>But I still needed to do something for the climate, right?</strong> Imagine on scale, this would matter! “Do you know how many people download the awfully bloated Google.com <em>per second</em>?!”</p><p>So I attended online conferences, read articles and wrote countless notes, with the promise of eventually publishing a lot, running a newsletter, lecturing a course, etc.</p><p>But the more I did it, the less I could justify what I was proposing. The most common question was along the lines of “what’s the impact of this in my individual web project?”, and I simply couldn’t feel good about any <em>honest</em> answer.</p><p>Sure, the inflated MBs shipped on Amazon.com do mean some good tonnes of CO2 emitted per year, <strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong>but it’s insignificant close to the destruction done by Amazon-led consumerism.</strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></p><p>With this unsettling truth circumdating as almost-conscious feelings in myself, I kept postponing “the dream of being a green builder”, until I started drawing more attention and money in other markets. And so ended my tech sustainability journey, of which the only rubble is my still-green website (which is still beautiful, thanks <a href="https://marifulness.com/" target="_blank" rel="noopener">Mari</a>!)</p><h2 id="heading-3d986b28697c"><a href="#heading-3d986b28697c" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Going beyond utilitarism and individualism</h2><p>Since then, I’ve developed my thoughts a bit further on sociopolitical and environmental topics. Most importantly, I’ve developed more closeness to myself, other people and nature, going into the body and emotions, and beyond what the intelect can reach. That gave me <em>a lot</em> of perspective on this failed attempt, not all of which I’d be able to articulate.</p><p>For one, I was seeking the money and vanity of being one of the first on a nascent field. Individualistic, status-driven, self-centric modern society at its finest.</p><p>Also, what kept me from acting in the end was the “lack of value” of the work - an utilitarian notion which keeps us producing always more, giving higher surplus to our capitalist overlords.</p><p>And more recently, I finally understood that <strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong>any commodity that names itself “sustainable”</strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong></strong> (in this case, websites) <strong><strong><strong><strong><strong><strong><strong><strong>is just paving the path for corporate greenwashing and guilt-free consumerism</strong></strong></strong></strong></strong></strong></strong></strong>. Even if the proposers of the so-called sustainability are well-meaning.</p><p>Companies trying to make a buck co-opting this web sustainability discourse have been reaching me out more and more, which was what prompted me to write these thoughts. If this isn’t a glaring indication of the issue above, I don’t know what is.</p><figure><div class=" lazy-img" data-alt="Green hosting and hdoro.dev  Hello,  My name is (REDACTED) and I&#x27;m a long-time volunteer for environmental causes.  Lately, we&#x27;ve managed to publish a post talking on the benefits of hosting websites on green hosting (...)  (REDACTED)  I wanted to ask if you can share this post with your users and help protect the environment.  There are several well-respected companies there for any environmental enthusiastic to choose from.  I assume you can link to it from here: hdoro.dev/sustainability or from wherever you see fit.  I greatly appreciate your assistance," data-src="https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.5047080979284368"><div style="width:100%;padding-bottom:66.45807259073842%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/efc0a78e52ef8f6cc33610fb89c9bad9e70e9f52-1598x1062.png?w=1300&amp;fit=max&amp;auto=format" alt="Green hosting and hdoro.dev  Hello,  My name is (REDACTED) and I&#x27;m a long-time volunteer for environmental causes.  Lately, we&#x27;ve managed to publish a post talking on the benefits of hosting websites on green hosting (...)  (REDACTED)  I wanted to ask if you can share this post with your users and help protect the environment.  There are several well-respected companies there for any environmental enthusiastic to choose from.  I assume you can link to it from here: hdoro.dev/sustainability or from wherever you see fit.  I greatly appreciate your assistance," sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">An e-mail I got the other day from a &quot;long-time volunteer for environmental causes&quot; asking for free promotion on her business</figcaption></figure><p></p><p>On October 2020, I closed my talk on an “eco-friendly web framework” saying <em>we don’t need more tools, we need education</em>.</p><div class=" lazy-img" data-alt="Screenshot of a slide with the phrase: &quot;We don&#x27;t need more tools. We need education&quot;" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.7787418655097613"><div style="width:100%;padding-bottom:56.21951219512195%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/d1d4882c24d253440b1b827c1f435dc30e558a7e-1640x922.png?w=1300&amp;fit=max&amp;auto=format" alt="Screenshot of a slide with the phrase: &quot;We don&#x27;t need more tools. We need education&quot;" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><p>I still agree with this statement. But I no longer think it’s about educating programmers to build leaner websites and designers to rethink the need &amp; scope of what’s being built.</p><p>I now think we should be educating people to become <strong><strong><strong><strong>anti-capitalist,</strong></strong></strong></strong> and embrace the anti-racist, anti-racialist, feminist, agroecological movements that are required for this change to happen.</p><p><strong>If we want to “save the planet”, we need ecosocialism, not an eco-friendly web.</strong></p><hr/><p><em>How am I becoming an ecosocialist myself, you ask? Still learning, and glad to learn with you, feel free to reach out!</em></p></div>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Learn Typescript by writing Javascript]]></title>
            <link>https://hdoro.dev/learn-typescript-trick</link>
            <guid>https://hdoro.dev/learn-typescript-trick</guid>
            <pubDate>Thu, 20 Oct 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[How I'd approach learning Typescript if I had to start from scratch]]></description>
            <content:encoded><![CDATA[<div class=""><p><strong>TL;DR</strong>: while learning its syntax, instead of trying to write <a href="https://typescriptlang.org/" target="_blank" rel="noopener">Typescript</a>, you can write Javascript and let the compiler and your IDE do the work for you.</p><figure><div class=" lazy-img" data-alt="Screenshot of a code editor showing a `sampleUser` Javascript object being used in a function through `typeof sampleUser`" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:0.8058608058608059"><div style="width:100%;padding-bottom:124.0909090909091%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/fb0ba172a45587f482fc3d0411bb8233de6ba85a-880x1092.png?w=1300&amp;fit=max&amp;auto=format" alt="Screenshot of a code editor showing a `sampleUser` Javascript object being used in a function through `typeof sampleUser`" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Hover over Javascript functions &amp; variables to see their TS definitions</figcaption></figure><p>If you want to type an existing piece function or value, you can use <code>typeof VARIABLE_NAME</code> and Typescript&#x27;s compiler will introspect the type from your Javascript, without you having to manually type it out. </p><p>Besides lowering the barrier to entry, this also speeds things up quite significantly. And you still get TS&#x27;s amazing auto-complete and warnings!</p><figure><div class=" lazy-img" data-alt="Screenshot of a code editor with an autocomplete dialog in the invocation of a `registerUser` function" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.2760180995475112"><div style="width:100%;padding-bottom:78.36879432624114%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/0bd3cc8d13a137f4cc5ba3f01945530e35d32fb2-1128x884.png?w=1300&amp;fit=max&amp;auto=format" alt="Screenshot of a code editor with an autocomplete dialog in the invocation of a `registerUser` function" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">✨</figcaption></figure><p>Perhaps more importantly, if you use TS-friendly IDEs like <a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VS Code</a> you can hover over a variable definition to get the shape of the data and start learning its syntax.</p><div class=" lazy-img" data-alt="Screenshot of a code editor showing Typescript definitions of a `sampleUser` variable on a floating dialog as the user hovers the variable definition" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:0.8111111111111111"><div style="width:100%;padding-bottom:123.28767123287672%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/5546a4e6bd0bcbac5dfafe9eacbb172cbf911188-730x900.png?w=1300&amp;fit=max&amp;auto=format" alt="Screenshot of a code editor showing Typescript definitions of a `sampleUser` variable on a floating dialog as the user hovers the variable definition" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><p>Use these tricks with a basic knowledge of <a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-aliases" target="_blank" rel="noopener">Type Aliases</a>, and you&#x27;ve pretty much got to <strong>80% of Typescript values</strong>.</p><p>Of course, there are unions, assertions, narrowing, type casting, constraints, overloads <em>and-otherso-confusing-my-brain-is-melting</em> features 🫠</p><p>These are mainly for library authors, though.</p><p>I&#x27;ve been using TS for almost 5 years and I still write mostly <code>type</code> definitions (or <code>interface</code>, basically the same but for objects) and <a href="https://www.typescriptlang.org/docs/handbook/enums.html" target="_blank" rel="noopener">enums</a> (fancy key/value objects). Here&#x27;s an adaption of real-world code I wrote recently:</p><div class="code-block" data-explanation="false"><pre class="language-typescript"><span class="token comment">// We often use library-provided types</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> SanityDocument <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@sanity/client'</span>

<span class="token keyword">type</span> <span class="token class-name">TranslationInMeta</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token comment">/**
   * Using TSDoc comments is great, by the way!
   * @link https://tsdoc.org/
   */</span>
  _key<span class="token operator">:</span> <span class="token builtin">string</span>
  _ref<span class="token operator">:</span> <span class="token builtin">string</span>
<span class="token punctuation">}</span>

<span class="token comment">// Sometimes we do fancy merging of types...</span>
<span class="token keyword">type</span> <span class="token class-name">TranslationMetaDoc</span> <span class="token operator">=</span> TranslationInMeta <span class="token operator">&amp;</span> SanityDocument <span class="token punctuation">{</span>
  locales<span class="token operator">:</span> TranslationInMeta<span class="token punctuation">[</span><span class="token punctuation">]</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">useDocumentTranslations</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  currentDocument<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  currentDocument<span class="token operator">?</span><span class="token operator">:</span> SanityDocument
<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// It's okay to use `any`, too!</span>
  <span class="token keyword">const</span> routerContext <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">useContext</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>RouterContext<span class="token punctuation">)</span>

  <span class="token keyword">const</span> <span class="token punctuation">[</span>translationsDocument<span class="token punctuation">,</span> setTranslationsDocument<span class="token punctuation">]</span> <span class="token operator">=</span>
    React<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">useState</span><span class="token generic class-name"><span class="token operator">&lt;</span>TranslationMetaDoc <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span>

  <span class="token comment">// ...</span>
<span class="token punctuation">}</span>
<br /></pre></div><p>Looks mostly like <del>React</del> Javascript, right? That&#x27;s because it is! Just a &quot;flavored&quot; version of it 😉</p><p>Now, if you&#x27;re a library author, you may find yourself writing code like this... (<a href="https://github.com/statelyai/xstate/blob/4dac83587291eeca41ddb595f0f181766e399196/packages/core/src/Machine.ts#L84-L112" target="_blank" rel="noopener">source</a>)</p><div class="code-block" data-explanation="false"><pre class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">createMachine</span><span class="token generic class-name"><span class="token operator">&lt;</span>
  TContext<span class="token punctuation">,</span>
  TEvent <span class="token keyword">extends</span> EventObject <span class="token operator">=</span> AnyEventObject<span class="token punctuation">,</span>
  TTypestate <span class="token keyword">extends</span> Typestate<span class="token operator">&lt;</span>TContext<span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> value<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">;</span> context<span class="token operator">:</span> TContext <span class="token punctuation">}</span><span class="token punctuation">,</span>
  TServiceMap <span class="token keyword">extends</span> ServiceMap <span class="token operator">=</span> ServiceMap<span class="token punctuation">,</span>
  TTypesMeta <span class="token keyword">extends</span> TypegenConstraint <span class="token operator">=</span> TypegenDisabled
<span class="token operator">></span></span></span><span class="token punctuation">(</span>
  config<span class="token operator">:</span> MachineConfig<span class="token operator">&lt;</span>
    TContext<span class="token punctuation">,</span>
    <span class="token builtin">any</span><span class="token punctuation">,</span>
    TEvent<span class="token punctuation">,</span>
    BaseActionObject<span class="token punctuation">,</span>
    TServiceMap<span class="token punctuation">,</span>
    TTypesMeta
  <span class="token operator">></span><span class="token punctuation">,</span>
  options<span class="token operator">?</span><span class="token operator">:</span> InternalMachineOptions<span class="token operator">&lt;</span>
    TContext<span class="token punctuation">,</span>
    TEvent<span class="token punctuation">,</span>
    ResolveTypegenMeta<span class="token operator">&lt;</span>TTypesMeta<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> BaseActionObject<span class="token punctuation">,</span> TServiceMap<span class="token operator">></span>
  <span class="token operator">></span>
<span class="token punctuation">)</span><span class="token operator">:</span> StateMachine<span class="token operator">&lt;</span>
  TContext<span class="token punctuation">,</span>
  <span class="token builtin">any</span><span class="token punctuation">,</span>
  TEvent<span class="token punctuation">,</span>
  TTypestate<span class="token punctuation">,</span>
  BaseActionObject<span class="token punctuation">,</span>
  TServiceMap<span class="token punctuation">,</span>
  ResolveTypegenMeta<span class="token operator">&lt;</span>TTypesMeta<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> BaseActionObject<span class="token punctuation">,</span> TServiceMap<span class="token operator">></span>
<span class="token operator">></span><span class="token punctuation">;</span><br /></pre></div><p>I honestly don&#x27;t even know how this code works - it&#x27;s a function that has no body, and there&#x27;s another declaration of a function with the same name <a href="https://github.com/statelyai/xstate/blob/4dac83587291eeca41ddb595f0f181766e399196/packages/core/src/Machine.ts#L114" target="_blank" rel="noopener">right below it</a>. Too much for my brain to handle 😵‍💫</p><p>So yeah, <strong>take it easy</strong>, start by writing Javascript and adopt TS incrementally :)</p><hr/><p>Credits to Clara Amenyo for sparking this idea in a pairing session during my <a href="https://www.recurse.com/" target="_blank" rel="noopener">Recurse Center</a> batch :)</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Rendering performant Sanity.io images]]></title>
            <link>https://hdoro.dev/performant-sanity-io-images</link>
            <guid>https://hdoro.dev/performant-sanity-io-images</guid>
            <pubDate>Thu, 16 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[How I've been approaching rendering Sanity images in the web for the past years]]></description>
            <content:encoded><![CDATA[<div class=""><p>Non-web developers are surprised when we tell them how complicated rendering images can be. Worrying about the layout is only a part of it, we also need to ensure images are accessible, performant, and responsive. </p><p>Over the years, I&#x27;ve spent hours obsessing over details and often got overwhelmed. Thankfully, Sanity.io&#x27;s powerful CDN and recent CSS &amp; HTML advancements made it much simpler &amp; easier to get it right. Let&#x27;s get to it!</p><h2 id="heading-1bcaf673421e"><a href="#heading-1bcaf673421e" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>The holy grail of web images</h2><p>In short, we want <strong>crisp, fast-loading, accessible images that don&#x27;t waste bandwidth</strong>. Concretely, they need to:</p><ol><li>Scale to their visual placement on the page - no 3000px-wide file in a tiny 50px avatar component</li><li>Scale to the user&#x27;s device pixel density - a 3x retina screen will use 150 actual pixels to render 50 &quot;virtual&quot; pixels (the equivalent of CSS&#x27;s <code>50px</code>)</li><li>Be served in the most effective format - if a simple vector, an SVG will always be smaller &amp; crispier, for example</li><li>If cropped by the layout, they should adapt to their container with proper focus on the most important bits of the picture</li><li>Only load when in view (<a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading" target="_blank" rel="noopener"><em>lazy loading</em></a>)</li><li>Contain clear descriptions in the <code>alt</code> property</li><li>Avoid low contrast and other visibility issues</li></ol><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Going deeper</div><div class=""><p><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images" target="_blank" rel="noopener">MDN&#x27;s responsive images guide</a> is a great read if you want to diver deeper into the points above. It goes beyond the technicalities of <code>srcset</code>s and brushes on art direction, which is often required for specific types of content.</p></div></div><p>With <a href="https://www.sanity.io/docs/presenting-images" target="_blank" rel="noopener">Sanity&#x27;s image CDN</a> we can automate 1, 2 &amp; 3 and give editors the peace of mind of images <em>just working™</em>, so they can focus on creating high-quality content and not on scaling files.</p><p>For cropping (#4), you can enable <a href="https://www.sanity.io/docs/presenting-images#QsZtvbLC" target="_blank" rel="noopener">hotspots &amp; crops in Sanity</a> and have your front-end(s) react automagically to them, ensuring the most important bits of the image are always shown, as defined by creators.</p><p>6 &amp; 7 are out of the front-end&#x27;s control, but you can instruct and nudge your editors to help visually impaired readers. See step 3 below.</p><p>Now, let&#x27;s go through the step-by-step to achieve the holy grail above 🌟</p><h2 id="heading-7f2f70d37cf9"><a href="#heading-7f2f70d37cf9" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Step 1: generating image variations</h2><p>Sanity&#x27;s CDN allows you to generate multiple variations of the same image by simply replacing a few query parameters in their URLs, such as:</p><ul><li><code>?w=200</code> (scale to 200px wide)</li><li><code>?fit=max</code> (scale the image up to its maximum dimensions)</li><li><code>?auto=format</code> (use whatever format is more efficient)</li><li><code>?bg=333</code> (add a #333 gray color as the image&#x27;s background, if transparent)</li><li><code>?q=40</code> (compress the image to 40% of its original quality)</li></ul><p>Refer to the <a href="https://www.sanity.io/docs/image-urls" target="_blank" rel="noopener">image URL documentation</a> for all the available transformations.</p><figure><div class=" lazy-img" data-alt="A browser window showing another image of this website scaled to 200px through query parameters in its URL" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:2.2843450479233227"><div style="width:100%;padding-bottom:43.77622377622377%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626.png?w=1300&amp;fit=max&amp;auto=format" alt="A browser window showing another image of this website scaled to 200px through query parameters in its URL" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Try copying the URL of this image and experimenting with its query parameters 😉</figcaption></figure><p>Knowing this, we can build as many size variations of an image as needed for achieving the holy grail described above ✨</p><p>We&#x27;ll generate a set of image sources to feed into the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset" target="_blank" rel="noopener"><code>img element&#x27;s srcset</code></a>, and specify a <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes" target="_blank" rel="noopener">sizes property</a> with how the image scales according to the window size, and let the browser pick which image to download based on the user&#x27;s device width and pixel density. Here&#x27;s how I generate these variations:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> getImageDimensions <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./getImageDimensions"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> imageUrlBuilder <span class="token keyword">from</span> <span class="token string">"@sanity/image-url"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> imageBuilder <span class="token operator">=</span> <span class="token function">imageUrlBuilder</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">projectId</span><span class="token operator">:</span> <span class="token string">"SANITY_PROJECT_ID"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">dataset</span><span class="token operator">:</span> <span class="token string">"SANITY_DATASET"</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token constant">LARGEST_VIEWPORT</span> <span class="token operator">=</span> <span class="token number">1920</span><span class="token punctuation">;</span> <span class="token comment">// Retina sizes will take care of 4k (2560px) and other huge screens</span>

<span class="token keyword">const</span> <span class="token constant">DEFAULT_MIN_STEP</span> <span class="token operator">=</span> <span class="token number">0.1</span><span class="token punctuation">;</span> <span class="token comment">// 10%</span>
<span class="token keyword">const</span> <span class="token constant">DEFAULT_WIDTH_STEPS</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">400</span><span class="token punctuation">,</span> <span class="token number">600</span><span class="token punctuation">,</span> <span class="token number">850</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">,</span> <span class="token number">1150</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// arbitrary</span>
<span class="token comment">// Based on statcounter's most common screen sizes: https://gs.statcounter.com/screen-resolution-stats</span>
<span class="token keyword">const</span> <span class="token constant">DEFAULT_FULL_WIDTH_STEPS</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">360</span><span class="token punctuation">,</span> <span class="token number">414</span><span class="token punctuation">,</span> <span class="token number">768</span><span class="token punctuation">,</span> <span class="token number">1366</span><span class="token punctuation">,</span> <span class="token number">1536</span><span class="token punctuation">,</span> <span class="token number">1920</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">getImageProps</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token comment">/**
   * The image's reference object.
   * Example: {asset: {_ref: string}, hotspot: {...}, crop: {...} }
   */</span>
  image<span class="token punctuation">,</span>

  <span class="token comment">// Number of the largest width it can assume in the design</span>
  <span class="token comment">// or "100vw" if it occupies the whole width</span>
  <span class="token literal-property property">maxWidth</span><span class="token operator">:</span> userMaxWidth<span class="token punctuation">,</span>

  <span class="token comment">/**
   * The minimal width difference, in PERCENTAGE (decimal), between the image's srcSet variations.
   *
   * -> 0.10 (10%) by default.
   */</span>
  minimumWidthStep <span class="token operator">=</span> <span class="token constant">DEFAULT_MIN_STEP</span><span class="token punctuation">,</span>

  <span class="token comment">// List of width sizes to use in the srcSet (NON-RETINA)</span>
  customWidthSteps<span class="token punctuation">,</span>

  <span class="token comment">// Custom &lt;img> element's `sizes` attribute</span>
  sizes<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>image<span class="token operator">?.</span>asset<span class="token operator">?.</span>_ref<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">const</span> maxWidth <span class="token operator">=</span>
    <span class="token keyword">typeof</span> userMaxWidth <span class="token operator">===</span> <span class="token string">"number"</span> <span class="token operator">?</span> userMaxWidth <span class="token operator">:</span> <span class="token constant">LARGEST_VIEWPORT</span><span class="token punctuation">;</span>

  <span class="token comment">// For all image variations, we'll use an auto format and prevent scaling it over its max dimensions</span>
  <span class="token keyword">const</span> builder <span class="token operator">=</span> imageBuilder<span class="token punctuation">.</span><span class="token function">image</span><span class="token punctuation">(</span>image<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">fit</span><span class="token punctuation">(</span><span class="token string">"max"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">auto</span><span class="token punctuation">(</span><span class="token string">"format"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> imageDimensions <span class="token operator">=</span> <span class="token function">getImageDimensions</span><span class="token punctuation">(</span>image<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// Width sizes the image could assume</span>
  <span class="token keyword">const</span> baseSizes <span class="token operator">=</span> <span class="token punctuation">[</span>
    maxWidth<span class="token punctuation">,</span>
    <span class="token operator">...</span><span class="token punctuation">(</span>customWidthSteps <span class="token operator">||</span>
      <span class="token punctuation">(</span><span class="token keyword">typeof</span> userMaxWidth <span class="token operator">===</span> <span class="token string">"number"</span>
        <span class="token operator">?</span> <span class="token constant">DEFAULT_WIDTH_STEPS</span>
        <span class="token operator">:</span> <span class="token constant">DEFAULT_FULL_WIDTH_STEPS</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> retinaSizes <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>
    <span class="token comment">// De-duplicate sizes with a Set</span>
    <span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
      <span class="token operator">...</span>baseSizes<span class="token punctuation">,</span>
      <span class="token operator">...</span>baseSizes<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">size</span><span class="token punctuation">)</span> <span class="token operator">=></span> size <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token operator">...</span>baseSizes<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">size</span><span class="token punctuation">)</span> <span class="token operator">=></span> size <span class="token operator">*</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">)</span>
  <span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> a <span class="token operator">-</span> b<span class="token punctuation">)</span> <span class="token comment">// Lowest to highest</span>
    <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>
      <span class="token punctuation">(</span><span class="token parameter">size</span><span class="token punctuation">)</span> <span class="token operator">=></span>
        <span class="token comment">// Exclude sizes 10% or more larger than the image itself. Sizes slightly larger</span>
        <span class="token comment">// than the image are included to ensure we always get closest to the highest</span>
        <span class="token comment">// quality for an image. Sanity's CDN won't scale the image above its limits.</span>
        size <span class="token operator">&lt;=</span> imageDimensions<span class="token punctuation">.</span>width <span class="token operator">*</span> <span class="token number">1.1</span> <span class="token operator">&amp;&amp;</span>
        <span class="token comment">// Exclude those larger than maxWidth's retina (x3)</span>
        size <span class="token operator">&lt;=</span> maxWidth <span class="token operator">*</span> <span class="token number">3</span>
    <span class="token punctuation">)</span>

    <span class="token comment">// Exclude those with a value difference to their following size smaller than `minimumWidthStep`</span>
    <span class="token comment">// This ensures we don't have too many srcSet variations, polluting the HTML</span>
    <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">size<span class="token punctuation">,</span> i<span class="token punctuation">,</span> arr</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> nextSize <span class="token operator">=</span> arr<span class="token punctuation">[</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>nextSize<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> nextSize <span class="token operator">/</span> size <span class="token operator">></span> minimumWidthStep <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    <span class="token comment">// Use the original image as the `src` for the &lt;img></span>
    <span class="token literal-property property">src</span><span class="token operator">:</span> builder<span class="token punctuation">.</span><span class="token function">width</span><span class="token punctuation">(</span>maxWidth<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>

    <span class="token comment">// Build a `{URL} {SIZE}w, ...` string for the srcset</span>
    <span class="token literal-property property">srcset</span><span class="token operator">:</span> retinaSizes
      <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">size</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>builder<span class="token punctuation">.</span><span class="token function">width</span><span class="token punctuation">(</span>size<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>size<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">w</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">", "</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token literal-property property">sizes</span><span class="token operator">:</span>
      props<span class="token punctuation">.</span>maxWidth <span class="token operator">===</span> <span class="token string">"100vw"</span>
        <span class="token operator">?</span> <span class="token string">"100vw"</span>
        <span class="token operator">:</span> sizes <span class="token operator">||</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">(max-width: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>maxWidth<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px) 100vw, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>maxWidth<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>

    <span class="token comment">// Let's also tell the browser what's the size of the image so it can calculate aspect ratios</span>
    <span class="token literal-property property">width</span><span class="token operator">:</span> retinaSizes<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token literal-property property">height</span><span class="token operator">:</span> retinaSizes<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">/</span> imageDimensions<span class="token punctuation">.</span>aspectRatio<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><br /></pre></div><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>If the usage of <code>imageBuilder</code> isn&#x27;t clear, refer to <a href="https://www.npmjs.com/package/@sanity/image-url" target="_blank" rel="noopener">@sanity/image-url&#x27;s documentation</a>.</p></div></div><p>And, to <code>getImageDimensions</code>, we can rely on its image assets&#x27; _id (<code>asset._ref</code>):</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">getImageDimensions</span><span class="token punctuation">(</span><span class="token parameter">image</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>image<span class="token operator">?.</span>asset<span class="token operator">?.</span>_ref<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">const</span> dimensions <span class="token operator">=</span> image<span class="token punctuation">.</span>asset<span class="token punctuation">.</span>_ref<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'-'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>width<span class="token punctuation">,</span> height<span class="token punctuation">]</span> <span class="token operator">=</span> dimensions<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'x'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>Number<span class="token punctuation">)</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>width <span class="token operator">||</span> <span class="token operator">!</span>height <span class="token operator">||</span> Number<span class="token punctuation">.</span><span class="token function">isNaN</span><span class="token punctuation">(</span>width<span class="token punctuation">)</span> <span class="token operator">||</span> Number<span class="token punctuation">.</span><span class="token function">isNaN</span><span class="token punctuation">(</span>height<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    width<span class="token punctuation">,</span>
    height<span class="token punctuation">,</span>
    <span class="token literal-property property">aspectRatio</span><span class="token operator">:</span> width <span class="token operator">/</span> height<span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><br /></pre></div><p>Then, we can use <code>getImageProps</code> with any image, according to its maximum size in the layout:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// We call getImageProps with the raw `image` object, not the expanded asset reference.</span>
<span class="token function">getImageProps</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">image</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">_key</span><span class="token operator">:</span> <span class="token string">"389dd32f5e44"</span><span class="token punctuation">,</span>
    <span class="token literal-property property">_type</span><span class="token operator">:</span> <span class="token string">"image"</span><span class="token punctuation">,</span>
    <span class="token literal-property property">alt</span><span class="token operator">:</span> <span class="token string">"A browser window showing another image of this website"</span><span class="token punctuation">,</span>
    <span class="token literal-property property">asset</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">_ref</span><span class="token operator">:</span> <span class="token string">"image-556d99f9e1be990a12c0742c62008fe3147f4bad-1430x626-png"</span><span class="token punctuation">,</span>
      <span class="token literal-property property">_type</span><span class="token operator">:</span> <span class="token string">"reference"</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token literal-property property">crop</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">maxWidth</span><span class="token operator">:</span> <span class="token number">600</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<br /></pre></div><p>Initially, I posted this article with a more minimal approach, but I&#x27;ve since extended the function with what I personally use. I plan on abstracting this into a package and simplifying the code above to better explain the approach. In the meantime, reach out if you have questions: <a href="mailto:meet@hdoro.dev" rel="noopener noreferrer">meet@hdoro.dev</a> or <a href="https://twitter.com/hdorodev" target="_blank" rel="noopener noreferrer">hdorodev</a></p><h2 id="heading-6c02c6c5ac19"><a href="#heading-6c02c6c5ac19" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Step 2: rendering the image</h2><p>With the <code>srcset</code>, <code>src</code> and <code>size</code> values ready, we can start building the image component itself. I&#x27;ll use <a href="https://svelte.dev/" target="_blank" rel="noopener">Svelte</a> as it&#x27;s close to HTML, but you can use these principles with any web framework. Let&#x27;s start with the basics:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token comment">&lt;!-- SanityImage.svelte --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
	<span class="token keyword">import</span> getImageProps <span class="token keyword">from</span> <span class="token string">'../utils/getImageProps'</span><span class="token punctuation">;</span>

	<span class="token keyword">export</span> <span class="token keyword">let</span> image<span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

&lt;img
  alt={image.alt || " "}
  {// Pass src, srcset and sizes to the image element
    ...getImageProps({
      image,
      maxWidth: 600
    })
  }
/>
<br /></pre></div><p>With the above, images will load and the browser will choose the proper size for them. BUT all images are loading as soon as the page loads, and what we really want is to only load them when they show up on-screen - the famous <em>lazy loading</em>.</p><p>In the past, I&#x27;d leverage custom lazy loading code that would hide the image until an <a href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver" target="_blank" rel="noopener">IntersectionObserver</a> fired when its parent showed up in the viewport. Then, I&#x27;d finally add the <code>&lt;img&gt;</code> element to the DOM and the browser would do its magic.</p><p>It worked, but it has a few accessibility and SEO concerns, as the content isn&#x27;t all there for non-visual readers &amp; bots. Thankfully, in 2022 the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading" target="_blank" rel="noopener">loading=&quot;lazy&quot; attribute</a> (for images, not iframes) <a href="https://caniuse.com/loading-lazy-attr" target="_blank" rel="noopener">works on all major browsers</a>. So, with one line our images are lazily loaded ✨</p><div class="code-block" data-explanation="false"><pre class="language-html">&lt;img
  loading="lazy"
  alt={image.alt || " "}
  {// Pass src, srcset, width, height and sizes to the image element
    ...getImageProps({
      image,
      maxWidth: 600
    })
  }
/>
<br /></pre></div><p>The above won&#x27;t be 100% smooth for UX, though, as the browser won&#x27;t know what height the image will assume at a given width. This leads to content jumping around as images load and the browser recalculates their dimensions and the page&#x27;s layout.</p><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>I won&#x27;t get into any other image styles here - go wild with your designs, it&#x27;s a simple <code>img</code> element you can plug anywhere! 🔥</p></div></div><p>In the past, I&#x27;d use the <a href="https://www.bram.us/2017/06/16/aspect-ratios-in-css-are-a-hack/" target="_blank" rel="noopener">padding hack</a> to force a consistent height on the image element (or its parent) and hence prevent content from jumping around. It worked wonderfully but led to extra markup and styles that made it harder to create complex image layouts. Plus, the image component ended up much harder to reason about.</p><p>Again, it&#x27;s 2022 and the browser now calculates the image&#x27;s aspect ratio based on images width and height attributes (<a href="https://caniuse.com/mdn-html_elements_img_aspect_ratio_computed_from_attributes" target="_blank" rel="noopener">supported across all major browsers</a>). As getImageProps is already returning <code>width</code> &amp; <code>height</code>, all we need to do is set &quot;height: auto&quot; to prevent the image from being distorted! <a href="https://www.youtube.com/watch?v=YM3KszYmn58" target="_blank" rel="noopener">More info on this by Jen Simmons</a>.</p><div class="code-block" data-explanation="false"><pre class="language-html">&lt;img
  style="height: auto;"
  loading="lazy"
  alt={image.alt || " "}
  {// Pass src, srcset, width, height and sizes to the image element
    ...getImageProps({
      image,
      maxWidth: 600
    })
  }
/>
<br /></pre></div><p>As an added bonus, <strong>our component is now much less tied to framework-specific features</strong>. It&#x27;s literally a couple of generic JS functions and a single <code>&lt;img /&gt;</code> element with dynamic attributes.</p><p>React version:</p><div class="code-block" data-explanation="false"><pre class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">SanityImage</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> image <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span>
      <span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token string">"auto"</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span> <span class="token comment">// could be a global style</span>
      <span class="token attr-name">loading</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>lazy<span class="token punctuation">"</span></span>
      <span class="token attr-name">alt</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>image<span class="token punctuation">.</span>alt <span class="token operator">||</span> <span class="token string">" "</span><span class="token punctuation">}</span></span>
      <span class="token spread"><span class="token punctuation">{</span><span class="token comment">// Pass src, srcset, width, height and sizes to the image element</span>
        <span class="token operator">...</span><span class="token function">getImageProps</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
          image<span class="token punctuation">,</span>
          <span class="token literal-property property">maxWidth</span><span class="token operator">:</span> <span class="token number">600</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span></span>
    <span class="token punctuation">/></span></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span><br /></pre></div><p>Plain JS version:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">SanityImage</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> image <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> src<span class="token punctuation">,</span> srcset<span class="token punctuation">,</span> sizes<span class="token punctuation">,</span> width<span class="token punctuation">,</span> height <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">getImageProps</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    image<span class="token punctuation">,</span>
    <span class="token literal-property property">maxWidth</span><span class="token operator">:</span> <span class="token number">600</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">
    &lt;img
      style="height: auto"
      loading="lazy"
      alt="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>image<span class="token punctuation">.</span>alt <span class="token operator">||</span> <span class="token string">" "</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"
      src="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>src<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"
      srcset="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>srcset<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"
      sizes="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>sizes<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"
      width="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"
      height="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>height<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"
    />
  </span><span class="token template-punctuation string">`</span></span>
<span class="token punctuation">}</span><br /></pre></div><h3 id="heading-6bdf8daeac6d"><a href="#heading-6bdf8daeac6d" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Above-the-fold, &quot;eager&quot; image loading</h3><p>For images above the fold, such as the one in the &quot;hero&quot; (or header) component, you don&#x27;t want to lazy load them as you want users to see them as soon as the page is loaded. The image component above could easily support this modification with a configurable <code>loading</code> property:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token comment">&lt;!-- SanityImage.svelte --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
	<span class="token keyword">import</span> getImageProps <span class="token keyword">from</span> <span class="token string">'../utils/getImageProps'</span><span class="token punctuation">;</span>

	<span class="token keyword">export</span> <span class="token keyword">let</span> image<span class="token punctuation">;</span>
	<span class="token keyword">export</span> <span class="token keyword">let</span> loading <span class="token operator">=</span> <span class="token string">"lazy"</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

&lt;img
  style="height: auto"
  {loading}
  fetchPriority={loading === "eager" ? "high" : undefined}
  alt={image.alt || " "}
  {// Pass src, srcset and sizes to the image element
    ...getImageProps({
      image,
      maxWidth: 600
    })
  }
/>

<span class="token comment">&lt;!-- Using the above: --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>SanityImage</span> <span class="token attr-name">image</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{...}</span> <span class="token attr-name">loading</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>eager<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /></pre></div><p>Notice we&#x27;re adding <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/fetchPriority" target="_blank" rel="noopener">fetchPriority=&quot;high&quot;</a> for eager images, which tells the browser to give preference to loading them sooner, reducing the hit on the <a href="https://web.dev/lcp/" target="_blank" rel="noopener">Largest Contentful Paint performance benchmark</a>.</p><h3 id="heading-489c5093ceba"><a href="#heading-489c5093ceba" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Bonus: image loading transition/animation</h3><p>For a nicer loading effect, we can hide the image until it&#x27;s fully loaded, and then trigger an opacity transition for a subtle-yet-elegant animation. Here&#x27;s how I achieve that in Svelte:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
  <span class="token comment">// ...</span>
  
  <span class="token keyword">let</span> loaded <span class="token operator">=</span> <span class="token boolean">false</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

&lt;img
  ...
  data-loaded={loaded}
  on:load={() => loaded = true}
/>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
  <span class="token selector">img</span> <span class="token punctuation">{</span>
    <span class="token property">transition</span><span class="token punctuation">:</span> .15s opacity<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">img[data-loaded="false"]</span> <span class="token punctuation">{</span>
    <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><br /></pre></div><p>Once loaded, the browser will call the <code>onload</code> event, and the <code>data-loaded</code> attribute will become true, changing the CSS from <code>opacity: 0</code> to the unset, default value of 1, creating the simple transition. </p><div style="display:none">[@portabletext/react] Unknown block type &quot;video&quot;, specify a component for it in the `components.types` prop</div><h2 id="heading-c4923ee29dd0"><a href="#heading-c4923ee29dd0" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Step 3: guiding editors &amp; enforcing accessibility</h2><p>Finally, a crisp, fast-loading, and frugal image component is no good if its output is not shared across all users. To make it more democratic, guide your editors to supplement images with useful <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/alt" target="_blank" rel="noopener">alternative descriptions</a>. You can even make the <code>alt</code> fields required if your team is open to it!</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// Example image schema in Sanity</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"image"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">"Image / photo"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">description</span><span class="token operator">:</span>
    <span class="token string">"💡 highest quality possible without upscaling the image (up to 2500px). If the image contains text, make sure it's highly readable with a high contrast."</span><span class="token punctuation">,</span>
  <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"image"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">options</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">hotspot</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">fields</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
      <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"alt"</span><span class="token punctuation">,</span>
      <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">"Accessibility label for the image"</span><span class="token punctuation">,</span>
      <span class="token literal-property property">description</span><span class="token operator">:</span>
        <span class="token string">'Help make the site more accessible &amp; SEO-friendly with a short textual description of the image, such as "screenshot of the dashboard app"'</span><span class="token punctuation">,</span>
      <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"string"</span><span class="token punctuation">,</span>
      <span class="token function-variable function">validation</span><span class="token operator">:</span> <span class="token parameter">Rule</span> <span class="token operator">=></span> Rule<span class="token punctuation">.</span><span class="token function">required</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token literal-property property">options</span><span class="token operator">:</span> <span class="token punctuation">{</span>
        <span class="token literal-property property">isHighlighted</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span><br /></pre></div><p>If you want to go the extra (10) mile(s), you could <strong>test an image&#x27;s contrast and how friendly to color-blind people it is</strong>. I&#x27;m not sure if there&#x27;s a clear way to run these tests automatically, but given Facebook has been running checks on text in ads&#x27; images for almost 10 years, I&#x27;m sure we can figure it out. I&#x27;d love to help implement something like this, reach out if you&#x27;re interested: <a href="mailto:meet@hdoro.dev" rel="noopener noreferrer">meet@hdoro.dev</a> or <a href="https://twitter.com/hdorodev" target="_blank" rel="noopener noreferrer">hdorodev</a></p><hr/><p>That&#x27;s it! Get in touch if you have questions or suggestions for how to further improve this image component, it&#x27;ll be a pleasure to connect :)</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Defining element spacing with CSS custom properties]]></title>
            <link>https://hdoro.dev/element-spacing-css-custom-properties</link>
            <guid>https://hdoro.dev/element-spacing-css-custom-properties</guid>
            <pubDate>Wed, 15 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[A simple and effective approach to creating reliable and ambitious page layouts]]></description>
            <content:encoded><![CDATA[<div class=""><p>The traditional web page is a set of sections/blocks/modules stacked on top of each other, with varying margins between elements depending on their type.</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.630669546436285"><div style="width:100%;padding-bottom:61.32450331125828%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/5139c26b271b06192751fc75832b724c18b20341-1510x926.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Simple stacked section example, with 120px between them (example: marifulness.com)</figcaption></figure><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>Many interesting design patterns go beyond this box model and include sections overlapping and interacting with each other. You can apply the principles of this guide for some of these, but they aren&#x27;t my focus here.</p></div></div><p>You could model this in many ways, and I&#x27;ve tried several over the years. In the end, it&#x27;d always become a hot mess that was hard to reason about.</p><p>With <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties" target="_blank" rel="noopener">CSS custom properties</a>, though, you specify that every child in a parent has a set <code>margin-top</code> based on a <code>mg-top</code> variable, and store all design rules in a single, concise CSS file.</p><div class="code-block" data-explanation="false"><pre class="language-css"><span class="token comment">/* Parent sets the margin of every child, except the
first, which should be at the top of the parent */</span>
<span class="token selector">.layout-root > *:not(:first-child)</span> <span class="token punctuation">{</span>
  <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--mg-top<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* 1em as the default, could be 0 */</span>
  
  
  <span class="token comment">/* or, if you want to specify mg-top as a simple number and want to convert it to rems: */</span>
  <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--mg-top<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span> / 16<span class="token punctuation">)</span> * 1rem<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>


<span class="token comment">/* Then, each individual section inside the layout can define their own top margins */</span>
<span class="token selector">.faq</span> <span class="token punctuation">{</span>
  <span class="token property">--mg-top</span><span class="token punctuation">:</span> 3rem<span class="token punctuation">;</span>
  
  <span class="token property">--mg-top</span><span class="token punctuation">:</span> 60<span class="token punctuation">;</span> <span class="token comment">/* or number only if using the calc() route above */</span>
<span class="token punctuation">}</span>
<span class="token selector">.project, .about</span> <span class="token punctuation">{</span>
  <span class="token property">--mg-top</span><span class="token punctuation">:</span> 120<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">.highlighted-cta</span> <span class="token punctuation">{</span>
  <span class="token property">--mg-top</span><span class="token punctuation">:</span> 180<span class="token punctuation">;</span> <span class="token comment">/* need more prominence? higher margin! */</span>
<span class="token punctuation">}</span><br /></pre></div><p>This works flawlessly with media queries:</p><div class="code-block" data-explanation="false"><pre class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 1024px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
  <span class="token selector">.highlighted-cta</span> <span class="token punctuation">{</span>
    <span class="token property">--mg-top</span><span class="token punctuation">:</span> 240<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><br /></pre></div><p>And creates the perfect framework for rule-based design or <strong>Content Responsive Design</strong>:</p><div class="code-block" data-explanation="false"><pre class="language-css"><span class="token selector">.cta</span> <span class="token punctuation">{</span>
  <span class="token property">--mg-top</span><span class="token punctuation">:</span> 60<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">/* CTAs coming after a pricing table should be closer */</span>
<span class="token selector">.pricing-table + .cta</span> <span class="token punctuation">{</span>
  <span class="token property">--mg-top</span><span class="token punctuation">:</span> 20<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">.project</span> <span class="token punctuation">{</span>
  <span class="token property">--mg-top</span><span class="token punctuation">:</span> 120<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">/* No need to separate projects all that much */</span>
<span class="token selector">.project + .project</span> <span class="token punctuation">{</span>
  <span class="token property">--mg-top</span><span class="token punctuation">:</span> 90<span class="token punctuation">;</span>
<span class="token punctuation">}</span><br /></pre></div><p>I recently up with this while building <a href="https://marifulness.com" target="_blank" rel="noopener">Marifulness.com</a>, it&#x27;s another fruit of my exploration of leaning more heavily on CSS&#x27;s capabilities. It&#x27;s one of those things that you miss when going too heavy-handed on <a href="https://tailwindcss.com/" target="_blank" rel="noopener">Tailwind CSS</a>. Honestly, I think I won&#x27;t use Tailwind again, just copy its pattern for useful token-based classes.</p><p>As I was exploring <a href="https://hankchizljaw.com/" target="_blank" rel="noopener">Andy Bell</a>&#x27;s <a href="https://cube.fyi/" target="_blank" rel="noopener">CUBE CSS</a> as an alternative to utility-only frameworks, I came across his approach to &quot;flow&quot; (in <a href="https://www.youtube.com/watch?v=KE8MdPD9yac" target="_blank" rel="noopener">this CSSTricks video</a>), which is essentially the same solution I arrived at. As expected, he was more insightful than me, though, and revealed:</p><ol><li>the need for excluding the first child to ensure safe layouts - <code>*:not(:first-child)</code> (in his case, <code>* + *</code>)</li><li>and the possibility of using this as a general flow approach anywhere in your layouts - even deeply nested paragraphs in a card component, for example</li></ol><p>With 2, the following is possible:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>flow<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token comment">&lt;!-- Project is also a flow container --></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>project flow<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>Project name<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>My project description ...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>...<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
  <span class="token selector">.flow > *:not(:first-child)</span> <span class="token punctuation">{</span>
    <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--mg-top<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.project > p</span> <span class="token punctuation">{</span>
    <span class="token property">--mg-top</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.project > img</span> <span class="token punctuation">{</span>
    <span class="token property">--mg-top</span><span class="token punctuation">:</span> 3em<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><br /></pre></div><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>article-content flow<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span>Full blog post<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>...<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>blockquote</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>blockquote</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span>
    <span class="token comment">&lt;!-- ... --></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
  <span class="token selector">.flow > *:not(:first-child)</span> <span class="token punctuation">{</span>
    <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--mg-top<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.article-content > blockquote</span> <span class="token punctuation">{</span>
    <span class="token property">--mg-top</span><span class="token punctuation">:</span> 2em<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.article-content > img</span> <span class="token punctuation">{</span>
    <span class="token property">--mg-top</span><span class="token punctuation">:</span> 1.5em<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.article-content > h2</span> <span class="token punctuation">{</span>
    <span class="token property">--mg-top</span><span class="token punctuation">:</span> 3em<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token selector">.article-content > ul + p</span> <span class="token punctuation">{</span>
    <span class="token property">--mg-top</span><span class="token punctuation">:</span> 1.5em<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">/* And whatever other spacing rules you may have for rich text */</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><br /></pre></div><hr/><p>If you have suggestions, reach out at <a href="mailto:meet@hdoro.dev" rel="noopener noreferrer">meet@hdoro.dev</a> or <a href="https://twitter.com/hdorodev" target="_blank" rel="noopener noreferrer">hdorodev</a> :)</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Tapi lab notes #1]]></title>
            <link>https://hdoro.dev/tapi-lab-notes-1</link>
            <guid>https://hdoro.dev/tapi-lab-notes-1</guid>
            <pubDate>Thu, 09 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Ramblings and investigations of where I'm in the process of building my content modeling tool]]></description>
            <content:encoded><![CDATA[<div class=""><p>I&#x27;ve recently published the 1st version of <a href="https://www.youtube.com/watch?v=n53mAfzkKMo" target="_blank" rel="noopener">Tapi</a>, a visual &amp; collaborative content modeling tool that generates Sanity.io schemas with one click. As this is my first time building a product, I&#x27;m unsure where to go next and how to reason about the journey forward.</p><p>These &quot;lab notes&quot; will be a collection of loose notes exploring pathways and how I feel about them.</p><h2 id="heading-82e354eed4ff"><a href="#heading-82e354eed4ff" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Tapi&#x27;s original sin</h2><p>I started working on Tapi on January 14th, 2022, when my partner sparked the idea of using Figjam as the medium for creating content models. My first step was to open a new board and start experimenting with ways of modeling using native Figjam&#x27;s tools: post-its, arrows/connectors, stickers, and comments.</p><figure><div class=" lazy-img" data-alt="Diagram of a web development directory&#x27;s content model" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.5935613682092555"><div style="width:100%;padding-bottom:62.75252525252525%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/c13bd1a210d248862638b15aaf005af394032fb0-1584x994.png?w=1300&amp;fit=max&amp;auto=format" alt="Diagram of a web development directory&#x27;s content model" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">The first content model diagram I created</figcaption></figure><p>I recorded the process and voiced my thinking out loud, polishing the approach very quickly. I realized that <strong>a solid workflow for diagramming content models could add the most value to the field</strong>. But, as a developer addicted to building, tinkering &amp; automating, I wanted to create a tool that baked these decisions in and automatically exported models to target content platforms.</p><p>I think this is the source of many of my woes and indecisions. While I don&#x27;t regret building the widget, I <em>know</em> that starting with a no-code, visual template for diagramming models would have given me more clarity in the problem space and more fluidity to test multiple approaches.</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.2634315424610052"><div style="width:100%;padding-bottom:79.14951989026063%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/cc0b7a6baa6f814ab9b794733fac008c054f857b-1458x1154.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">These are the sort of rules I could&#x27;ve iterated on - how to structure models, what information to add at which stages, etc.</figcaption></figure><p>I didn&#x27;t spend enough time on this conflict between tooling vs. workflows. When I learned about <a href="https://www.figma.com/widget-docs/intro/" target="_blank" rel="noopener">Figjam widgets</a>, <strong>the builder in me went wild, and tooling won.</strong> The UX design phase began based on ~2hr of research and experimenting 🙈</p><p>And, well, it&#x27;s easy to see in retrospect that this original sin is seeping into the rest of the work.</p><h2 id="heading-7a52410eadf4"><a href="#heading-7a52410eadf4" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Where I&#x27;m at: confusion</h2><p>Tapi is launched, and it&#x27;s helpful to a handful of people. I&#x27;ve had really heart-warming messages coming my way from enthusiasts of content modeling &amp; infinite canvases, but very little actual usage (that I know of, I don&#x27;t do analytics).</p><p>I have a clear road I could follow to make it more useful and start generating revenue:</p><ol><li>Create a library of pre-made content models that users can add to their projects to build faster</li><li>Start charging per project export - either monetarily or by submitting a model to the library (crowdsourcing of content)</li><li>Get a better sense of the content world<ol><li>Read <a href="https://www.amazon.com/Designing-Connected-Content-Products-Tomorrow/dp/0134763386" target="_blank" rel="noopener">Carrie Hane&#x27;s Designing Connected Content</a></li><li>Watch Marcelo Lewin&#x27;s <a href="https://www.headlesscreator.com/course/content-modeling-weekly" target="_blank" rel="noopener">Content Modeling Weekly screencasts</a></li><li>Dive deep into content strategy medium (Twitter, conferences, what else?)</li><li>Get together and interview multiple content designers</li></ol></li><li>Research how to better communicate the pieces of a model<ol><li>What should a field/attribute be called?</li><li>How to explain &quot;documents&quot; and &quot;blocks&quot;? Are there better names?</li></ol></li><li>Bake in content design best practices &amp; workflows<ol><li>How to bake the transition from <em>high-level, title-cased work</em> (which models are there, how they relate) to <em>low-level definitions</em> (which fields exist, their types &amp; validation, etc.)?</li><li>What editing UI concerns should be defined in Tapi? For example, should it expose field descriptions, or whether or not a plain text field should have multiple lines?</li><li>How deep should be the customization of validation options? Right now it&#x27;s either required/optional, but content people often talk about text&#x27;s length very early in the process, for example. What else should be included?</li></ol></li><li>Polish papercuts and fix UX/UI issues</li><li>Marketing mode!<ol><li>Use Tapi as a learning tool in my <a href="https://hdoro.dev/sanity-mentoring" target="_blank" rel="noopener">Sanity mentoring sessions</a></li><li>Produce content on creating content models with Tapi</li><li>Seek partnerships such as sponsoring Marcelo Lewin&#x27;s weekly screencasts or offering affiliate links/coupons</li><li>Give talks and show up in the public sphere</li><li>Dedicated website with documentation, testimonials, storytelling, etc.</li><li>Paid advertising?</li></ol></li></ol><p></p><p>Of course, this is an untested road. I won&#x27;t know if it works until I actually do it. However, I have a strong hunch that this would lead to success, in my own definition of it - income enough to pay my meager bills and strong connections with interesting people.</p><p>BUT that&#x27;s where the original sin comes in: <strong>I&#x27;m not super stoked about content strategy as a problem space</strong>. I do it when I need it, and I enjoy it - it was a blast discussing the content model for the <a href="https://www.sanity.io/exchange" target="_blank" rel="noopener">Sanity.io Exchange</a>, and that&#x27;s still the satisfaction peak of my career.</p><p>My connection to structured content, however, is intuitive and based on my limited experience, and I don&#x27;t feel a calling to become an intellectual on it. Point #3 above on diving deep into the field gives me all sorts of weird sensations in my stomach and throat.</p><p>Rationality tells me to pursue it:</p><ol><li>&quot;This is a field prepped for growth. Here comes stable income! 🤑&quot; (career stability, &quot;success&quot; for the outside world, ego)</li><li>&quot;Committing myself to this goal would make me a better human &amp; professional&quot;</li><li>&quot;I can contribute a lot to the field, and hence to humanity, by helping establish industry-wide standards&quot;</li><li>&quot;<strong>You&#x27;ll never be successful if you can&#x27;t even establish &amp; grow a tiny tool in a tiny niche like this</strong>&quot;</li></ol><p>But my body &amp; soul ask me to treat this less seriously, to bring more play into Tapi, to focus on developers &amp; designers instead of principles-based content strategists. If I follow my desire, I&#x27;ll build a bunch of features that I personally find <em>&quot;cool&quot;</em> (either as a user or the problem solver building it). And I&#x27;ll throw a bunch of effort down the sink.</p><h2 id="heading-6db7408713e3"><a href="#heading-6db7408713e3" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Conclusion</h2><p>So yeah, debating which features to build, how to approach the market, or any other &quot;hard thinking&quot; isn&#x27;t what&#x27;s at stake for lab note #1. My relationship with this project is.</p><p>Right now, I feel a very strong trichotomy between focusing on developers (speed, 2-way binding to code), content strategists (processes and people), or educators (visual clarity, great onboarding). I&#x27;m not inclined to focus on either but fear that the middle ground won&#x27;t take me anywhere.</p><p><strong>My biggest fear is to have thrown hundreds of hours away just to create a nice demo that makes for a popular tweet but doesn&#x27;t help anyone</strong>.</p><p>I&#x27;ll let this simmer a bit, bringing it up in conversations, meditations &amp; journal writing.</p><p>If you have strong opinions, I&#x27;m all ears (or eyes) at <a href="mailto:meet@hdoro.dev" rel="noopener noreferrer">meet@hdoro.dev</a> or <a href="https://twitter.com/hdorodev" target="_blank" rel="noopener noreferrer">hdorodev</a>. I&#x27;m also open to collaborating 😉 (thanks for reading!)</p></div>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Using Sanity.io data in SvelteKit projects]]></title>
            <link>https://hdoro.dev/sanity-io-to-svelte-kit</link>
            <guid>https://hdoro.dev/sanity-io-to-svelte-kit</guid>
            <pubDate>Fri, 29 Apr 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[How to fetch data from your Sanity content lake and use it to feed Svelte templates]]></description>
            <content:encoded><![CDATA[<div class=""><p>From a high-level, the process is simple:</p><ol><li>Create a Sanity client with your project&#x27;s configuration</li><li>From <a href="https://kit.svelte.dev/docs/routing#endpoints" target="_blank" rel="noopener">SvelteKit&#x27;s endpoints</a>, use this client to fetch data with GROQ</li><li>Render this data with Svelte from the endpoint props passed to pages</li><li>Ensure the data is there before rendering pages</li></ol><p>Let&#x27;s get into the specifics - you can also follow <a href="https://www.youtube.com/watch?v=xp1vT8ES8wQ" target="_blank" rel="noopener"><strong>my video going through this process step-by-step</strong></a>.</p><h2 id="heading-b2e847885ca3"><a href="#heading-b2e847885ca3" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Creating the Sanity client</h2><p>Start by installing <code>@sanity/client</code> in your SvelteKit project:</p><div class="code-block" data-explanation="false"><pre class="language-sh">npm i @sanity/client
# or yarn add</pre></div><p>Then, create a <code>sanityClient.js|ts</code> file exporting a configured client with your project&#x27;s info.</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// sanityClient.js</span>
<span class="token keyword">import</span> sanityClient <span class="token keyword">from</span> <span class="token string">"@sanity/client"</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> client <span class="token operator">=</span> <span class="token function">sanityClient</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">projectId</span><span class="token operator">:</span> <span class="token string">"YOUR_PROJECT_ID"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">dataset</span><span class="token operator">:</span> <span class="token string">"YOUR_PROJECT_DATASET"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">apiVersion</span><span class="token operator">:</span> <span class="token string">"2022-03-24"</span><span class="token punctuation">,</span> <span class="token comment">// choose the API version you want</span>
  <span class="token literal-property property">useCdn</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> client<span class="token punctuation">;</span>
<br /></pre></div><p>Refer to <a href="https://www.sanity.io/docs/js-client" target="_blank" rel="noopener">@sanity/client&#x27;s documentation</a> for the options you configure. Put this file where you can access from routes - most SvelteKit apps have the <code>src/lib</code> folder configured with a <code>$lib</code> alias, which is a great option.</p><p>You can also configure the client with <a href="https://kit.svelte.dev/faq#env-vars" target="_blank" rel="noopener"> SvelteKit&#x27;s environment variables</a> if you don&#x27;t want to have project tokens in the code or need to have different test environments.</p><h2 id="heading-8452a7e13133"><a href="#heading-8452a7e13133" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Fetching Sanity data from endpoints</h2><p>Assuming you have a homepage at <code>src/routes/index.svelte</code> that you want to feed with Sanity content, create a <code>src/routes/index.js|ts</code> file to fetch data for this page - this will be your <a href="https://kit.svelte.dev/docs/routing#endpoints-page-endpoints" target="_blank" rel="noopener">page endpoint</a>.</p><p>For data fetching, we want to use the <code>get</code> function and use our Sanity client to run a GROQ query that picks exactly the desired data:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">import</span> client <span class="token keyword">from</span> <span class="token string">"../sanityClient"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">get</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> genuses <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token comment">/* groq */</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">*[
    _type == "genus"
  ][0..10]{
    _id,
    name,
  }</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      genuses<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<br /></pre></div><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Alternative</div><div class=""><p>The previous approach to fetching data in SvelteKit routes was to use a load function in a <code>&lt;script context=&quot;module&quot;&gt;</code> and access an API endpoint from there (a <a href="https://kit.svelte.dev/docs/routing#endpoints-standalone-endpoints" target="_blank" rel="noopener">&quot;standalone endpoint&quot;</a>).</p><p>Although that works, page endpoints (introduced in <a href="https://github.com/sveltejs/kit/blob/master/packages/kit/CHANGELOG.md#100-next260" target="_blank" rel="noopener">next.260 as shadow endpoints</a>) are simpler, lead to less duplication, and make it easier to handle 404s and 500s - so use that if you can 😊</p></div></div><p>To make sure the above is working, you can add a <code>console.log</code> before returning the endpoint&#x27;s body. If the data is <code>undefined</code> or <code>null</code>, make sure your client configuration is right and test your GROQ query in the <a href="https://www.sanity.io/docs/the-vision-plugin" target="_blank" rel="noopener">Sanity studio&#x27;s Vision plugin</a>.</p><p>You can also have route-dependent data by accessing a route parameters. Here&#x27;s an example for the <code>routes/genuses/[_id].js</code> endpoint:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">import</span> client <span class="token keyword">from</span> <span class="token string">"../../sanityClient"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">get</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token comment">// Access the _id from the params object ✨</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> _id <span class="token punctuation">}</span> <span class="token operator">=</span> event<span class="token punctuation">.</span>params<span class="token punctuation">;</span>
  <span class="token keyword">const</span> genus <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token function">fetch</span><span class="token punctuation">(</span>
    <span class="token comment">/* groq */</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">*[
      _type == "genus" &amp;&amp;
      _id == $_id
    ][0]</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span> _id <span class="token punctuation">}</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>genus<span class="token operator">?.</span>_id<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">404</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      genus<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<br /></pre></div><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Plug</div><div class=""><p>Notice how we use $_id in the query above? Refer to <a href="https://hdoro.dev/learn-groq#heading-320c093448d8" target="_blank" rel="noopener">my GROQ course&#x27;s section on query parameters</a> if you&#x27;re unclear on its meaning 😉</p></div></div><h2 id="heading-7e3c4e55b8ce"><a href="#heading-7e3c4e55b8ce" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Rendering data in pages</h2><p>In the SvelteKit page route that matches the page endpoint above, add <code>export let PROP_NAME</code> for all of the props you return in your get function&#x27;s body. Now you can access your data in your front-end 🎉</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">export</span> <span class="token keyword">let</span> genuses <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span>
  {#each genuses as genus}
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{`/genuses/${genus._id}/`}</span><span class="token punctuation">></span></span>{genus.name}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
  {/each}
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span>
<br /></pre></div><h2 id="heading-ebb265909b56"><a href="#heading-ebb265909b56" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Warning: check data&#x27;s validity before rendering pages</h2><p>In the <code>genuses/[_id].js</code> endpoint example above you&#x27;ll notice I return a <code>{ status: 404 }</code> if there&#x27;s no genus returned from Sanity. This makes sure a request to my-site.com/genuses/a-fake-genus-that-doesnt-exist would return a 404 instead of a blank or broken page.</p><p>When working with dynamic content, it&#x27;s a good practice to <strong>always check if the keys you expect are there and their values are valid</strong>. It&#x27;s often the case that a <em><del>reckless</del></em> component doesn&#x27;t do these checks, accessing something like <code>optionalObject.property</code> and ends up leading its parent page to a 500 as Javascript throws an error.</p><p>And that&#x27;s it! Reach out if you have any questions <a href="mailto:meet@hdoro.dev" rel="noopener noreferrer">meet@hdoro.dev</a> or <a href="https://twitter.com/hdorodev" target="_blank" rel="noopener noreferrer">hdorodev</a></p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Minimal content pagination for Sanity.io data]]></title>
            <link>https://hdoro.dev/minimal-sanity-io-pagination</link>
            <guid>https://hdoro.dev/minimal-sanity-io-pagination</guid>
            <pubDate>Wed, 27 Apr 2022 19:23:19 GMT</pubDate>
            <description><![CDATA[How I approach pagination through powerful & expressive GROQ queries]]></description>
            <content:encoded><![CDATA[<div class=""><p>Here&#x27;s the high-level view of how to do Sanity pagination with GROQ:</p><ol><li>define a filter for the documents you need to fetch</li><li>limit them with <a href="https://www.sanity.io/docs/how-queries-work#16b34b599b2f" target="_blank" rel="noopener">query slices</a></li><li>paginate by changing the number in the slices</li><li>and keep tab of the total page count to properly display the pagination UI in the front-end</li></ol><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>This guide is focused on the <em>data side of pagination</em>. With the concepts here, you can build both a traditional pagination widget with next/prev links, as well as infinite scrolling feeds.</p></div></div><h2 id="heading-084f1a760c37"><a href="#heading-084f1a760c37" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Querying paginated data</h2><p>Now, let&#x27;s tackle this in practice. Let&#x27;s assume you have a feed of content, which you fetch like so:</p><div class="code-block" data-explanation="false"><pre class="language-js"><span class="token operator">*</span><span class="token punctuation">[</span>
  <span class="token comment">// A multi-content feed</span>
  _type <span class="token keyword">in</span> <span class="token punctuation">[</span><span class="token string">"news"</span><span class="token punctuation">,</span> <span class="token string">"thought"</span><span class="token punctuation">,</span> <span class="token string">"poem"</span><span class="token punctuation">,</span> <span class="token string">"event"</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span>
  <span class="token operator">!</span><span class="token punctuation">(</span>_id <span class="token keyword">in</span> <span class="token function">path</span><span class="token punctuation">(</span><span class="token string">"drafts.**"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span>
  <span class="token function">defined</span><span class="token punctuation">(</span>slug<span class="token punctuation">.</span>current<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span>
  status <span class="token operator">==</span> <span class="token string">"published"</span>
<span class="token punctuation">]</span>
<span class="token comment">// Editor-picked &amp; popular come first</span>
<span class="token operator">|</span> <span class="token function">score</span><span class="token punctuation">(</span>editorPick<span class="token punctuation">,</span> popularity<span class="token punctuation">)</span>
<span class="token comment">// Freshest and hottest first</span>
<span class="token operator">|</span> <span class="token function">order</span><span class="token punctuation">(</span>_score desc<span class="token punctuation">,</span> _createdAt desc<span class="token punctuation">)</span><br /></pre></div><p>The first thing you need to do is to extract the query filter into a re-usable fragment so that you can use it consistently across the places we&#x27;ll need it (see the <a href="#heading-cf5cc22d550c" target="_blank" rel="noopener">pagination UI section below</a>).</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">COLLECTION_FRAGMENT</span> <span class="token operator">=</span> <span class="token comment">/* groq */</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">
*[
  _type in ["news", "thought", "poem", "event"] &amp;&amp;
  !(_id in path("drafts.**")) &amp;&amp;
  defined(slug.current) &amp;&amp;
  status == "published"
]
</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br /></pre></div><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>I&#x27;m intentionally using a complicated content model &amp; query to show you this method works with any query complexity. This approach is the same as used in <a href="https://www.sanity.io/exchange" target="_blank" rel="noopener">sanity.io/exchange</a>, with all of its contribution types and moderation mechanisms.</p><p>As long as you have a way to get a specific collection of items, you have a way of paginating them.</p></div></div><p>With this, you can build the most obvious part of your query, the one that gets the actual data:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">const</span> <span class="token constant">ITEMS_PER_PAGE</span> <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> myData <span class="token operator">=</span> <span class="token keyword">await</span> sanityClient<span class="token punctuation">.</span><span class="token function">fetch</span><span class="token punctuation">(</span>
  <span class="token comment">/* groq */</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">{
  "items": </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">COLLECTION_FRAGMENT</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">

    // === ORDERING THE COLLECTION ===
    // Editor-picked &amp; popular come first
    | score(editorPick, popularity)
    // Freshest and hottest first
    | order(_score desc, _createdAt desc)
    
    // === SLICING THE COLLECTION ===
    [($pageIndex * </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">ITEMS_PER_PAGE</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)...($pageIndex + 1) * </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">ITEMS_PER_PAGE</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">]
    {
      ..., // your item data projection here
    }
  }</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
  <span class="token comment">// == Query parameters ==</span>
  <span class="token punctuation">{</span>
    <span class="token comment">// Let's get the first page</span>
    <span class="token literal-property property">pageIndex</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span><br /></pre></div><p>See how above we&#x27;re simply appending the scoring &amp; ordering operators after the filter fragment? It may be hard to grasp at first, but it helps to remember that <a href="https://hdoro.dev/learn-groq#heading-42184f533d7a" target="_blank" rel="noopener">GROQ runs sequentially and we can chain our operations</a>.</p><p>In the snippet above we&#x27;re already slicing up our collection to get only the amount defined by our <code>ITEMS_PER_PAGE</code> variable. Let&#x27;s break that line down:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// === SLICING THE COLLECTION ===</span>
<span class="token keyword">const</span> <span class="token constant">ITEMS_PER_PAGE</span> <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>

<span class="token comment">// Notice the difference between the GROQ variable ($pageIndex)</span>
<span class="token comment">// and the javascript constant (${ITEMS_PER_PAGE})</span>
<span class="token keyword">const</span> slice <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[($pageIndex * </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">ITEMS_PER_PAGE</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)...($pageIndex + 1) * </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">ITEMS_PER_PAGE</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">]</span><span class="token template-punctuation string">`</span></span>

<span class="token comment">// ITEMS_PER_PAGE is fixed/constant, so we could inject it in the query directly</span>
<span class="token keyword">const</span> sliceWithParam <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[($pageIndex * 10)...($pageIndex + 1) * 10]</span><span class="token template-punctuation string">`</span></span>

<span class="token comment">// Now, let's add a parameter to the query</span>
<span class="token keyword">const</span> myData <span class="token operator">=</span> <span class="token keyword">await</span> sanityClient<span class="token punctuation">.</span><span class="token function">fetch</span><span class="token punctuation">(</span>
  <span class="token string">"QUERY_HERE"</span><span class="token punctuation">,</span>
  <span class="token comment">// == Query parameters ==</span>
  <span class="token punctuation">{</span>
    <span class="token literal-property property">pageIndex</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// Injecting the parameter in the query...</span>
<span class="token keyword">const</span> sliceWithValue <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[(0 * 10)...(0 + 1) * 10]</span><span class="token template-punctuation string">`</span></span> <span class="token comment">// or [0...10], AKA the first 10 items</span>
<br /></pre></div><p><em>If the above isn&#x27;t clear, going through <a href="https://hdoro.dev/learn-groq#heading-6107b264530c" target="_blank" rel="noopener">my GROQ course&#x27;s section on slices</a> may help </em>😉</p><p>Now, all you need to do is change your <code>pageIndex</code> parameter and you&#x27;ll get the proper data for each page ✨</p><h2 id="heading-cf5cc22d550c"><a href="#heading-cf5cc22d550c" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Powering the pagination UI</h2><p>In order to build a pagination widget or an infinite scrolling mechanism that users can interact with, you&#x27;ll need to know <strong>how many pages there are available</strong>.</p><p>This is where our re-usable filter fragment comes in handy. We can create a pagination object with the current <code>pageNumber</code> and <code>totalPageCount</code> in our GROQ query:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">const</span> myData <span class="token operator">=</span> <span class="token keyword">await</span> sanityClient<span class="token punctuation">.</span><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">{
  "items": {
    // Our previous item query here
  },
  "pagination": {
    "totalPageCount": count(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">COLLECTION_FRAGMENT</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">._id) / </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">ITEMS_PER_PAGE</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">,
    "pageNumber": $pageIndex + 1,
  }
}</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
  <span class="token comment">// == Query parameters ==</span>
  <span class="token punctuation">{</span>
    <span class="token comment">// Let's get the first page</span>
    <span class="token literal-property property">pageIndex</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span><br /></pre></div><p>Now, you can buid any pagination UI you desire (<em>you&#x27;ll need to round totalPageCount, though</em>)!</p><p>If you&#x27;re designing the UI, do check out <a href="https://www.smashingmagazine.com/2022/03/designing-better-infinite-scroll/" target="_blank" rel="noopener">Smashing Magazine&#x27;s article on designing better infinite scroll</a>, which covers well the distinction and trade-offs between traditional pagination links and endless feeds.</p><h2 id="heading-592e11d957d2"><a href="#heading-592e11d957d2" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Back/front-end example with SvelteKit</h2><p>If you&#x27;re still wondering how to implement the full flow, this SvelteKit implementation may be a good reference. I&#x27;m doing traditional pagination, but infinite scrolling would have a similar approach, except querying from an API endpoint on the client-side.</p><p>First, let&#x27;s create a route under <code>routes/page/[pageNumber].svelte</code>:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
	<span class="token keyword">import</span> PaginationUI <span class="token keyword">from</span> <span class="token string">'$lib/components/PaginationUI.svelte'</span><span class="token punctuation">;</span>
	<span class="token keyword">import</span> PostsGrid <span class="token keyword">from</span> <span class="token string">'$lib/components/PostsGrid.svelte'</span><span class="token punctuation">;</span>
	<span class="token keyword">import</span> type <span class="token punctuation">{</span> PaginationData<span class="token punctuation">,</span> PostCardProps <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$lib/types'</span><span class="token punctuation">;</span>

	<span class="token keyword">export</span> <span class="token keyword">let</span> <span class="token literal-property property">posts</span><span class="token operator">:</span> PostCardProps<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
	<span class="token keyword">export</span> <span class="token keyword">let</span> <span class="token literal-property property">pagination</span><span class="token operator">:</span> PaginationData<span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>page-title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Posts<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>PostsGrid</span> <span class="token attr-name">{posts}</span> <span class="token punctuation">/></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>PaginationUI</span> <span class="token attr-name">{pagination}</span> <span class="token punctuation">/></span></span>
<br /></pre></div><p>To get the posts and pagination data this route depends on, we&#x27;ll create a <a href="https://kit.svelte.dev/docs/routing#endpoints-page-endpoints" target="_blank" rel="noopener">page endpoint</a> at <code>routes/page/[pageNumber].ts</code>:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> client <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$lib/client'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token constant">PAGINATION_FRAGMENT</span><span class="token punctuation">,</span> <span class="token constant">POST_LIST_QUERY</span><span class="token punctuation">,</span> <span class="token constant">SETTINGS_QUERY</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$lib/queries'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">get</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> params <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
	<span class="token keyword">const</span> pageNumber <span class="token operator">=</span> params<span class="token punctuation">.</span>number <span class="token operator">?</span> <span class="token function">Number</span><span class="token punctuation">(</span>params<span class="token punctuation">.</span>number<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">;</span>
	<span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token function">fetch</span><span class="token punctuation">(</span>
		<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">{
		"posts": </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">POST_LIST_QUERY</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">,
		"settings": </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">SETTINGS_QUERY</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">,
		"pagination": </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">PAGINATION_FRAGMENT</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
	}</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
		<span class="token punctuation">{</span>
			<span class="token literal-property property">pageIndex</span><span class="token operator">:</span> pageNumber <span class="token operator">-</span> <span class="token number">1</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">)</span><span class="token punctuation">;</span>
	
	<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>data<span class="token punctuation">.</span>posts<span class="token operator">?.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span>
	  <span class="token keyword">return</span> <span class="token punctuation">{</span>
	    <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">404</span>
	  <span class="token punctuation">}</span>
	<span class="token punctuation">}</span>

	<span class="token keyword">return</span> <span class="token punctuation">{</span>
		<span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token punctuation">{</span>
			<span class="token operator">...</span>data<span class="token punctuation">,</span>
			<span class="token literal-property property">pagination</span><span class="token operator">:</span> <span class="token punctuation">{</span>
				<span class="token operator">...</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>pagination <span class="token operator">||</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token comment">// Assume totalPageCount is at least 1, and round it up</span>
				<span class="token literal-property property">totalPageCount</span><span class="token operator">:</span> Math<span class="token punctuation">.</span><span class="token function">ceil</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>pagination<span class="token operator">?.</span>totalPageCount <span class="token operator">||</span> <span class="token number">1</span><span class="token punctuation">)</span>
			<span class="token punctuation">}</span>
		<span class="token punctuation">}</span>
	<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<br /></pre></div><p>And, if you&#x27;re wondering, here&#x27;s my minimal, unstyled PaginationUI component:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
	<span class="token keyword">import</span> type <span class="token punctuation">{</span> PaginationData <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$lib/types'</span><span class="token punctuation">;</span>
	<span class="token keyword">import</span> <span class="token punctuation">{</span> getPathFromSlug <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$lib/urls'</span><span class="token punctuation">;</span>

	<span class="token keyword">export</span> <span class="token keyword">let</span> <span class="token literal-property property">pagination</span><span class="token operator">:</span> PaginationData<span class="token punctuation">;</span>

	<span class="token literal-property property">$</span><span class="token operator">:</span> getPaginationHref <span class="token operator">=</span> <span class="token punctuation">(</span>pageNumber<span class="token operator">:</span> number<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token parameter">string</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
		<span class="token keyword">if</span> <span class="token punctuation">(</span>pageNumber <span class="token operator">&lt;=</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
			<span class="token keyword">return</span> <span class="token string">'/'</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span>

		<span class="token keyword">if</span> <span class="token punctuation">(</span>pageNumber <span class="token operator">>=</span> pagination<span class="token punctuation">.</span>totalPageCount<span class="token punctuation">)</span> <span class="token punctuation">{</span>
			<span class="token keyword">return</span> <span class="token function">getPathFromSlug</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">page/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>pagination<span class="token punctuation">.</span>totalPageCount<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token punctuation">}</span>
		<span class="token keyword">return</span> <span class="token function">getPathFromSlug</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">page/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>pageNumber<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span>
	&lt;a
		href={getPaginationHref(pagination.pageNumber - 1)}
		disabled={pagination.pageNumber - 1 &lt;= 0}
		rel={pagination.pageNumber === 1 ? 'current' : undefined}
		class="nav-link"
	>
		Previous
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>
		Page {pagination.pageNumber} of {pagination.totalPageCount}
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span>
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span>
		<span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{getPaginationHref(pagination.pageNumber</span> <span class="token attr-name">+</span> <span class="token attr-name">1)}</span>
		<span class="token attr-name">disabled</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{pagination.pageNumber</span> <span class="token attr-name">+</span> <span class="token attr-name">1</span> <span class="token punctuation">></span></span> pagination.totalPageCount}
		rel={pagination.pageNumber >= pagination.totalPageCount ? 'current' : undefined}
		class="nav-link"
	>
		Next
	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br /></pre></div><h3 id="heading-80270465085b"><a href="#heading-80270465085b" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>SvelteKit bonus: parameter matching</h3><p>SvelteKit recently landed support for <a href="https://kit.svelte.dev/docs/routing#advanced-routing-matching" target="_blank" rel="noopener">parameter matching</a> to verify if a given parameter value is valid for a given route or not.</p><p><code>params/pageNumber</code> will ensure page numbers can only be positive integers:</p><div class="code-block" data-explanation="false"><pre class="language-typescript"><span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> ParamMatcher <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@sveltejs/kit'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> match<span class="token operator">:</span> <span class="token function-variable function">ParamMatcher</span> <span class="token operator">=</span> <span class="token punctuation">(</span>param<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
	<span class="token keyword">return</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^\d+$</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>param<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">Number</span><span class="token punctuation">(</span>param<span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<br /></pre></div><p>And with this we can update our route&#x27;s filenames to be <code>[number=pageNumber]</code>, so if <em>page/im-not-a-number</em> or <em>page/-130</em> get requested SvelteKit will immediately return a 404.</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Sanity.io SSR previews without changing your front-end code]]></title>
            <link>https://hdoro.dev/sanity-ssr-previews</link>
            <guid>https://hdoro.dev/sanity-ssr-previews</guid>
            <pubDate>Thu, 21 Apr 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[The simplest approach to previews I've found so far]]></description>
            <content:encoded><![CDATA[<div class=""><details><summary class="text-mono" style="font-size:1.1em;cursor:pointer">Backstory: how I got to this method</summary><div class="Accordion_content__Mtyuk"><p>Back in September 2018, I published a guide on <a href="/gatsby-live-preview-sanity" target="_self" rel="">how to do live previews to react websites with Sanity</a> which relied on listening to data changes from the client-side.</p><p>This works okay, but requires you to change your components and front-end code. Previews should be table-stakes for editorial experience, but when projects are running late they often get de-prioritized. That&#x27;s why I started using the method below.</p></div></details><p>The easiest way to provide web previews for Sanity.io content: fetching the draft version of documents if the requested URL includes a <code>?preview=true</code> query parameter.</p><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Limitation</div><div class=""><p>This only works in server-side rendering (SSR) contexts. If you&#x27;re using static site generators like GatsbyJS, Eleventy or Bridgetown, you&#x27;ll need to approach previews differently. If your site is built with a reactive framework like React, Svelte, or Vue, <a href="/gatsby-live-preview-sanity" target="_self" rel="">my guide on client-side Sanity.io previews</a> may help 😊</p></div></div><p>Here&#x27;s an example with SvelteKit for a blog post route handler:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> sanityClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$lib/client'</span><span class="token punctuation">;</span>

<span class="token comment">// If requests include a ?preview=true query parameter, we're in preview mode</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">isPreview</span><span class="token punctuation">(</span><span class="token parameter">request</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">return</span> <span class="token function">Boolean</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>url<span class="token punctuation">.</span>searchParams<span class="token operator">?.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'preview'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">get</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
	<span class="token keyword">const</span> <span class="token punctuation">{</span> slug <span class="token punctuation">}</span> <span class="token operator">=</span> event<span class="token punctuation">.</span>params<span class="token punctuation">;</span>
	<span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> sanityClient<span class="token punctuation">.</span><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token comment">/* groq */</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">{
    "post": *[
			_type == "post" &amp;&amp;
			slug.current == $slug
      // If not in preview, only show published documents
			</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">isPreview</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">''</span> <span class="token operator">:</span> <span class="token string">'&amp;&amp; !(_id in path("drafts.**"))'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">
		]
		</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>
			<span class="token function">isPreview</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span>
				<span class="token operator">?</span> <span class="token comment">// In preview, get draft if existent - it'll have the freshest _updatedAt</span>
				  <span class="token string-property property">'| order(_updatedAt desc)'</span>
				<span class="token operator">:</span> <span class="token string">''</span>
		<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">	
		[0]{
			..., // your query here
		},
  }</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
		<span class="token punctuation">{</span> slug <span class="token punctuation">}</span>
	<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>data<span class="token punctuation">.</span>post<span class="token operator">?.</span>_id<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">404</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

	<span class="token keyword">return</span> <span class="token punctuation">{</span>
		<span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span>
		<span class="token literal-property property">body</span><span class="token operator">:</span> data<span class="token punctuation">,</span>
	<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span><br /></pre></div><p>The three main parts of the code above:</p><ul><li>We define if we&#x27;re in preview mode by checking the &quot;preview&quot; query parameter and converting it to a boolean</li><li>If we&#x27;re not in preview, exclude draft documents (<code>!(_id in path(&quot;drafts.**&quot;))</code> in the query&#x27;s filter)</li><li>If we are in preview, give preference to drafts<ul><li>We <em>could</em> query only drafts, but if the document was published and didn&#x27;t have a draft, the page would return a 404.</li><li>Instead, we order all documents that match the given <code>slug</code> by their <code>_updatedAt</code> value. As drafts are the freshest, they&#x27;ll always show up first.</li><li>However, if no draft is present, the published version will show up.</li></ul></li></ul><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Lost?</div><div class=""><p>If the GROQ query above is unclear, <a href="/learn-groq" target="_blank" rel="noopener">my tiny course for the language</a> may help you 😊</p></div></div><h2 id="heading-e9009be514d2"><a href="#heading-e9009be514d2" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Accessing previews from the Sanity studio</h2><figure><div class=" lazy-img" data-alt="Screenshot of the Sanity studio: a web app with a form to the left for filling the content, and the website&#x27;s preview to the right" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.7777777777777777"><div style="width:100%;padding-bottom:56.25%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/afb24ff1a497bbc3f1aaaf6288842370a8048b80-1920x1080.png?w=1300&amp;fit=max&amp;auto=format" alt="Screenshot of the Sanity studio: a web app with a form to the left for filling the content, and the website&#x27;s preview to the right" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">An example of the preview UI in the Sanity studio</figcaption></figure><p>To replicate the UI above, you can use Simeon Griggs&#x27; great <a href="https://www.sanity.io/plugins/iframe-pane" target="_blank" rel="noopener">sanity-plugin-iframe-pane</a> and add it as a custom view to previewable documents.</p><p>If you don&#x27;t have a custom structure builder, start by following the <a href="https://www.sanity.io/docs/create-custom-document-views-with-structure-builder" target="_blank" rel="noopener">official docs on custom document views</a>.</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// /deskStructure.js</span>
<span class="token keyword">import</span> <span class="token constant">S</span> <span class="token keyword">from</span> <span class="token string">'@sanity/desk-tool/structure-builder'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> EarthGlobeIcon<span class="token punctuation">,</span> EditIcon <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@sanity/icons'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> Iframe <span class="token keyword">from</span> <span class="token string">'sanity-plugin-iframe-pane'</span><span class="token punctuation">;</span>

<span class="token comment">// Replace with your own approach to getting the URL for each document</span>
<span class="token keyword">function</span> <span class="token function">resolveProductionUrl</span><span class="token punctuation">(</span><span class="token parameter">document</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">return</span> <span class="token function">slugToAbsUrl</span><span class="token punctuation">(</span>
		<span class="token function">getDocumentPath</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
			<span class="token literal-property property">_type</span><span class="token operator">:</span> document<span class="token punctuation">.</span>_type<span class="token punctuation">,</span>
			<span class="token literal-property property">slug</span><span class="token operator">:</span> document<span class="token punctuation">.</span>slug<span class="token operator">?.</span>current
		<span class="token punctuation">}</span><span class="token punctuation">)</span>
	<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// Example where only the homepage, case studies &amp; posts are previewable</span>
<span class="token keyword">const</span> previewableTypes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'home'</span><span class="token punctuation">,</span> <span class="token string">'caseStudy'</span><span class="token punctuation">,</span> <span class="token string">'post'</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token comment">/**
 * Defines views/tabs for each content type.
 */</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">getDefaultDocumentNode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
	<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>previewableTypes<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>props<span class="token punctuation">.</span>schemaType<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token keyword">return</span> <span class="token constant">S</span><span class="token punctuation">.</span><span class="token function">document</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">views</span><span class="token punctuation">(</span><span class="token constant">S</span><span class="token punctuation">.</span>view<span class="token punctuation">.</span><span class="token function">form</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span>

	<span class="token keyword">return</span> <span class="token constant">S</span><span class="token punctuation">.</span><span class="token function">document</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">views</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
		<span class="token constant">S</span><span class="token punctuation">.</span>view<span class="token punctuation">.</span><span class="token function">form</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">icon</span><span class="token punctuation">(</span>EditIcon<span class="token punctuation">)</span><span class="token punctuation">,</span>
		<span class="token constant">S</span><span class="token punctuation">.</span>view
			<span class="token punctuation">.</span><span class="token function">component</span><span class="token punctuation">(</span>Iframe<span class="token punctuation">)</span>
			<span class="token punctuation">.</span><span class="token function">options</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
				<span class="token literal-property property">defaultSize</span><span class="token operator">:</span> <span class="token string">'desktop'</span><span class="token punctuation">,</span>
				<span class="token function-variable function">url</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">doc</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">resolveProductionUrl</span><span class="token punctuation">(</span>doc<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">'?preview=true'</span><span class="token punctuation">,</span>
				<span class="token literal-property property">reload</span><span class="token operator">:</span> <span class="token punctuation">{</span>
					<span class="token literal-property property">button</span><span class="token operator">:</span> <span class="token boolean">true</span>
				<span class="token punctuation">}</span>
			<span class="token punctuation">}</span><span class="token punctuation">)</span>
			<span class="token punctuation">.</span><span class="token function">icon</span><span class="token punctuation">(</span>EarthGlobeIcon<span class="token punctuation">)</span>
			<span class="token punctuation">.</span><span class="token function">title</span><span class="token punctuation">(</span><span class="token string">'Preview'</span><span class="token punctuation">)</span>
	<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<br /></pre></div><p>Want to have <strong>live previews</strong> that change as users edit? In my experience, this is more of a wasteful distraction than a useful feature - I want to focus on writing most of the time and only check how it&#x27;ll visually look every once in a while.</p><p>But, if that&#x27;s your jam, you can pass the document&#x27;s <code>_rev</code> version id as a separate query parameter in the URL to constantly refresh the iFrame as users edit the content.</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token constant">S</span><span class="token punctuation">.</span>view
  <span class="token punctuation">.</span><span class="token function">component</span><span class="token punctuation">(</span>Iframe<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">options</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token literal-property property">defaultSize</span><span class="token operator">:</span> <span class="token string">'desktop'</span><span class="token punctuation">,</span>
    <span class="token function-variable function">url</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">doc</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">resolveProductionUrl</span><span class="token punctuation">(</span>doc<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">?preview=true&amp;_rev=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>doc<span class="token punctuation">.</span>_rev<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
    <span class="token literal-property property">reload</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">button</span><span class="token operator">:</span> <span class="token boolean">true</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">icon</span><span class="token punctuation">(</span>EarthGlobeIcon<span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">title</span><span class="token punctuation">(</span><span class="token string">'Preview'</span><span class="token punctuation">)</span><br /></pre></div><p>---</p><p>And that&#x27;s it! You&#x27;ll need to change your GROQ queries slightly, but compared to changing components and creating dedicated preview routes, this method is much simpler than my previous iteration 😊</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[How I'm writing & maintaining complex regular expressions (RegEx)]]></title>
            <link>https://hdoro.dev/building-maintainable-regex</link>
            <guid>https://hdoro.dev/building-maintainable-regex</guid>
            <pubDate>Mon, 18 Apr 2022 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<div class=""><p>RegEx is <a href="#note-content-bcb9f5caad8b" id="note-link-bcb9f5caad8b">notoriously hard<!-- --> <sup class="text-mono">1</sup></a> to write and maintain after a certain threshold of complexity.</p><p>In building a regular expression to parse Wordpress shortcodes, I went way beyond the simplicity boundary, and had to come up with a few tricks to help me make the code easier to write and simpler to maintain:</p><ol><li>Use <a href="https://www.regular-expressions.info/named.html" target="_blank" rel="noopener"><strong>named capture groups</strong></a></li><li><strong>Split the expression</strong> in smaller sub expressions</li><li>Use extensive, <strong>recipe-like documentation</strong> of how each sub-part works</li></ol><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>How to use these tricks depends on each language&#x27;s RegEx implementation. Not all of them have named capture groups.</p></div></div><p></p><p>Take the raw RegEx below:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// Good luck debugging this 10+ min after your write it</span>
<span class="token keyword">const</span> myMagicalRegex <span class="token operator">=</span>
  <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\[([a-z][\w\d_\*-]{1,})(?:\s{1,}([^\]]{0,})){0,1}\](?:((?:(?!\[\/\1\]).)*)\[\/\1\]){0,1}</span><span class="token regex-delimiter">/</span><span class="token regex-flags">gmu</span></span><span class="token punctuation">;</span><br /></pre></div><p>I&#x27;m convinced that even those who work daily with complex regular expressions will have a hard time reading / parsing this. Here&#x27;s the alternative, implementing the 3 principles above:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">/**
 * Shortcode names are made up of lowercase words/digits/asterisks, separated by hyphens and underscores.
 *
 * Rules:
 *  - no whitespace
 *  - can include words, digits, hyphens, underscores &amp; asterisks
 *  - must start with a lowercased letter
 *
 * Examples: popup, wcf7-contact-form,  vc_columns,  text*
 */</span>
<span class="token keyword">const</span> nameExp <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">(?&lt;name>[a-z][\w\d_\*-]{1,})</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">;</span> <span class="token comment">// Group #1</span>
<span class="token comment">// 👆👆👆 Example of a named capture group</span>

<span class="token comment">/**
 * Attributes come in all shapes and sizes, but as a group they must always be preceded by a whitespace.
 * Right now I'm accepting anything that isn't a closing bracket "]".
 * @TODO: how to handle closing brackets in attributes' values?
 *
 * Examples:
 *  - Strings: title="Not" padding_bottom="0px"
 *  - Numbers: width=100 id=30
 *  - Numbers as strings: width="100" id="30"
 *  - Attribute name IS the value: [textarea mensagem] [text* your-name]
 */</span>
<span class="token keyword">const</span> attributesExp <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">(?:\s{1,}(?&lt;attributes>[^\]]{0,})){0,1}</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">;</span> <span class="token comment">// Group #2</span>

<span class="token comment">/**
 * Include everything inside the shortcode, if any.
 *
 * Breaking it down:
 * 1. we wrap it in a non-capturing group (?:) to check how many times the full match happens.
 *  - (?:ACTUAL_CONTENT_REGEX){0,1}
 *  - Content can occur either 0 or 1 times as not all shortcodes have content.
 * 2. The actual content is wrapped in a named capture group (?&lt;content>)
 *  - The content will be whatever (`.`) isn't the closing shortcode bracket (see 3. below)
 *  - The usage of negative lookahead (?!) is explained here: https://stackoverflow.com/a/8057827
 *  - @TODO: better understand this portion
 * 3. The content *must* finish with the closing shortcode bracket
 *  - we backreference the captured "name" group (group #1 above -> \1)
 *  - as we know the bracket will close like [/shortcode-name], we can test for \[\/\1\]
 */</span>
<span class="token keyword">const</span> contentExp <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">(?:(?&lt;content>(?:(?!\[\/\1\]).)*)\[\/\1\]){0,1}</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">;</span> <span class="token comment">// Group #3</span>

<span class="token comment">// 👇👇👇 The final shortcode is built from 3 manageable sub-expressions</span>
<span class="token keyword">const</span> shortcodeRegExp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RegExp</span><span class="token punctuation">(</span>
  <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">\\[</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>nameExp<span class="token punctuation">.</span>source<span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>attributesExp<span class="token punctuation">.</span>source<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">\\]</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>contentExp<span class="token punctuation">.</span>source<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
  <span class="token string">"gum"</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<br /></pre></div><p>Sure, it&#x27;s extensive and isn&#x27;t perfect. RegEx remains a big challenge for me, but now I feel more capable of tackling harder problems with it (and <a href="/javascript-truncation" target="_self" rel="">this confidence is bearing fruit!</a>).</p><p>I had a couple of bugs with this expression a few days/weeks after the first implementation, and was capable of fixing them with relative ease. At least I know that, years from now, it&#x27;ll be clear where to start debugging, and that&#x27;s a huge win!</p><p><strong>Bonus tip for JS:</strong> when consuming the RegEx matches, use array de-structuring to name matches just like your capture groups:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token string">"..."</span><span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span>
  shortcodeRegExp<span class="token punctuation">,</span>
  <span class="token punctuation">(</span><span class="token parameter">match<span class="token punctuation">,</span> <span class="token operator">...</span>groups</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token comment">// Keeping in mind the capture group order in the expression,</span>
    <span class="token comment">// we know what's each group in the final match 👇👇👇</span>
    <span class="token keyword">const</span> <span class="token punctuation">[</span>name<span class="token punctuation">,</span> rawAttributes<span class="token punctuation">,</span> content<span class="token punctuation">]</span> <span class="token operator">=</span> groups<span class="token punctuation">;</span>

    <span class="token comment">// Then we can use it freely</span>
    <span class="token keyword">const</span> attributes <span class="token operator">=</span> <span class="token function">parseShortcodeAttributes</span><span class="token punctuation">(</span>rawAttributes<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> shortcode <span class="token operator">=</span> <span class="token punctuation">{</span> name<span class="token punctuation">,</span> attributes<span class="token punctuation">,</span> content <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>IsShortcode<span class="token operator">?.</span><span class="token punctuation">(</span>shortcode<span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token keyword">return</span> match<span class="token punctuation">;</span>
    
    <span class="token comment">// ...</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span><br /></pre></div><p>If you have other tricks for writing, understanding and maintaining RegEx, do reach out!</p><details><summary class="text-mono" style="font-size:1.1em;cursor:pointer">Sidenote: are you migrating content off Wordpress?</summary><div class="Accordion_content__Mtyuk"><p>I&#x27;m building a tool to help with that, and would love to get together to speed up your work. Get in touch if you&#x27;re interested <a href="mailto:meet@hdoro.dev" rel="noopener noreferrer">meet@hdoro.dev</a> or <a href="https://twitter.com/hdorodev" target="_blank" rel="noopener noreferrer">hdorodev</a> 😉</p></div></details><p></p><p></p></div><div class=""><hr/><ol><li id="note-content-bcb9f5caad8b"><div class=""><p>I <em>think</em> I&#x27;ve seen a few jokes of that floating around, but right now I can only resort to common sense to drive the point home 😬</p></div> <a href="#note-link-bcb9f5caad8b">👆 Go back up</a></li></ol></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[The most compact and readable string clamping/truncation approach (in JS)]]></title>
            <link>https://hdoro.dev/javascript-truncation</link>
            <guid>https://hdoro.dev/javascript-truncation</guid>
            <pubDate>Tue, 12 Apr 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[At least, the best I've created so far!]]></description>
            <content:encoded><![CDATA[<div class=""><p>There are many ways to limit the size of a given piece of content - a process usually called <strong>truncation or clamping</strong>. <a href="https://css-tricks.com/line-clampin/" target="_blank" rel="noopener">CSS Tricks has a bunch of interesting approaches</a> and there are tons of libraries out there to do it, from <a href="https://www.npmjs.com/package/lodash.truncate" target="_blank" rel="noopener">lodash.truncate</a> to <a href="https://github.com/pablosichert/react-truncate" target="_blank" rel="noopener">React-specific components</a> to <a href="https://github.com/search?q=truncate" target="_blank" rel="noopener">thousands of others</a>.</p><p>I started doing this with the naive <code>str.slice(0, maxLength) + &quot;...&quot;</code>, but that will add ellipsis even to text that is smaller than <code>maxLength</code>.</p><p>Adding a <code>if (str.length &lt; maxLength) return str</code> conditional is a good step, but then you&#x27;ll find some words are truncated in hal...</p><p>Frustrating, right?</p><p>To fix this, I created this crazy complex function that would increase the truncated string until it found a whitespace, at which point it&#x27;d return to ensure only whole words were displayed. It worked across many projects for over a year, but felt kinda yucky.</p><p>Now that <a href="/building-maintainable-regex" target="_self" rel="">I&#x27;m a little more comfortable with RegEx</a> I figured that there must be a better way. The answer came through the power of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search" target="_blank" rel="noopener">String.prototype.search()</a>:</p><div class="code-block" data-explanation="false"><pre class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">truncate</span><span class="token punctuation">(</span>str<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> maxLength<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>str<span class="token punctuation">.</span>length <span class="token operator">&lt;</span> maxLength<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  	<span class="token keyword">return</span> str<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  
  <span class="token comment">// To prevent truncating in the middle of words, let's get</span>
  <span class="token comment">// the position of the first whitespace after the truncation</span>
  <span class="token keyword">const</span> firstWhitespaceAfterTruncation <span class="token operator">=</span> str<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span>maxLength<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">search</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\s</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">)</span> <span class="token operator">+</span> maxLength<span class="token punctuation">;</span>
  
  <span class="token keyword">return</span> str<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> firstWhitespaceAfterTruncation<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">'...'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><br /></pre></div><p>11 lines of code including comments and tons of whitespace! I&#x27;ll definitely drag this around all of my projects - hope it&#x27;s useful to you :)</p><p></p><p><strong>PS:</strong> technically I&#x27;m not respecting the <code>maxLength</code> in this code, but I&#x27;m okay with the small deviation this brings.</p><p>If you must have maxLength as the absolute maximum length of strings, you could use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll" target="_blank" rel="noopener">String.matchAll()</a> to check the index of the last whitespace right before the original target truncation, and use that as the value for <code>str.slice(0, truncationTarget)</code> 😉</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[I finally discovered why Javascript Sets exist!]]></title>
            <link>https://hdoro.dev/why-js-sets-exist</link>
            <guid>https://hdoro.dev/why-js-sets-exist</guid>
            <pubDate>Thu, 24 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[My first practical takeaway from learning Clojure]]></description>
            <content:encoded><![CDATA[<div class=""><p>I&#x27;ve used <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set" target="_blank" rel="noopener">Javascript Sets</a> to de-duplicate items in an array for a few years, but I never understood it beyond that. It felt like one of those obscure language features that would never fully make it into my programs...</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// My only usage of sets so far</span>
<span class="token keyword">const</span> uniqueTags <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span>tags<span class="token punctuation">)</span><span class="token punctuation">)</span><br /></pre></div><p>But reading <a href="https://pragprog.com/titles/roclojure/getting-clojure/" target="_blank" rel="noopener">Russ Olsen&#x27;s Getting Clojure</a> (which is super well written and fun, by the way!), I casually came upon this line:</p><blockquote>Since sets are all about membership, the main thing you can do with them is discover if this or that value is in the set.<br/><em>(Page 32)</em></blockquote><p></p><p>And then it dawned on me: Sets are <em>solely</em> a collection of unique values, so there&#x27;s no better tool for these use cases it specializes in.</p><h2 id="heading-c4b54f3d4772"><a href="#heading-c4b54f3d4772" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Use-case #1: checking if item is in collection</h2><p><code>Set.has</code> is faster than <code>Array.includes</code>:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// With Arrays</span>
<span class="token keyword">const</span> singletonTypes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'home'</span><span class="token punctuation">,</span> <span class="token string">'settings'</span><span class="token punctuation">]</span>
<span class="token keyword">const</span> isSingleton <span class="token operator">=</span> singletonTypes<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>doc<span class="token punctuation">.</span>_type<span class="token punctuation">)</span>

<span class="token comment">// With Sets - faster</span>
<span class="token keyword">const</span> singletonTypes <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'home'</span><span class="token punctuation">,</span> <span class="token string">'settings'</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> isSingleton <span class="token operator">=</span> singletonTypes<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>doc<span class="token punctuation">.</span>_type<span class="token punctuation">)</span><br /></pre></div><p>Of course, with few items this is negligible. But this could be a performance opportunity when there are hundreds, thousands of items in a set that is being checked multiple times per second, such as the render method of hundreds of a React/Svelte components.</p><h2 id="heading-fb543688d404"><a href="#heading-fb543688d404" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Use-case #2: ensuring collection has no duplicates</h2><p>This is way easier with sets:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// With arrays, you manually need to check if item isn't already there</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>singletonTypes<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'settings'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  singletonTypes<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string">'settings'</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token comment">// With Sets</span>
singletonTypes<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'settings'</span><span class="token punctuation">)</span> <span class="token comment">// if already exists, it'll be skipped</span><br /></pre></div><h2 id="heading-a471abebae9a"><a href="#heading-a471abebae9a" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Issues with Sets</h2><ul><li>Set order is according to time of addition and you can&#x27;t overwrite this behavior<ul><li>Arrays are still the go-to option for tightly ordered lists of values</li></ul></li><li>Sets aren&#x27;t immutable like in Clojure and they aren&#x27;t as easy to make immutable like Arrays, so be careful of side-effects in seemingly innocent contexts</li><li>Sets are rare in most real-world JS, so your code may end up more complicated and inaccessible to beginners than needed</li></ul><p>So... yeah, I&#x27;m not convinced I&#x27;ll actually use sets, but now I know how it could be useful. I feel slightly more capable with the language 😊</p><h2 id="heading-74a7c197105f"><a href="#heading-74a7c197105f" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Bonus: sets are a mathematical concept</h2><p>These come from the <a href="https://en.wikipedia.org/wiki/Set_(mathematics)" target="_blank" rel="noopener">mathematical model of sets</a>. That thing we used in school exercises to denote that a given variable was a real number, <code>α ∈ R</code>, is a practical use of sets.</p><p>And reading the Wikipedia article on Sets, I discovered where the term &quot;singleton&quot; came from! It&#x27;s the sole item in a mathematical set. Next time I create a singleton in my content model, I&#x27;ll definitely think of Sets.</p><p>This is my first &quot;real-world&quot; example of how much programming is based on Mathematics, I&#x27;m hooked!</p><p></p><p></p><p>Next up is getting a good grip on <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" target="_blank" rel="noopener">Map</a> - but that&#x27;s a challenge for another time.</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Getting JSON data from Sanity.io with a lightweight script]]></title>
            <link>https://hdoro.dev/getting-json-from-sanity-io</link>
            <guid>https://hdoro.dev/getting-json-from-sanity-io</guid>
            <pubDate>Tue, 22 Mar 2022 17:20:22 GMT</pubDate>
            <description><![CDATA[Or how to feed Hugo, Eleventy, Jekyll, Bridgetown and other SSGs from Sanity]]></description>
            <content:encoded><![CDATA[<div class=""><p>JSON, being such a flexible and widespread format, is commonly used by Static Site Generators (SSGs) to feed data into their HTML templates. This is the case of <a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo</a>, <a href="https://www.11ty.dev/" target="_blank" rel="noopener">Eleventy</a>, <a href="https://jekyllrb.com/" target="_blank" rel="noopener">Jekyll</a>, <a href="https://www.bridgetownrb.com/" target="_blank" rel="noopener">Bridgetown</a> and most others from the <a href="https://jamstack.org/generators/" target="_blank" rel="noopener">330+ generators listed in jamstack.org</a>.</p><p>Thankfully, <strong>Sanity.io is JSON-first</strong>. Your data is stored in it, <a href="https://groq.dev/" target="_blank" rel="noopener">GROQ</a> (the querying language for fetching from your dataset) understands JSON natively, and even <a href="https://www.sanity.io/docs/content-modelling" target="_blank" rel="noopener">your content models are defined through Javascript</a> objects!</p><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>In this article, I won&#x27;t cover centralized GraphQL systems like the one used by Gatsby, or frameworks that fetch data on each route or user request like Remix or NextJS.</p></div></div><p>Here&#x27;s the high level view of how to create JSON files from Sanity data during builds to feed SSGs:</p><ol><li>Run a script before the SSG builds</li><li>This script will connect to Sanity through one of its <a href="https://www.sanity.io/docs/client-libraries" target="_blank" rel="noopener">client libraries</a></li><li>Fetch data from the GROQ queries you define</li><li>Save those to the proper files in disk</li><li><em>Then</em> run the generator&#x27;s process and access the data from your templates</li></ol><p>Keep in mind that 1. could happen in a process native to your generator, such as <a href="https://github.com/Elderjs/elderjs" target="_blank" rel="noopener">ElderJS</a>&#x27;s <code>bootstrap</code> hook and <a href="https://www.11ty.dev/docs/data/" target="_blank" rel="noopener">Eleventy&#x27;s many data fetching approaches</a>.</p><p></p><p>Below is my current go-to script for doing <em>1-4</em> in NodeJS with the <a href="https://www.npmjs.com/package/@sanity/client" target="_blank" rel="noopener">@sanity/client package</a>:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// NodeJS-based GROQ queries that are persisted into the filesystem</span>
<span class="token keyword">import</span> sanityClient <span class="token keyword">from</span> <span class="token string">"@sanity/client"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> fs <span class="token keyword">from</span> <span class="token string">"fs"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> path <span class="token keyword">from</span> <span class="token string">"path"</span><span class="token punctuation">;</span>

<span class="token comment">// @TODO: Update with your project's config</span>
<span class="token keyword">const</span> client <span class="token operator">=</span> <span class="token function">sanityClient</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">projectId</span><span class="token operator">:</span> <span class="token string">"tvkfaenh"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">dataset</span><span class="token operator">:</span> <span class="token string">"production"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">apiVersion</span><span class="token operator">:</span> <span class="token string">"v2022-02-26"</span><span class="token punctuation">,</span>
  <span class="token comment">// As this runs in a static generation context, we can afford not using the CDN to always get the freshest data</span>
  <span class="token literal-property property">useCdn</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// @TODO: write your GROQ queries here</span>
<span class="token keyword">const</span> <span class="token constant">QUERIES</span> <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token punctuation">{</span>
    <span class="token literal-property property">filename</span><span class="token operator">:</span> <span class="token string">"genus.json"</span><span class="token punctuation">,</span>
    <span class="token literal-property property">query</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">*[_type == "genus"]</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    <span class="token literal-property property">filename</span><span class="token operator">:</span> <span class="token string">"homepage.json"</span><span class="token punctuation">,</span>
    <span class="token literal-property property">query</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">{
      "hero": *[_type == "homepage"][0],
      "genus": *[_type == "genus"][0..3],
      "settings": *[_type == "settings"][0]
    }</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> promises <span class="token operator">=</span> <span class="token constant">QUERIES</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> filename<span class="token punctuation">,</span> query <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">resolve<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token comment">// 1. Get the data from Sanity</span>
      <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token function">fetch</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token comment">// 2. Save that as JSON to disk</span>
      fs<span class="token punctuation">.</span><span class="token function">writeFileSync</span><span class="token punctuation">(</span>
        path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">"data"</span><span class="token punctuation">,</span> filename<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span>
      <span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>filename<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> went wrong</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token function">reject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">allSettled</span><span class="token punctuation">(</span>promises<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<br /></pre></div><p>If you aren&#x27;t comfortable with it, <a href="https://hdoro.dev/learn-groq" target="_blank" rel="noopener">my interactive article on GROQ</a> may help! Let me know if you have questions or improvements to the script above <a href="mailto:meet@hdoro.dev" rel="noopener noreferrer">meet@hdoro.dev</a> or <a href="https://twitter.com/hdorodev" target="_blank" rel="noopener noreferrer">hdorodev</a> 😊</p><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>I initially shared this with Larry Swanson in <a href="https://www.youtube.com/watch?v=geCxWvzdwGU" target="_blank" rel="noopener">our session on connecting Sanity to Hugo</a>. Check it out if you want to go into the thought process behind this.</p></div></div><p></p><h2 id="heading-a576aaaadb84"><a href="#heading-a576aaaadb84" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Using the data in SSGs</h2><p>Each generator will have its own approach to loading &amp; using data into it. Some examples:</p><ul><li>Hugo &amp; Eleventy automatically insert data into templates from JSON files in the <code>data</code> directory</li><li>Jekyll, Bridgetown &amp; <a href="https://hexo.io" target="_blank" rel="noopener">Hexo</a> have the <code>_data</code> folder</li></ul><p>More commonly, generators like ElderJS, Astro and full-stack frameworks like Next, Nuxt, SvelteKit &amp; Remix have their bespoke ways of loading data on a per-route or per-request basis. The method I describe above isn&#x27;t recommended for these stacks as you&#x27;d be over-fetching data from loading all content for every page.</p><p>Do reach out if you have questions about specific integrations, it&#x27;d be a pleasure to help! You can <a href="/sanity-mentoring" target="_blank" rel="noopener">book a free mentoring session here</a> 😉</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Building RSS feeds from Sanity.io data]]></title>
            <link>https://hdoro.dev/rss-feeds-from-sanity-io-data</link>
            <guid>https://hdoro.dev/rss-feeds-from-sanity-io-data</guid>
            <pubDate>Tue, 22 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Broad directions & tips on how to fetch data and render the feeds]]></description>
            <content:encoded><![CDATA[<div class=""><p>I recently built a couple of RSS feeds - <a href="https://hdoro.dev/feed.xml" target="_blank" rel="noopener">one for my own site</a> (thanks to Ollie for <a href="https://twitter.com/TheOllieJT/status/1484156708603801606" target="_blank" rel="noopener">the request</a>) and <a href="https://marifulness.com/rss" target="_blank" rel="noopener">another for my partner&#x27;s</a>. This is how I approached it.</p><p></p><h2 id="heading-c2217589945f"><a href="#heading-c2217589945f" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Getting the right data</h2><p>Each feed is going to vary according to their site&#x27;s content structure. Start by identifying which document types you&#x27;ll display and how their data translates into feed items&#x27; schema:</p><div class="code-block" data-explanation="false"><pre class="language-typescript"><span class="token comment">// Data format for items in the "feed" npm package (Typescript)</span>
<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">Item</span> <span class="token punctuation">{</span>
    title<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
    description<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
    id<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
    link<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
    date<span class="token operator">:</span> Date<span class="token punctuation">;</span>
    content<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
    category<span class="token operator">?</span><span class="token operator">:</span> Category<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    guid<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
    image<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> Enclosure<span class="token punctuation">;</span>
    audio<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> Enclosure<span class="token punctuation">;</span>
    video<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> Enclosure<span class="token punctuation">;</span>
    enclosure<span class="token operator">?</span><span class="token operator">:</span> Enclosure<span class="token punctuation">;</span>
    author<span class="token operator">?</span><span class="token operator">:</span> Author<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    contributor<span class="token operator">?</span><span class="token operator">:</span> Author<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    published<span class="token operator">?</span><span class="token operator">:</span> Date<span class="token punctuation">;</span>
    copyright<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
    extensions<span class="token operator">?</span><span class="token operator">:</span> Extension<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><br /></pre></div><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>I&#x27;m using the npm package <a href="https://www.npmjs.com/package/feed" target="_blank" rel="noopener">feed</a> to generate my RSS to avoid having to get into the nitty gritty of XML and web feed specifications. There are plenty of alternatives if you&#x27;re using other languages and runtimes, but I&#x27;ll use its abstractions to explain my process.</p></div></div><p>At the very minimal, you&#x27;ll need to add a <code>title</code>,  <code>date</code>, and <code>link</code> to your entries. <code>description</code> is a nice addition, if possible.</p><p>Data-related tips:</p><ul><li><strong>Important: </strong>links must be absolute, including the HTTP protocol -&gt; <code>&quot;https://hdoro.dev/my-post&quot;</code> instead of <code>&quot;/my-post&quot;</code></li><li><code>id</code> can be the same as <code>link</code></li><li>If you have multiple document types, add a <code>category</code> to differentiate them</li><li>Have editor-defined <code>publishedAt</code> and/or <code>updatedAt</code> fields to allow controlling when readers get updates about each piece</li></ul><p>From the decisions above, build a single GROQ query to get all the data you&#x27;ll need for items and make sure it runs properly (if you aren&#x27;t comfortable with it, <a href="https://hdoro.dev/learn-groq" target="_blank" rel="noopener">my interactive article on GROQ</a> may help!). Here&#x27;s an example from Mari&#x27;s site, which exposes case studies and blog posts in a single feed:</p><div class="code-block" data-explanation="false"><pre class="language-js"><span class="token punctuation">{</span>		
  <span class="token comment">// You could also have separate sub-queries for each document type.</span>
  <span class="token comment">// I have them in a single one for simplicity given they're very similar.</span>
  <span class="token string-property property">"entries"</span><span class="token operator">:</span> <span class="token operator">*</span><span class="token punctuation">[</span>
    _type <span class="token keyword">in</span> <span class="token punctuation">[</span><span class="token string">"post"</span><span class="token punctuation">,</span> <span class="token string">"caseStudy"</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span>
    <span class="token function">defined</span><span class="token punctuation">(</span>slug<span class="token punctuation">.</span>current<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span>
    <span class="token operator">!</span><span class="token punctuation">(</span>_id <span class="token keyword">in</span> <span class="token function">path</span><span class="token punctuation">(</span><span class="token string">"drafts.**"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token punctuation">]</span><span class="token punctuation">{</span>
    _type<span class="token punctuation">,</span>
    <span class="token comment">// Notice how I'm already massaging all the data in GROQ,</span>
    <span class="token comment">// getting the most desirable value for each property.</span>
    <span class="token string-property property">"publishedAt"</span><span class="token operator">:</span> <span class="token function">coalesce</span><span class="token punctuation">(</span>publishedAt<span class="token punctuation">,</span> _createdAt<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string-property property">"updatedAt"</span><span class="token operator">:</span> <span class="token function">coalesce</span><span class="token punctuation">(</span>updatedAt<span class="token punctuation">,</span> _updatedAt<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string-property property">"slug"</span><span class="token operator">:</span> slug<span class="token punctuation">.</span>current<span class="token punctuation">,</span>
    <span class="token string-property property">"image"</span><span class="token operator">:</span> <span class="token function">coalesce</span><span class="token punctuation">(</span>seo<span class="token punctuation">.</span>ogImage<span class="token punctuation">,</span> image<span class="token punctuation">,</span> images<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string-property property">"title"</span><span class="token operator">:</span> <span class="token function">coalesce</span><span class="token punctuation">(</span>seo<span class="token punctuation">.</span>title<span class="token punctuation">,</span> title<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token string-property property">"description"</span><span class="token operator">:</span> <span class="token function">coalesce</span><span class="token punctuation">(</span>seo<span class="token punctuation">.</span>description<span class="token punctuation">,</span> subtitle<span class="token punctuation">)</span><span class="token punctuation">,</span>

    <span class="token comment">// type-specific values</span>
    _type <span class="token operator">==</span> <span class="token string">"post"</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token string-property property">"category"</span><span class="token operator">:</span> <span class="token string">"Articles"</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    _type <span class="token operator">==</span> <span class="token string">"caseStudy"</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token string-property property">"category"</span><span class="token operator">:</span> <span class="token string">"Case Studies"</span><span class="token punctuation">,</span>
      services<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">|</span><span class="token function">order</span><span class="token punctuation">(</span>updatedAt desc<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// finish by ordering them</span>

  <span class="token comment">// Global settings for the feed configuration (see below)</span>
  <span class="token string-property property">"settings"</span><span class="token operator">:</span> <span class="token operator">*</span><span class="token punctuation">[</span>_id <span class="token operator">==</span> <span class="token string">"settings"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">{</span>
    $<span class="token punctuation">{</span><span class="token constant">SETTINGS_FRAGMENT</span><span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>  
<span class="token punctuation">}</span><br /></pre></div><h3 id="heading-c4785799a71b"><a href="#heading-c4785799a71b" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Data for the feed configuration</h3><p>Aside from specific items, you&#x27;ll also need to add general information to your feed, such as its title, description, language, copyright, etc.</p><p>Some of this information will be inherently static (such as <code>favicon</code>, the root <code>link</code>, and <code>copyright</code>), but others should probably come from Sanity itself to allow more control to editors (<code>title</code>, <code>description</code> could be updated, for example).</p><p>For this, I highly recommend creating a global settings document and querying it in your GROQ query (as I&#x27;m doing in the code above).</p><h3 id="heading-827101a15d55"><a href="#heading-827101a15d55" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Decision: full text or not?</h3><p>You could add the full content of each entry to your feed, which many readers prefer as it allows them to do all their reading in one app without having to visit each source website.</p><p>Personally, I&#x27;d rather have people read on my own website for various reasons - design &amp; vibe, branding, owning the medium, opening up for dynamic elements in the middle of written content, etc.</p><p>Whatever you choose, keep in mind that <strong>providing the full content will require you to convert <a href="https://portabletext.org/" target="_blank" rel="noopener">PortableText data</a></strong>, with all of your custom block types, annotations and styles, to plain text or HTML.</p><p>This can be a time-intensive if you use front-end frameworks such as React &amp; Svelte, as you won&#x27;t be able to use your component code without first using the framework&#x27;s rendering API directly to get the HTML. Then we start getting into bundling issues and other complications that make the process sour very quickly.</p><p></p><p>Once you&#x27;re comfortable with the data format you&#x27;re getting, let&#x27;s generate the actual feed!</p><p></p><h2 id="heading-f07554374e05"><a href="#heading-f07554374e05" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Generating the feed</h2><p>As I mentioned above, the npm package <a href="https://www.npmjs.com/package/feed" target="_blank" rel="noopener">feed</a> takes care of generating the XML for me. I&#x27;ve used it both in SvelteKit and in NextJS, and the approach is pretty much the same:</p><ol><li>Create an API endpoint</li><li>Fetch Sanity.io data according to what you require in the step above</li><li>Configure the feed</li><li>Add each entry to it</li><li>Export to XML</li><li>Finish the request with the proper headers</li></ol><p>Here&#x27;s the simplified code for a SvelteKit <code>GET</code> route, properly commented for the most important bits:</p><div class="code-block" data-explanation="false"><pre class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">get</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token comment">// 1. ===== FETCHING DATA =====</span>
  <span class="token comment">// See the query in the section above</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> settings<span class="token punctuation">,</span> entries <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> sanityServerClient<span class="token punctuation">.</span><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token constant">QUERY</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// 2. ===== CONFIGURING FEED =====</span>
  <span class="token keyword">const</span> author <span class="token operator">=</span> <span class="token punctuation">{</span>
    name<span class="token operator">:</span> <span class="token string">"Mari Moraes"</span><span class="token punctuation">,</span>
    email<span class="token operator">:</span> settings<span class="token punctuation">.</span>email<span class="token punctuation">,</span>
    link<span class="token operator">:</span> <span class="token constant">BASE_URL</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> feed <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Feed</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    title<span class="token operator">:</span> <span class="token string">"Marifulness"</span><span class="token punctuation">,</span>
    description<span class="token operator">:</span> settings<span class="token punctuation">.</span>feedDescription<span class="token punctuation">,</span>
    id<span class="token operator">:</span> <span class="token function">slugToAbsUrl</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    link<span class="token operator">:</span> <span class="token function">slugToAbsUrl</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    language<span class="token operator">:</span> <span class="token string">"en"</span><span class="token punctuation">,</span>
    <span class="token comment">// Will get to safe image in a bit!</span>
    image<span class="token operator">:</span> <span class="token function">getSafeImage</span><span class="token punctuation">(</span>settings<span class="token punctuation">.</span>ogImage<span class="token punctuation">)</span><span class="token punctuation">,</span>
    favicon<span class="token operator">:</span> <span class="token constant">BASE_URL</span> <span class="token operator">+</span> <span class="token string">"/favicons/apple-icon-72x72.png"</span><span class="token punctuation">,</span>
    copyright<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">All rights reserved </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getFullYear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, Mari Moraes</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
    updated<span class="token operator">:</span> entries<span class="token operator">?.</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>entries<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token operator">?.</span>updatedAt<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span>
    author<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// 3. ===== ADDING ENTRIES =====</span>
  entries<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span>entry<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token function">slugToAbsUrl</span><span class="token punctuation">(</span><span class="token function">getDocumentPath</span><span class="token punctuation">(</span>entry<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    feed<span class="token punctuation">.</span><span class="token function">addItem</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      title<span class="token operator">:</span> entry<span class="token punctuation">.</span>title<span class="token punctuation">,</span>
      description<span class="token operator">:</span>
        entry<span class="token punctuation">.</span>description <span class="token operator">||</span>
        <span class="token comment">// alternative description for case studies</span>
        <span class="token keyword">new</span> <span class="token punctuation">(</span>Intl <span class="token keyword">as</span> <span class="token builtin">any</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ListFormat</span><span class="token punctuation">(</span><span class="token string">"en"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>entry<span class="token punctuation">.</span>services <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      published<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>entry<span class="token punctuation">.</span>publishedAt<span class="token punctuation">)</span><span class="token punctuation">,</span>
      id<span class="token operator">:</span> url<span class="token punctuation">,</span>
      link<span class="token operator">:</span> url<span class="token punctuation">,</span>
      date<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>entry<span class="token punctuation">.</span>publishedAt<span class="token punctuation">)</span><span class="token punctuation">,</span>
      image<span class="token operator">:</span> <span class="token function">getSafeImage</span><span class="token punctuation">(</span>entry<span class="token punctuation">.</span>image<span class="token punctuation">)</span><span class="token punctuation">,</span>
      author<span class="token operator">:</span> <span class="token punctuation">[</span>author<span class="token punctuation">]</span><span class="token punctuation">,</span>
      category<span class="token operator">:</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
          name<span class="token operator">:</span> entry<span class="token punctuation">.</span>category<span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">// 4. ===== GENERATING THE XML =====</span>
  <span class="token keyword">const</span> xml <span class="token operator">=</span> feed<span class="token punctuation">.</span><span class="token function">rss2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> <span class="token punctuation">{</span>
    status<span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span>
    body<span class="token operator">:</span> xml<span class="token punctuation">,</span>
    headers<span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token string-property property">"Content-Type"</span><span class="token operator">:</span> <span class="token string">"application/xml"</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<br /></pre></div><h3 id="heading-cec15479d0ec"><a href="#heading-cec15479d0ec" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Beware of images</h3><p>For some reason, XML is picky about its URLs and apparently can&#x27;t handle query params (not 100% of this!). As Sanity images depend on query parameters to define their width, height, cropping, etc., we need to sanitize them before adding to the feed. Here&#x27;s how I&#x27;m doing it:</p><div class="code-block" data-explanation="false"><pre class="language-typescript"><span class="token comment">/**
 * XML is picky about its URLs and can't handle query params.
 * As our images require those, we need to encode them first
 */</span>
<span class="token keyword">function</span> <span class="token function">getSafeImage</span><span class="token punctuation">(</span>image<span class="token operator">?</span><span class="token operator">:</span> SanityImageRef<span class="token punctuation">)</span> <span class="token punctuation">{</span>
	<span class="token keyword">try</span> <span class="token punctuation">{</span>
		<span class="token keyword">const</span> imageUrl <span class="token operator">=</span> image
			<span class="token operator">?</span> imageBuilder<span class="token punctuation">.</span><span class="token function">image</span><span class="token punctuation">(</span>image<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">fit</span><span class="token punctuation">(</span><span class="token string">'max'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">auto</span><span class="token punctuation">(</span><span class="token string">'format'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">maxWidth</span><span class="token punctuation">(</span><span class="token number">1200</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">url</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
			<span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span>
		<span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>imageUrl<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'?'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">?</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>imageUrl<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'?'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
		<span class="token keyword">return</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span><br /></pre></div><p>The route code above has a few examples of how I use this <code>getSafeImage</code> function 😉</p><h3 id="heading-2908a08296fd"><a href="#heading-2908a08296fd" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Style your feed</h3><p>By default, RSS feeds are only readable to machines. If you want to make it also readable by humans, you could follow <a href="https://aboutfeeds.com/" target="_blank" rel="noopener">Matt Webb&#x27;s AboutFeeds template</a> and style it. Here&#x27;s how I&#x27;m doing it from <a href="https://hdoro.dev/rss-styles.xsl" target="_blank" rel="noopener">my adapted XSL stylesheet</a>: </p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// Add the stylesheet attribute right below the first line of the XML</span>
<span class="token keyword">const</span> <span class="token constant">FIRST_LINE</span> <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;?xml version="1.0" encoding="utf-8"?></span><span class="token template-punctuation string">`</span></span>
<span class="token keyword">const</span> xml <span class="token operator">=</span> feed
  <span class="token punctuation">.</span><span class="token function">rss2</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span>
    <span class="token constant">FIRST_LINE</span><span class="token punctuation">,</span>
    <span class="token constant">FIRST_LINE</span> <span class="token operator">+</span>
      <span class="token string">'&lt;?xml-stylesheet href="/rss-styles.xsl" type="text/xsl"?>'</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span>
res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Type'</span><span class="token punctuation">,</span> <span class="token string">'application/xml'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>xml<span class="token punctuation">)</span><br /></pre></div><p></p><h2 id="heading-6df11644f77a"><a href="#heading-6df11644f77a" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Publishing your feed</h2><p>Now that you have the proper feed being generated, make sure to add it to your website and other mediums you want to advertise it on. Suggestions:</p><ol><li>Add a link to it from the website&#x27;s footer</li><li>Add a <code>link</code> tag to your feed from the HTML&#x27;s head<ol><li>Example: <code>&lt;link rel=&quot;alternate&quot; type=&quot;application/rss+xml&quot; href=&quot;https://hdoro.dev/feed.xml&quot;&gt;</code></li></ol></li></ol><p></p><p>That&#x27;s it! Hope you find this useful :)</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Cloudflare Workers isn't a drop-in serverless platform (yet?)]]></title>
            <link>https://hdoro.dev/cloudflare-workers-not-drop-in-serverless-hosting</link>
            <guid>https://hdoro.dev/cloudflare-workers-not-drop-in-serverless-hosting</guid>
            <pubDate>Thu, 16 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[Using Next.js, SvelteKit and other modern front-end frameworks on Cloudflare Workers requires a good deal of pacience and creative debugging.]]></description>
            <content:encoded><![CDATA[<div class=""><p>I would love to use <a href="https://workers.cloudflare.com/" target="_blank" rel="noopener">Cloudflare Workers</a> for hosting all of my work - from static sites to highly dynamic <a href="https://kit.svelte.dev/" target="_blank" rel="noopener">SvelteKit</a> applications. They have <a href="https://www.pierbover.com/posts/static-hosting-benchmark-2020/" target="_blank" rel="noopener">the <strong>best performance</strong> of any serverless platform</a> with 0ms cold starts (with <a href="https://blog.cloudflare.com/cloudflare-workers-the-fast-serverless-platform/" target="_blank" rel="noopener">improvements coming often!</a>), are affordable &amp; powerful, and, most importantly, are <strong>aware of their responsibility with the planet</strong> and actively work on improving the sustainability of their products.</p><p>They allow you to <a href="https://blog.cloudflare.com/announcing-green-compute/" target="_blank" rel="noopener">set your worker to only run in servers powered by renewables</a>, are <a href="https://blog.cloudflare.com/green-hosting-with-cloudflare-pages/" target="_blank" rel="noopener">certified by the The Green Web Foundation</a>  and even <a href="https://blog.cloudflare.com/understand-and-reduce-your-carbon-impact-with-cloudflare/" target="_blank" rel="noopener">provide carbon emission reports</a> to educate clients on the impact of their web properties. You can say I&#x27;m a fan.</p><p>BUT, given the architecture of their platform, <strong>adopting Workers isn&#x27;t as straightforward as I&#x27;d love it to be</strong>. The Javascript code you ship to it runs on the V8 engine, similar to Node &amp; Chromium browsers, but it doesn&#x27;t expose many of the APIs these two have. This makes it so you can&#x27;t use many packages common in modern JAMstack development:</p><ul><li><code>dotenv</code> breaks it as it requires <code>fs</code>, a Node API<ul><li>💡 You can get around this with <a href="https://www.npmjs.com/package/dotenv-cli" target="_blank" rel="noopener">dotenv-cli</a>, though</li></ul></li><li>Popular database adapters, such as <code>@supabase/supabase-js</code>, use XMLRequest (<a href="https://community.cloudflare.com/t/xmlhttprequest-is-not-defined/133866" target="_blank" rel="noopener">not supported in Workers</a>) to handle fetch requests in older browsers<ul><li>For Supabase specifically, <a href="https://github.com/supabase/supabase/tree/master/examples/with-cloudflare-workers" target="_blank" rel="noopener">the official example for integrating with Workers</a> relies on using <a href="npmjs.com/package/patch-package" target="_blank" rel="noopener">patch-package</a> in a postinstall script to modify cross-fetch, one of its dependencies. If you update your packages, you have to do it all over again - not the best experience!</li><li>I&#x27;ve experimented with the process above inside a slightly complex SvelteKit handler in <a href="https://github.com/hdoro/testing-svelte-kit-cloudflare-workers" target="_blank" rel="noopener">this repository</a>, and it almost worked! My future self will probably come back to try to fix the last bits.</li></ul></li><li>Many libraries reference <code>window</code>, a browser-only object</li></ul><p>In short, you&#x27;ll have to be extra <strong>careful about your usage of npm packages</strong>, and publishing any non-trivial project that will certainly face dependency issues.</p><p>There&#x27;s also the <strong>clunky deployment process</strong> - I&#x27;d need to set-up a GH action to do deployments or do it manually from my machine, both which involve extra work when compared to Vercel, Netlify or Workers&#x27; cousin, <a href="https://pages.cloudflare.com" target="_blank" rel="noopener">Cloudflare Pages</a>.</p><p>Cloudflare Pages is actually a big step up for them, and will soon power this very blog. However, it&#x27;s suitable solely for purely static sites, such as those powered by <a href="https://astro.build" target="_blank" rel="noopener">Astro</a> or <a href="https://elderguide.com/tech/elderjs/" target="_blank" rel="noopener">ElderJS</a>, as it doesn&#x27;t (yet?) integrate seamlessly with Workers. You need to have two separate processes for deploying the front-end and the dynamic API, which removes the possibility of deploying a NextJS or SvelteKit app using a mix of static and server-rendered routes.</p><p>I imagine they&#x27;re working on solving these issues as I write this, and I trust they&#x27;ll come around with better solutions for these problems. In my experience, their products and communication are usually very confusing and the UX isn&#x27;t the best, but the fact that Cloudflare Pages offers a really smooth flow is a sign they&#x27;re about to change that. They&#x27;re probably seeing Vercel&#x27;s success with all of the focus on Developer Experience (DX) &amp; productivity, and are now trying to follow.</p><p><em>If you represent Cloudflare and think I made an unjust statement here, do reach out (</em><a href="mailto:meet@hdoro.dev" rel="noopener noreferrer">meet@hdoro.dev</a> or <a href="https://twitter.com/hdorodev" target="_blank" rel="noopener noreferrer">hdorodev</a><em>)! I&#x27;d love to rectify and learn better 😊</em></p></div>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Integrating Sanity.io and Algolia]]></title>
            <link>https://hdoro.dev/integrating-sanity-io-algolia</link>
            <guid>https://hdoro.dev/integrating-sanity-io-algolia</guid>
            <pubDate>Fri, 23 Jul 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[3 steps for synchronizing your content across both systems]]></description>
            <content:encoded><![CDATA[<div class=""><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>You can watch the <a href="https://www.youtube.com/watch?v=ZtLtHiE4JEY" target="_blank" rel="noopener">video version of this guide here</a></p></div></div><p>Before using Algolia to search through our Sanity.io data, we first need to sync content between the two systems. This guide will help you with that.</p><p>There are 3 main parts to this equation:</p><ol><li>Defining &amp; querying the data we want to have in Algolia</li><li>Indexing all existing Sanity content, which we can do any time there&#x27;s a significant schema change</li><li>Synchronizing changes between them - happens through Sanity webhooks fired on create, delete &amp; update operations</li></ol><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.6608544027898866"><div style="width:100%;padding-bottom:60.20997375328084%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/42b96f6a299ad2dcc1a362a40fa36b10863c3880-1905x1147.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Diagram of the 2 ways we can add Sanity.io data into Algolia</figcaption></figure><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>I won&#x27;t get into <em>why</em> connect Sanity &amp; Algolia - I&#x27;m assuming you&#x27;re already sold and here to see the technical implementation details. <a href="https://twitter.com/hdorodev" target="_blank" rel="noopener">Let me know on Twitter</a> if I should cover that!</p></div></div><p></p><h2 id="heading-a6c4dad5dbdd"><a href="#heading-a6c4dad5dbdd" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Defining our search objects&#x27; data structure</h2><p>The bigger our Algolia records are, the slower search requests will be. <a href="https://www.algolia.com/doc/faq/basics/is-there-a-size-limit-for-my-index-records/" target="_blank" rel="noopener">The hard limit is 10kb</a>, but we want to keep them under 2kb for snappy response times. Hence, we don&#x27;t want to index our entire Sanity documents.</p><p>To think about the data you want to index, I make 5 informal distinctions between fields:</p><ol><li>Bureaucratic/required fields are those you have to include no matter what<ol><li><a href="https://www.algolia.com/doc/api-reference/api-methods/partial-update-objects/#method-param-objectid" target="_blank" rel="noopener">objectID for updating records</a> - usually the Sanity document&#x27;s <code>_id</code></li><li>document&#x27;s <code>_type</code></li><li>the <a href="https://www.sanity.io/docs/history-api" target="_blank" rel="noopener">revision ID</a> (<code>_rev</code>) as a just-in-case for special sync operations</li></ol></li><li>Fields for textual search - think titles, descriptions, headings, etc.</li><li>Data for <a href="https://www.algolia.com/doc/guides/managing-results/refine-results/faceting/" target="_blank" rel="noopener">faceting/categorization</a> - tags, status, authors, genre, etc.</li><li><a href="https://www.algolia.com/doc/guides/managing-results/must-do/custom-ranking/" target="_blank" rel="noopener">Custom ranking factors</a> - upvote count, date published, featured...<ol><li>These <a href="https://www.algolia.com/doc/guides/managing-results/must-do/custom-ranking/how-to/configure-custom-ranking/#metric-types" target="_blank" rel="noopener">values need to be booleans (true/false) or numbers</a>.</li><li>In my GROQ query below, you&#x27;ll notice I transformed <code>status</code> (a string/enum) into <code>statusNumber</code>.</li></ol></li><li>Presentational content you need for rendering results to users - images, visual properties, etc.</li></ol><p>To experiment with these, I recommend creating and testing GROQ queries. We&#x27;ll use the projections later on, so it&#x27;s worth the investment. Here&#x27;s a commented example from the recipes website I&#x27;m creating:</p><div class="code-block" data-explanation="false"><pre class="language-js"><span class="token operator">*</span><span class="token punctuation">[</span>
  _type <span class="token operator">==</span> <span class="token string">'recipe'</span>
  <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token punctuation">(</span>_id <span class="token keyword">in</span> <span class="token function">path</span><span class="token punctuation">(</span><span class="token string">'drafts.**'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token operator">&amp;&amp;</span> <span class="token function">defined</span><span class="token punctuation">(</span>slug<span class="token punctuation">.</span>current<span class="token punctuation">)</span>
  <span class="token operator">&amp;&amp;</span> status <span class="token operator">!=</span> <span class="token string">'unapproved'</span>
<span class="token punctuation">]</span> <span class="token punctuation">{</span>
  <span class="token comment">// Bureaucracies</span>
  _type<span class="token punctuation">,</span>
  _rev<span class="token punctuation">,</span>
  <span class="token string-property property">"objectID"</span><span class="token operator">:</span> _id<span class="token punctuation">,</span>
  _createdAt<span class="token punctuation">,</span>

  <span class="token comment">// Textual search</span>
  title<span class="token punctuation">,</span>
  description<span class="token punctuation">,</span>
  <span class="token string-property property">"ingredients"</span><span class="token operator">:</span> ingredients<span class="token punctuation">[</span>_type <span class="token operator">==</span> <span class="token string">"recipe.ingredient"</span><span class="token punctuation">]</span><span class="token punctuation">.</span>title<span class="token punctuation">,</span>
  <span class="token string-property property">"headings"</span><span class="token operator">:</span> pt<span class="token operator">:</span><span class="token operator">:</span><span class="token function">text</span><span class="token punctuation">(</span>body<span class="token punctuation">[</span>
    _type <span class="token operator">==</span> <span class="token string">"block"</span> <span class="token operator">&amp;&amp;</span> style <span class="token keyword">in</span> <span class="token punctuation">[</span><span class="token string">"h1"</span><span class="token punctuation">,</span> <span class="token string">"h2"</span><span class="token punctuation">,</span> <span class="token string">"h3"</span><span class="token punctuation">,</span> <span class="token string">"h4"</span><span class="token punctuation">]</span>
  <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span>

  <span class="token comment">// Faceting</span>
  duration<span class="token punctuation">,</span>
  <span class="token string-property property">"categories"</span><span class="token operator">:</span> categories<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>
    title<span class="token punctuation">,</span>
    _id<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token string-property property">"tags"</span><span class="token operator">:</span> tags<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>
    title<span class="token punctuation">,</span>
    _id<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token comment">// Finer ranking</span>
  <span class="token string-property property">"statusNumber"</span><span class="token operator">:</span> <span class="token function">select</span><span class="token punctuation">(</span>
    status <span class="token operator">==</span> <span class="token string">"approved"</span> <span class="token operator">=></span> <span class="token number">100</span><span class="token punctuation">,</span>
    status <span class="token operator">==</span> <span class="token string">"pendingReview"</span> <span class="token operator">=></span> <span class="token number">50</span><span class="token punctuation">,</span>
    <span class="token operator">-</span><span class="token number">100</span>
  <span class="token punctuation">)</span><span class="token punctuation">,</span>

  <span class="token comment">// Presentational content</span>
  <span class="token string-property property">"mainImage"</span><span class="token operator">:</span> photos<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><br /></pre></div><p>If something in the query above is unclear, I wrote an <a href="/learn-groq" target="_self" rel="">in-depth guide on GROQ</a> you may find useful 😉</p><p>After you run the query and make sure it&#x27;s getting exactly the data you want, I recommend saving a subset of documents in a JSON file and manually uploading them to Algolia&#x27;s dashboard to live test your search.</p><p>If you&#x27;re indexing multiple document types, I&#x27;d recommend using the same GROQ query for all of them and running conditionals based on their type. It&#x27;d make the processes below easier to reason about. Here&#x27;s an example:</p><div class="code-block" data-explanation="false"><pre class="language-js"><span class="token operator">*</span><span class="token punctuation">[</span>
  _type <span class="token keyword">in</span> <span class="token punctuation">[</span><span class="token string">'recipe'</span><span class="token punctuation">,</span> <span class="token string">'article'</span><span class="token punctuation">,</span> <span class="token string">'user'</span><span class="token punctuation">]</span>
  <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token punctuation">(</span>_id <span class="token keyword">in</span> <span class="token function">path</span><span class="token punctuation">(</span><span class="token string">'drafts.**'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token operator">&amp;&amp;</span> <span class="token function">defined</span><span class="token punctuation">(</span><span class="token function">coalesce</span><span class="token punctuation">(</span>handle<span class="token punctuation">.</span>current<span class="token punctuation">,</span> slug<span class="token punctuation">.</span>current<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">]</span> <span class="token punctuation">{</span>
  <span class="token comment">// Bureaucracies - shared across all</span>
  _type<span class="token punctuation">,</span>
  _rev<span class="token punctuation">,</span>
  <span class="token string-property property">"objectID"</span><span class="token operator">:</span> _id<span class="token punctuation">,</span>
  _createdAt<span class="token punctuation">,</span>

  _type <span class="token operator">!=</span> <span class="token string">"user"</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    title<span class="token punctuation">,</span> description<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  _type <span class="token operator">==</span> <span class="token string">"user"</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    name<span class="token punctuation">,</span> bio<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  _type <span class="token operator">==</span> <span class="token string">"recipe"</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token string-property property">"ingredients"</span><span class="token operator">:</span> ingredients<span class="token punctuation">[</span>_type <span class="token operator">==</span> <span class="token string">"recipe.ingredient"</span><span class="token punctuation">]</span><span class="token punctuation">.</span>title<span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>

  <span class="token comment">// ...</span>
<span class="token punctuation">}</span><br /></pre></div><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>Be sure to clear the index once you&#x27;re done before moving to the next step. Else it&#x27;ll be hard to see the effects of the programmatic insertions we&#x27;ll do below.</p></div></div><h2 id="heading-cbee99d736b1"><a href="#heading-cbee99d736b1" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Index all applicable documents</h2><p>After you figure out the data structure &amp; queries you need to run, indexing all is the simpler part. In short, we&#x27;ll get the data and use Algolia&#x27;s SDK to run <code>saveObjects</code> on it. Here&#x27;s my code, in the shape of a SvelteKit API endpoint:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">import</span> sanityServerClient <span class="token keyword">from</span> <span class="token string">'$lib/utils/sanityServerClient'</span>
<span class="token keyword">import</span> algoliasearch <span class="token keyword">from</span> <span class="token string">'algoliasearch'</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> algoliaInstance <span class="token operator">=</span> <span class="token function">algoliasearch</span><span class="token punctuation">(</span>
  process<span class="token punctuation">.</span>env<span class="token punctuation">[</span><span class="token string">'ALGOLIA_APPLICATION_ID'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  process<span class="token punctuation">.</span>env<span class="token punctuation">[</span><span class="token string">'ALGOLIA_ADMIN_KEY'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">)</span>

<span class="token keyword">const</span> <span class="token constant">QUERY</span> <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">YOUR_QUERY_HERE</span><span class="token template-punctuation string">`</span></span>

<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">get</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">request</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token comment">// Basic security to prevent others from hitting this API</span>
  <span class="token keyword">const</span> passphrase <span class="token operator">=</span> request<span class="token punctuation">.</span>query<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'passphrase'</span><span class="token punctuation">)</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>passphrase <span class="token operator">!==</span> process<span class="token punctuation">.</span>env<span class="token punctuation">[</span><span class="token string">'ALGOLIA_SECRET'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">401</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">const</span> documents <span class="token operator">=</span> <span class="token keyword">await</span> sanityServerClient<span class="token punctuation">.</span><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token constant">QUERY</span><span class="token punctuation">)</span>

  <span class="token keyword">const</span> index <span class="token operator">=</span> algoliaInstance<span class="token punctuation">.</span><span class="token function">initIndex</span><span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">[</span><span class="token string">'ALGOLIA_INDEX'</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">time</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Saving </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>documents<span class="token punctuation">.</span>length<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> documents to index:</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
    <span class="token keyword">await</span> index<span class="token punctuation">.</span><span class="token function">saveObjects</span><span class="token punctuation">(</span>documents<span class="token punctuation">)</span>
    console<span class="token punctuation">.</span><span class="token function">timeEnd</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Saving </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>documents<span class="token punctuation">.</span>length<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> documents to index:</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span>
      <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token string">'Success!'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">500</span><span class="token punctuation">,</span>
      <span class="token literal-property property">body</span><span class="token operator">:</span> error<span class="token punctuation">,</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<br /></pre></div><p>If this script is successful running, you <!-- -->should see your data once you <!-- -->refresh the Algolia dashboard 🎉🎉</p><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>I use a serverless endpoint because I want to be able to flush all data even when I&#x27;m on the move. You can of course create a local script only you have access to - it&#x27;s definitely safer from abuse.</p></div></div><h2 id="heading-c03ffd65aebd"><a href="#heading-c03ffd65aebd" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Syncing data via webhooks</h2><p>To make sure data is updated, deleted or created accordingly, we&#x27;ll need to create an endpoint for handling <a href="https://www.sanity.io/docs/webhooks" target="_blank" rel="noopener">Sanity&#x27;s webhooks</a>. These fire whenever there&#x27;s a change in your dataset, sending a payload similar with the <code>_ids</code> <!-- -->of documents updated, created or deleted. Something similar to this:</p><div class="code-block" data-explanation="false"><pre class="language-js"><span class="token punctuation">{</span>
	<span class="token string-property property">"ids"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
		<span class="token string-property property">"updated"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
		<span class="token string-property property">"created"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"M9IWNQzEM85EAsYtvAZnPd"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
		<span class="token string-property property">"deleted"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span><br /></pre></div><p>We could manually query these <code>_ids</code>, figure out their <code>_type</code>, get the appropriate data and send that to Algolia. Thankfully, though, we can delegate this grunt work to the <a href="https://github.com/sanity-io/sanity-algolia/" target="_blank" rel="noopener">official sanity-algolia package</a>.</p><p>Here&#x27;s how my serverless function for handling those webhooks looks like:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">import</span> sanityServerClient <span class="token keyword">from</span> <span class="token string">'$lib/utils/sanityServerClient'</span>
<span class="token keyword">import</span> indexer <span class="token keyword">from</span> <span class="token string">'sanity-algolia'</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> algoliaInstance<span class="token punctuation">,</span> <span class="token constant">RECIPE_PROJECTION</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./index-all'</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token literal-property property">post</span><span class="token operator">:</span> <span class="token function-variable function">RequestHandler</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">request</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> passphrase <span class="token operator">=</span> request<span class="token punctuation">.</span>query<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'passphrase'</span><span class="token punctuation">)</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>passphrase <span class="token operator">!==</span> process<span class="token punctuation">.</span>env<span class="token punctuation">[</span><span class="token string">'ALGOLIA_SECRET'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">401</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">const</span> index <span class="token operator">=</span> algoliaInstance<span class="token punctuation">.</span><span class="token function">initIndex</span><span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">[</span><span class="token string">'ALGOLIA_INDEX'</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> sanityAlgolia <span class="token operator">=</span> <span class="token function">indexer</span><span class="token punctuation">(</span>
    <span class="token punctuation">{</span>
      <span class="token literal-property property">recipe</span><span class="token operator">:</span> <span class="token punctuation">{</span>
        index<span class="token punctuation">,</span>
        <span class="token comment">// The projection is the piece of the GROQ query</span>
        <span class="token comment">// where we determine what data to fetch</span>
        <span class="token literal-property property">projection</span><span class="token operator">:</span> <span class="token constant">RECIPE_PROJECTION</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// 💡 Could have many other document types here!</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    
    <span class="token comment">// Serializer function for manipulating documents with Javascript</span>
    <span class="token comment">// I'm not using it as GROQ is doing all the work</span>
    <span class="token punctuation">(</span><span class="token parameter">document</span><span class="token punctuation">)</span> <span class="token operator">=></span> document<span class="token punctuation">,</span>
    
    <span class="token comment">// Visibility function to determine which document should be included</span>
    <span class="token punctuation">(</span><span class="token parameter">document</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">!</span><span class="token punctuation">[</span><span class="token string">'unapproved'</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span>status<span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span>

  <span class="token comment">// Now let sanityAlgolia do the heavy lifting</span>
  <span class="token keyword">return</span> sanityAlgolia
    <span class="token punctuation">.</span><span class="token function">webhookSync</span><span class="token punctuation">(</span>sanityServerClient<span class="token punctuation">,</span> request<span class="token punctuation">.</span>body <span class="token keyword">as</span> any<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span>
      <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token string">'Success!'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token literal-property property">status</span><span class="token operator">:</span> <span class="token number">500</span><span class="token punctuation">,</span>
      <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token string">'Something went wrong'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<br /></pre></div><p>To test this is working, we can manually create a webhook payload and send that to the serverless function. For example, let&#x27;s pick up the first object in Algolia and try to delete it by sending a request similar to this:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token constant">WEBHOOK_ENDPOINT</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">"POST"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token string-property property">"Content-Type"</span><span class="token operator">:</span> <span class="token string">"application/json"</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token literal-property property">ids</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">updated</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token literal-property property">created</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token comment">// Add your document _id below:</span>
      <span class="token literal-property property">deleted</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"M9IWNQzEM85EAsYtvAZnPd"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /></pre></div><p>If you got a 200 out of that, open the Algolia dashboard, wait a few seconds and refresh it to see if the document is gone. Otherwise, triple-check your credentials &amp; index, and read through sanity-algolia&#x27;s docs.</p><p>If the experiment above is successful, it means <strong>you&#x27;re ready for production</strong>! Send your endpoint live, add its URL to <a href="https://www.sanity.io/manage" target="_blank" rel="noopener">Sanity&#x27;s manage dashboard</a> as a webhook for your project &amp; you&#x27;re good to go 🎉</p><p>This integration can definitely go deeper than this. My first implementation involved synchronizing almost 20 document types across 2 different Sanity datasets, with all sorts of data normalization challenges. I hope this guide gives a solid understanding that you can use when tackling these more complex use-cases. If not, feel free to reach out at <a href="mailto:meet@hdoro.dev" rel="noopener noreferrer">meet@hdoro.dev</a> or <a href="https://twitter.com/hdorodev" target="_blank" rel="noopener noreferrer">hdorodev</a>!</p><p>I may eventually write about the search UI I&#x27;m building with Xstate and Svelte, with a strong focus on SEO, performance &amp; UX. Glad to hear if that&#x27;s of interest to you 😊</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Rendering PortableText from scratch]]></title>
            <link>https://hdoro.dev/rendering-portable-text-from-scratch</link>
            <guid>https://hdoro.dev/rendering-portable-text-from-scratch</guid>
            <pubDate>Wed, 21 Jul 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[A walkthrough of my thought process for creating a PortableText component for Svelte with 0 dependencies.]]></description>
            <content:encoded><![CDATA[<div class=""><p><strong>TL;DR:</strong> if you&#x27;re in need of a PortableText renderer for Svelte, the <a href="https://www.npmjs.com/package/@portabletext/svelte" target="_blank" rel="noopener">official @portabletext/svelte package</a> is the way to go. The minimal renderer I built from the approach below can be found in this <a href="https://github.com/hdoro/svelte-pt-concept" target="_blank" rel="noopener">this Github repo</a> 😊</p><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Context</div><div class=""><p><a href="https://portabletext.org" target="_blank" rel="noopener">PortableText</a> (<strong>PT</strong>) is a JSON-based rich-text format, heavily used in <a href="https://sanity.io" target="_self" rel="">Sanity.io</a>, the creators of the technology. If you&#x27;ve ever had to migrate WordPress posts, extract insights from HTML or equip CMS editors with custom components for their pages, PortableText will feel like a breath of fresh air.</p><p>The rest of this guide assumes some familiarity with it.</p></div></div><p>Usually, when you want to render PortableText in your front-end(s), you reach to one of the official packages such as <a href="https://www.npmjs.com/package/@sanity/block-content-to-react" target="_blank" rel="noopener">@sanity/block-content-to-react</a> or <a href="https://www.npmjs.com/package/@sanity/block-content-to-html" target="_self" rel="">@sanity/block-content-to-html</a>. When your the medium/framework you&#x27;re developing in doesn&#x27;t have a solid alternative for rendering, though, things can get complicated.</p><p>I&#x27;m currently building a community-driven recipes website with my wife. It&#x27;s one of those projects where I give myself the luxury of creating everything from scratch to learn and challenge myself. We&#x27;re using Svelte, which has no stable PortableText renderer - although <a href="https://www.npmjs.com/package/@movingbrands/svelte-portable-text" target="_blank" rel="noopener">MovingBrand&#x27;s package</a> and <a href="https://github.com/runeh/svelte-pote" target="_blank" rel="noopener">Rune&#x27;s svelte-pote</a> are worth mentioning (thanks for the effort, y&#x27;all!), they aren&#x27;t super mature yet.</p><p>Knowing that, I was a reluctant from using PT-based content in the app as I knew I&#x27;d had to hand-roll my own implementation, but figured it&#x27;d be a good flexer of my understanding of the format.</p><p>In the past I&#x27;ve built a handful of Svelte-powered static websites using <code>@sanity/block-content-to-html</code> and rendering them as HTML with <code>{@html}</code>. It was messy, but it worked for static components. As now I need dynamic functionality such as inserting the logged user&#x27;s name inside the text, this wasn&#x27;t an option.</p><h2 id="heading-e708288417c6"><a href="#heading-e708288417c6" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Creating our renderer</h2><p>Let&#x27;s take the start of this article as an example of PortableText data:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">const</span> example <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token punctuation">{</span>
    <span class="token comment">// Rich text block (paragraph/header/list/quote)</span>
    <span class="token literal-property property">_type</span><span class="token operator">:</span> <span class="token string">"block"</span><span class="token punctuation">,</span>
    <span class="token literal-property property">_key</span><span class="token operator">:</span> <span class="token string">"2ca469a56edd"</span><span class="token punctuation">,</span>
    <span class="token comment">// Block type: "paragraph"</span>
    <span class="token literal-property property">style</span><span class="token operator">:</span> <span class="token string">"normal"</span><span class="token punctuation">,</span>
    <span class="token comment">// Marks enrich text with custom data or formatting, defined here</span>
    <span class="token literal-property property">markDefs</span><span class="token operator">:</span> <span class="token punctuation">[</span>
      <span class="token punctuation">{</span>
        <span class="token literal-property property">_type</span><span class="token operator">:</span> <span class="token string">"blockAbsUrl"</span><span class="token punctuation">,</span>
        <span class="token literal-property property">_key</span><span class="token operator">:</span> <span class="token string">"06d4cabb8fc8"</span><span class="token punctuation">,</span>
        <span class="token literal-property property">url</span><span class="token operator">:</span> <span class="token string">"https://www.npmjs.com/package/@sanity/block-content-to-react"</span><span class="token punctuation">,</span>
        <span class="token literal-property property">newWindow</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token punctuation">[</span>
      <span class="token punctuation">{</span>
        <span class="token literal-property property">_type</span><span class="token operator">:</span> <span class="token string">"span"</span><span class="token punctuation">,</span>
        <span class="token literal-property property">_key</span><span class="token operator">:</span> <span class="token string">"59e1710cc3f8"</span><span class="token punctuation">,</span>
        <span class="token literal-property property">text</span><span class="token operator">:</span> <span class="token string">"Usually, when you want to render PortableText in your front-end(s), you reach to one of the official packages, "</span><span class="token punctuation">,</span>
        <span class="token literal-property property">marks</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">{</span>
        <span class="token literal-property property">_type</span><span class="token operator">:</span> <span class="token string">"span"</span><span class="token punctuation">,</span>
        <span class="token literal-property property">_key</span><span class="token operator">:</span> <span class="token string">"asdoih123893f8"</span><span class="token punctuation">,</span>
        <span class="token literal-property property">text</span><span class="token operator">:</span> <span class="token string">"such as "</span><span class="token punctuation">,</span>
        <span class="token comment">// Marks can be about formatting, such as strong, em, code, etc.</span>
        <span class="token literal-property property">marks</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"em"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">{</span>
        <span class="token literal-property property">_type</span><span class="token operator">:</span> <span class="token string">"span"</span><span class="token punctuation">,</span>
        <span class="token literal-property property">_key</span><span class="token operator">:</span> <span class="token string">"4792e1707c91"</span><span class="token punctuation">,</span>
        <span class="token literal-property property">text</span><span class="token operator">:</span> <span class="token string">"@sanity/block-content-to-react"</span><span class="token punctuation">,</span>
        <span class="token comment">// Or you can use your own markDefs, such as a link</span>
        <span class="token comment">// Refers to the mark definition above (blockAbsUrl)</span>
        <span class="token literal-property property">marks</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"06d4cabb8fc8"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    <span class="token comment">// Custom component type:</span>
    <span class="token literal-property property">_type</span><span class="token operator">:</span> <span class="token string">"callout"</span><span class="token punctuation">,</span>
    <span class="token literal-property property">_key</span><span class="token operator">:</span> <span class="token string">"8d196ce704a5"</span><span class="token punctuation">,</span>
    <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">"Context"</span><span class="token punctuation">,</span>
    <span class="token comment">// Nested PortableText:</span>
    <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token punctuation">[</span>
      <span class="token punctuation">{</span>
        <span class="token literal-property property">_type</span><span class="token operator">:</span> <span class="token string">"block"</span><span class="token punctuation">,</span>
        <span class="token literal-property property">_key</span><span class="token operator">:</span> <span class="token string">"6ec552e3531b"</span><span class="token punctuation">,</span>
        <span class="token comment">// ...</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span><br /></pre></div><p>Its base structure is an array of objects, each containing a <code>_key</code> and <code>_type</code>.</p><h3 id="heading-51e70dffd858"><a href="#heading-51e70dffd858" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Rendering custom blocks</h3><p>We can have custom components/blocks with their own data structure, such as the <code>callout</code> which has a <code>title</code> and a <code>body</code>, which is its own PortableText instance. This is a solid place to start our renderer:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token comment">&lt;!-- PortableText.svelte --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">export</span> <span class="token keyword">let</span> blocks <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
  <span class="token keyword">export</span> <span class="token keyword">let</span> serializers <span class="token operator">=</span> <span class="token keyword">undefined</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

{#each blocks as block, index (block._key)}
  {#if serializers?.types?.[block._type]}
    <span class="token comment">&lt;!-- Custom block-level element --></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>component</span>
      <span class="token attr-name">this</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{serializers.types[block._type]}</span>
      <span class="token attr-name">{block}</span>
      <span class="token attr-name">{index}</span>
      <span class="token attr-name">{blocks}</span>
    <span class="token punctuation">/></span></span>
  {:else}
    <span class="token comment">&lt;!-- Block _type not yet supported --></span>
  {/if}
{/each}
<br /></pre></div><p>In the <code>PortableText.svelte</code> component above, we&#x27;re mapping over all <code>blocks</code> and rendering a Svelte component for them based on their <code>_type</code>. The <code>serializers</code> property is an object that takes components for rendering custom <code>marks</code> and <code>types</code> - here&#x27;s an example of that in action:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">import</span> PortableText <span class="token keyword">from</span> <span class="token string">'../PortableText/PortableText.svelte'</span>
  <span class="token keyword">import</span> AbsoluteURL <span class="token keyword">from</span> <span class="token string">'../PTElements/AbsoluteURL.svelte'</span>
  <span class="token keyword">import</span> Callout <span class="token keyword">from</span> <span class="token string">'../PTElements/Callout.svelte'</span>

  <span class="token keyword">export</span> <span class="token keyword">let</span> article
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

&lt;PortableText
  blocks={article.body}
  serializers={{
    // Rendering the Callout component for the callout block _type
    types: {
      callout: Callout,
    },
    marks: {
      absUrl: AbsoluteURL,
    },
  }}
/>
<br /></pre></div><p>If we render the above for the example data, we&#x27;ll get a Callout rendered 🎉</p><h3 id="heading-adf69469c843"><a href="#heading-adf69469c843" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Rendering rich text</h3><p>It&#x27;s a good start, but to get the full content we&#x27;ll need to render entries of <code>_type: &quot;block&quot;</code>, and this is where things start getting a bit complicated. PortableText blocks have the following complexities:</p><ul><li>They have multiple <code>children</code>, each which have their own <code>marks</code><ul><li>Marks can set formatting (<code>marks: [&quot;strong&quot;, &quot;em&quot;]</code> for an italicized, bold text)</li><li>Or they can refer to a custom mark defined in the block&#x27;s <code>markDefs</code> (<code>marks: [&quot;06d4cabb8fc8&quot;]</code>, where this id refers to a mark definition&#x27;s <code>_key</code>.</li><li><code>marks: [&quot;strong&quot;, &quot;06d4cabb8fc8&quot;]</code> for a bold link, for example</li><li>This means a single text property can be wrapped in multiple elements (<code>&lt;strong&gt;&lt;em&gt;&lt;a&gt;Click me!&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;</code>)</li></ul></li><li>Children also have their <code>_type</code>s<ul><li>Regular text is of <code>_type: &quot;span&quot;</code></li><li>But we can also add custom inline-blocks, such as <code>_type: &quot;userInfo&quot;</code> for displaying the user&#x27;s name and avatar in the middle of the text</li></ul></li><li>Their <code>style</code><ul><li>The <code>normal</code> style is the regular <code>&lt;p&gt;</code> element in HTML land</li><li><code>h1-h6</code> and <code>blockquote</code> are also straightforward</li><li>But styles can be customized. For example, I often use <code>textCenter</code> in my projects for a centralized paragraph.</li></ul></li><li>The <code>level</code> and <code>listItem</code> properties set their list indentation<ul><li>This means there is no &quot;parent bullet&quot; containing its children items, which would be easier to reason about.</li></ul></li></ul><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>As I still didn&#x27;t face the need for lists in my project&#x27;s rich content, I won&#x27;t cover them (yet?)</p></div></div><p>Let&#x27;s start by creating a <code>BlockRenderer</code> component and plugging it in into PortableText:</p><div class="code-block" data-explanation="false"><pre class="language-html">{#each blocks as block, index (block._key)}
  {#if serializers?.types?.[block._type]}
    <span class="token comment">&lt;!-- Custom block-level element --></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>component</span>
      <span class="token attr-name">this</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{serializers.types[block._type]}</span>
      <span class="token attr-name">{block}</span>
      <span class="token attr-name">{index}</span>
      <span class="token attr-name">{blocks}</span>
    <span class="token punctuation">/></span></span>
  {:else if block._type === "block"}
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>BlockRenderer</span> <span class="token attr-name">{blocks}</span> <span class="token attr-name">{index}</span> <span class="token attr-name">{block}</span> <span class="token attr-name">{serializers}</span> <span class="token punctuation">/></span></span>
  {:else}
    <span class="token comment">&lt;!-- Block _type not yet supported --></span>
  {/if}
{/each}<br /></pre></div><p>Aside from the wrapper element, <code>BlockRenderer</code> is very similar to <code>PortableText</code>: it gets an array of entries (in this case <code>block.children</code>) and renders components for each based on their <code>_type</code>. <!-- -->For now, let&#x27;s wrap every block in a <code></code>paragraph - we&#x27;ll deal with styles later:<code></code></p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token comment">&lt;!-- BlockRenderer.svelte --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">import</span> BlockSpan <span class="token keyword">from</span> <span class="token string">'./BlockSpan.svelte'</span>
  <span class="token keyword">import</span> type <span class="token punctuation">{</span> PTBlock<span class="token punctuation">,</span> Serializers <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./ptTypes'</span>

  <span class="token keyword">export</span> <span class="token keyword">let</span> index
  <span class="token keyword">export</span> <span class="token keyword">let</span> blocks
  <span class="token keyword">export</span> <span class="token keyword">let</span> block
  <span class="token keyword">export</span> <span class="token keyword">let</span> serializers
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>
  {#each block.children as child (child._key)}
    {#if serializers?.types?.[child._type]}
      <span class="token comment">&lt;!-- Custom inline element --></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>component</span>
        <span class="token attr-name">this</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{serializers.types[child._type]}</span>
        <span class="token attr-name">{block}</span>
        <span class="token attr-name">node</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{child}</span>
      <span class="token punctuation">/></span></span>
    {:else if child._type === 'span'}
      <span class="token comment">&lt;!-- Regular span / text child --></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>BlockSpan</span> <span class="token attr-name">span</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{child}</span> <span class="token attr-name">{block}</span> <span class="token attr-name">{serializers}</span><span class="token punctuation">></span></span>{child.text}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>BlockSpan</span><span class="token punctuation">></span></span>
    {:else}
      <span class="token comment">&lt;!-- Unsupported child _type --></span>
    {/if}
  {/each}
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
<br /></pre></div><p>This is enough for custom inline components. The <code>BlockSpan</code> requires a bit more work.</p><p>Before proceeding, notice how in the code above we&#x27;re adding <code>{child.text}</code> to the <code>&lt;BlockSpan&gt;</code>&#x27;s content. This is the actual textual value users will see, and it&#x27;ll be exposed inside <code>BlockSpan</code> as a <code>&lt;slot&gt;</code>, which is very similar to React&#x27;s <code>props.children</code> (<a href="https://svelte.dev/docs#slot" target="_blank" rel="noopener">more on Svelte slots</a>).</p><p>As mentioned above, we can have multiple marks per <code>_type: &quot;span<!-- -->&quot;</code>, so we&#x27;ll need to do a bit of recursion in order to properly render the full span. <!-- -->In the <code>BlockSpan</code> implementation below, notice how <!-- -->each instance of it is focused solely on one <code>mark</code> and recursively renders another child BlockSpan<!-- --> with the remaining marks (<code>nestedSpan</code>)<!-- -->:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token comment">&lt;!-- BlockSpan.svelte --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">export</span> <span class="token keyword">let</span> block
  <span class="token keyword">export</span> <span class="token keyword">let</span> span
  <span class="token keyword">export</span> <span class="token keyword">let</span> serializers

  <span class="token literal-property property">$</span><span class="token operator">:</span> allMarks <span class="token operator">=</span> span<span class="token punctuation">.</span>marks <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>

  <span class="token comment">// Let's start with the first mark</span>
  <span class="token literal-property property">$</span><span class="token operator">:</span> currentMark <span class="token operator">=</span>
    <span class="token comment">// If the mark references an entry in markDefs, use that object as the currentMark</span>
    block<span class="token punctuation">.</span>markDefs<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">def</span><span class="token punctuation">)</span> <span class="token operator">=></span> def<span class="token punctuation">.</span>_key <span class="token operator">===</span> allMarks<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">||</span> allMarks<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>

  <span class="token comment">// If we have more marks, we'll render a nested BlockSpan with remaining marks</span>
  <span class="token literal-property property">$</span><span class="token operator">:</span> nestedSpan <span class="token operator">=</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>span<span class="token punctuation">,</span>
    <span class="token literal-property property">marks</span><span class="token operator">:</span> allMarks<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span>

  <span class="token literal-property property">$</span><span class="token operator">:</span> customComponent <span class="token operator">=</span> serializers<span class="token operator">?.</span>marks
    <span class="token operator">?</span> <span class="token keyword">typeof</span> currentMark <span class="token operator">===</span> <span class="token string">'string'</span>
      <span class="token operator">?</span> serializers<span class="token punctuation">.</span>marks<span class="token punctuation">[</span>currentMark<span class="token punctuation">]</span>
      <span class="token operator">:</span> serializers<span class="token punctuation">.</span>marks<span class="token punctuation">[</span>currentMark<span class="token operator">?.</span>_type<span class="token punctuation">]</span>
    <span class="token operator">:</span> <span class="token keyword">undefined</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

{#if !currentMark}
  <span class="token comment">&lt;!-- If no current mark, render only the text without wrapping elements --></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span>
{:else if customComponent}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>component</span> <span class="token attr-name">this</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{customComponent}</span> <span class="token attr-name">{block}</span> <span class="token attr-name">{span}</span> <span class="token attr-name">mark</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{currentMark}</span><span class="token punctuation">></span></span>
    <span class="token comment">&lt;!-- Inside the custom component, render recursive BlockSpan with remaining marks --></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>self</span> <span class="token attr-name">{block}</span> <span class="token attr-name">span</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{nestedSpan}</span> <span class="token attr-name">{serializers}</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>self</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>component</span><span class="token punctuation">></span></span>
{:else if currentMark === 'strong'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>self</span> <span class="token attr-name">{block}</span> <span class="token attr-name">span</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{nestedSpan}</span> <span class="token attr-name">{serializers}</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>self</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">></span></span>
{:else if currentMark === 'em'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>em</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>self</span> <span class="token attr-name">{block}</span> <span class="token attr-name">span</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{nestedSpan}</span> <span class="token attr-name">{serializers}</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>self</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>em</span><span class="token punctuation">></span></span>
{:else if currentMark === 'code'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>code</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>self</span> <span class="token attr-name">{block}</span> <span class="token attr-name">span</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{nestedSpan}</span> <span class="token attr-name">{serializers}</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>self</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>code</span><span class="token punctuation">></span></span>
{:else if currentMark === 'underline'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>u</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>self</span> <span class="token attr-name">{block}</span> <span class="token attr-name">span</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{nestedSpan}</span> <span class="token attr-name">{serializers}</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>self</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>u</span><span class="token punctuation">></span></span>
{:else if currentMark === 'strike-through'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>s</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>self</span> <span class="token attr-name">{block}</span> <span class="token attr-name">span</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{nestedSpan}</span> <span class="token attr-name">{serializers}</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>self</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>s</span><span class="token punctuation">></span></span>
{:else}
  <span class="token comment">&lt;!-- Unsupported mark _type - let's render the plain text --></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span>
{/if}
<br /></pre></div><p>With the above, we have rich text with proper formatting and inline notations in a customizable way! Say we want to add custom classes to bold text - the default is a plain <code>&lt;strong&gt;</code> element as you can see above -, we&#x27;d do the following with <code>&lt;PortableText&gt;</code>:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">import</span> PortableText <span class="token keyword">from</span> <span class="token string">'../PortableText/PortableText.svelte'</span>
  <span class="token keyword">import</span> AbsoluteURL <span class="token keyword">from</span> <span class="token string">'../PTElements/AbsoluteURL.svelte'</span>
  <span class="token keyword">import</span> CustomStrong <span class="token keyword">from</span> <span class="token string">'../PTElements/CustomStrong.svelte'</span>

  <span class="token keyword">export</span> <span class="token keyword">let</span> article
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

&lt;PortableText
  blocks={article.body}
  serializers={{
    marks: {
      // Custom mark
      absUrl: AbsoluteURL,
      // Custom component for format-only mark
      strong: CustomStrong
    },
  }}
/>
<br /></pre></div><p>Here&#x27;s what <code>CustomStrong</code> could look like:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token comment">&lt;!-- CustomStrong.svelte --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-gray-900 font-black<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token comment">&lt;!-- Emojis for extra boldness 💥 --></span>
  👉 <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span> 👈
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">></span></span><br /></pre></div><p>Aside from not handling lists, the one issue with this PortableText renderer is that every block will be rendered inside a paragraph, even when its <code>style</code> is a heading, quote, etc. Let&#x27;s fix that by replacing the wrapping <code>&lt;p&gt;</code> in <code>BlockRenderer.svelte</code> with a new component, <code>BlockWrapper</code>:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token comment">&lt;!-- Subset of BlockRenderer.svelte --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>BlockWrapper</span> <span class="token attr-name">{block}</span> <span class="token attr-name">{serializers}</span> <span class="token attr-name">{index}</span> <span class="token attr-name">{blocks}</span><span class="token punctuation">></span></span>
  {#each block.children as child (child._key)}
    <span class="token comment">&lt;!-- rendering logic (see previous code example) --></span>
  {/each}
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>BlockWrapper</span><span class="token punctuation">></span></span>
<br /></pre></div><p><code>BlockWrapper</code> is <em>very</em> similar to <code>BlockSpan</code> in the sense that its sole purpose is to wrap its children in appropriate tags. The difference is that it doesn&#x27;t need the complicated recursion we saw above as each block can only have one <code>style</code>. The result is a much simpler component:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">import</span> type <span class="token punctuation">{</span> PTBlock<span class="token punctuation">,</span> Serializers <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./ptTypes'</span>
  <span class="token keyword">import</span> ReportError <span class="token keyword">from</span> <span class="token string">'./ReportError.svelte'</span>

  <span class="token keyword">export</span> <span class="token keyword">let</span> index
  <span class="token keyword">export</span> <span class="token keyword">let</span> blocks
  <span class="token keyword">export</span> <span class="token keyword">let</span> block
  <span class="token keyword">export</span> <span class="token keyword">let</span> serializers

  <span class="token literal-property property">$</span><span class="token operator">:</span> style <span class="token operator">=</span> block<span class="token punctuation">.</span>style <span class="token operator">||</span> <span class="token string">'normal'</span>

  <span class="token literal-property property">$</span><span class="token operator">:</span> customStyle <span class="token operator">=</span> serializers<span class="token operator">?.</span>blockStyles<span class="token operator">?.</span><span class="token punctuation">[</span>style<span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token keyword">undefined</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

{#if customStyle}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>component</span> <span class="token attr-name">this</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{customStyle}</span> <span class="token attr-name">{block}</span> <span class="token attr-name">{index}</span> <span class="token attr-name">{blocks}</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>component</span><span class="token punctuation">></span></span>
{:else if style === 'h1'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span>
{:else if style === 'h2'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span>
{:else if style === 'h3'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">></span></span>
{:else if style === 'h4'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h4</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h4</span><span class="token punctuation">></span></span>
{:else if style === 'h5'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h5</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h5</span><span class="token punctuation">></span></span>
{:else if style === 'h6'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h6</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h6</span><span class="token punctuation">></span></span>
{:else if style === 'blockquote'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>blockquote</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>blockquote</span><span class="token punctuation">></span></span>
{:else if style === 'normal'}
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
{:else}
  <span class="token comment">&lt;!-- Unsupported style - let's render it inside a paragraph to prevent hiding content --></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span>
{/if}
<br /></pre></div><p>Similarly to custom types and marks, some times we want to render styles differently. In order to deal with that, <!-- -->the serializers object<!-- --> can also take <code>blockStyles</code>:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">import</span> PortableText <span class="token keyword">from</span> <span class="token string">'../PortableText/PortableText.svelte'</span>
  <span class="token keyword">import</span> CustomHeading <span class="token keyword">from</span> <span class="token string">'../PTElements/CustomHeading.svelte'</span>
  <span class="token keyword">import</span> CentralizedText <span class="token keyword">from</span> <span class="token string">'../PTElements/CentralizedText.svelte'</span>

  <span class="token keyword">export</span> <span class="token keyword">let</span> article
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

&lt;PortableText
  blocks={article.body}
  serializers={{
    blockStyles: {
      // Custom heading 1
      h1: CustomHeading,
      // Custom user-defined style
      textCenter: CentralizedText
    },
  }}
/>
<br /></pre></div><p>And, as we&#x27;ve passed <code>index</code> and <code>blocks</code> properties to our custom style components, our <code>CustomHeading</code> can adapt its styles based on other blocks near it - what is often called <em>rule-based design</em>:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token comment">&lt;!-- CustomHeading --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">export</span> <span class="token keyword">let</span> index
  <span class="token keyword">export</span> <span class="token keyword">let</span> blocks
  <span class="token keyword">export</span> <span class="token keyword">let</span> block

  <span class="token keyword">const</span> <span class="token constant">HEADING_STYLES</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"h1"</span><span class="token punctuation">,</span> <span class="token string">"h2"</span><span class="token punctuation">,</span> <span class="token string">"h3"</span><span class="token punctuation">,</span> <span class="token string">"h4"</span><span class="token punctuation">,</span> <span class="token string">"h5"</span><span class="token punctuation">]</span>
  <span class="token literal-property property">$</span><span class="token operator">:</span> style <span class="token operator">=</span> block<span class="token punctuation">.</span>style
  <span class="token literal-property property">$</span><span class="token operator">:</span> precededByHeading <span class="token operator">=</span> <span class="token constant">HEADING_STYLES</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>blocks<span class="token punctuation">[</span>index <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token operator">?.</span>style<span class="token punctuation">)</span>

  <span class="token literal-property property">$</span><span class="token operator">:</span> anchorId <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">heading-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>block<span class="token punctuation">.</span>_key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

<span class="token comment">&lt;!-- If preceded by heading, have a higher margin top --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>relative {precededByHeading ? <span class="token punctuation">"</span></span><span class="token attr-name"><span class="token namespace">mt-10":</span></span> <span class="token attr-name">"mt-4"}"</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{anchorId}</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#{anchorId}<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sr-only<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Link to this heading<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span>
    🔗
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
  {#if style === "h1"}
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-4xl font-black<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span>
  {:else if style === "h2"}
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-3xl<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span>
  {:else}
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-xl text-gray-600<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">></span></span>
  {/if}
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br /></pre></div><p>The example above is a bit underwhelming, but imagine sticking a CTA next to a contact form if they come together; or creating a grid of images if two or more small images follow each-other; or putting testimonials on top of their preceding case studies... the sky is the limit!</p><p>I hoped this served to shine some light on PortableText and make it less of a black box for you. You can find the <a href="https://github.com/hdoro/svelte-pt-concept" target="_self" rel=""><strong>full source code here</strong></a>. Glad to take contributions and feedback!</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Inline audio player in Sanity.io rich text]]></title>
            <link>https://hdoro.dev/inline-audio-player-sanity-io</link>
            <guid>https://hdoro.dev/inline-audio-player-sanity-io</guid>
            <pubDate>Mon, 26 Apr 2021 19:13:37 GMT</pubDate>
            <description><![CDATA[How to use the Portable Text Editor's flexibility to insert dynamic content in the middle of paragraphs]]></description>
            <content:encoded><![CDATA[<div class=""><p>We can host audio files in Sanity and plug them into paragraphs by adding custom types to the <code>block.of</code> array. You can see this in action in my <a href="/learn-groq" target="_self" rel="">Learn GROQ guide</a>. Without further ado, here&#x27;s the process:</p><p>Starting with the schema, we need to add the <code>inlineAudio</code> schema type to our block content&#x27;s <code>of</code> property (<a href="https://www.sanity.io/docs/block-type#of-d0f97ffa1dd9" target="_blank" rel="noopener">documentation on this property</a>):</p><div class="code-block" data-explanation="true"><pre class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"contentBody"</span><span class="token punctuation">,</span>
  <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'Body of content'</span><span class="token punctuation">,</span>
  <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'array'</span><span class="token punctuation">,</span>
  <span class="token keyword">of</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
      <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'block'</span><span class="token punctuation">,</span>
      <span class="token keyword">of</span><span class="token operator">:</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
          <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'inlineAudio'</span><span class="token punctuation">,</span>
          <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'file'</span><span class="token punctuation">,</span>
          <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'Inline audio player'</span><span class="token punctuation">,</span>
          <span class="token literal-property property">options</span><span class="token operator">:</span> <span class="token punctuation">{</span>
            <span class="token literal-property property">accept</span><span class="token operator">:</span> <span class="token string">'audio/*'</span><span class="token punctuation">,</span>
          <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span><span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"image"</span><span class="token punctuation">}</span>
  <span class="token punctuation">]</span>
<span class="token punctuation">}</span><br /></pre><pre class="language-javascript">







The <span class="token code-snippet code keyword">`of`</span> array defines which custom types
should be rendered inline




Accept only audio files




Notice the difference with custom types at the same level as the <span class="token code-snippet code keyword">`type: block`</span>:
these, like the image here, will be standalone blocks which are placed between paragraphs, not inside them.<br /></pre></div><p>This will allow us to add the &quot;Inline audio player&quot; element to our paragraph from the insert menu:</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:2.179080824088748"><div style="width:100%;padding-bottom:45.8909090909091%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/3d5eb214bf99af7410f3ba9ac11ce32b2fae8bb7-1375x631.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Screenshot of the rich text editor of this guide with the insert menu open</figcaption></figure><p>Now editors have the ability to add inline audio players to their content! 🎉🎉 Let&#x27;s cover how to render this in the front-end. Here&#x27;s a portion of the component that renders the block content of my articles (I&#x27;m using React and the <a href="https://www.npmjs.com/package/@sanity/block-content-to-react" target="_blank" rel="noopener">@sanity/block-content-to-react package</a>):</p><div class="code-block" data-explanation="false"><pre class="language-jsx"><span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> React <span class="token keyword">from</span> <span class="token string">'react'</span>
<span class="token keyword">import</span> BlockContent <span class="token keyword">from</span> <span class="token string">'@sanity/block-content-to-react'</span>

<span class="token keyword">import</span> AudioPlayer <span class="token keyword">from</span> <span class="token string">'../AudioPlayer'</span>

<span class="token keyword">const</span> serializers <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">types</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token comment">// Handler for the "inlineAudio" _type</span>
    <span class="token function-variable function">inlineAudio</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> node <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
      <span class="token comment">// The component we use to render the actual player</span>
      <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">AudioPlayer</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>node<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> <span class="token function-variable function">ArticleBlockContent</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">BlockContent</span></span>
      <span class="token attr-name">blocks</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>props<span class="token punctuation">.</span>blocks<span class="token punctuation">}</span></span>
      <span class="token attr-name">serializers</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>serializers<span class="token punctuation">}</span></span>
    <span class="token punctuation">/></span></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> ArticleBlockContent
<br /></pre></div><p>And here&#x27;s the <code>AudioPlayer</code> component that actually renders the data into an actionable button for users:</p><div class="code-block" data-explanation="false"><pre class="language-jsx"><span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> React <span class="token keyword">from</span> <span class="token string">'react'</span>

<span class="token keyword">const</span> <span class="token function-variable function">AudioPlayer</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token comment">// Used to store the audio element once instanciated</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>audioEl<span class="token punctuation">,</span> setAudioEl<span class="token punctuation">]</span> <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">useState</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>props<span class="token punctuation">.</span>asset<span class="token operator">?.</span>_ref<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">null</span>
  <span class="token punctuation">}</span>
  
  <span class="token keyword">const</span> <span class="token punctuation">{</span> <span class="token literal-property property">_ref</span><span class="token operator">:</span> ref <span class="token punctuation">}</span> <span class="token operator">=</span> props<span class="token punctuation">.</span>asset
  <span class="token comment">// Example:</span>
  <span class="token comment">// From: file-ff7d1c2d7bd5ac367359d57f0319f5f458bc3c3d-m4a</span>
  <span class="token comment">// To: https://cdn.sanity.io/files/q2j8cwsg/production/ff7sfgc2d7bd5ac367359d57f0319f5f458bc3c3d.m4a?dl</span>

  <span class="token keyword">const</span> assetRefParts <span class="token operator">=</span> ref<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'-'</span><span class="token punctuation">)</span> <span class="token comment">// ["file", "ff7...", "m4a"]</span>
  <span class="token keyword">const</span> id <span class="token operator">=</span> assetRefParts<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token comment">// "ff7..."</span>
  <span class="token keyword">const</span> format <span class="token operator">=</span> assetRefParts<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token comment">// "m4a"</span>
  <span class="token keyword">const</span> assetUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://cdn.sanity.io/files/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NEXT_PUBLIC_SANITY_PROJECT_ID</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NEXT_PUBLIC_SANITY_DATASET</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>format<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span>

  <span class="token keyword">function</span> <span class="token function">playAudio</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>audioEl<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> audio <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Audio</span><span class="token punctuation">(</span>assetUrl<span class="token punctuation">)</span>
        <span class="token function">setAudioEl</span><span class="token punctuation">(</span>audio<span class="token punctuation">)</span>
        audio<span class="token punctuation">.</span><span class="token function">play</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        audioEl<span class="token punctuation">.</span><span class="token function">play</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>playAudio<span class="token punctuation">}</span></span> <span class="token attr-name">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Play audio<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      🔊
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> AudioPlayer
<br /></pre></div><p>And that&#x27;s it! There are many more interesting use cases of inline blocks, some of which I hope to cover in the future.</p><p>Reach me at <a href="mailto:meet@hdoro.dev" rel="noopener noreferrer">meet@hdoro.dev</a> or <a href="https://twitter.com/hdorodev" target="_blank" rel="noopener noreferrer">hdorodev</a> if you have any questions ;)</p><p><strong>PS: </strong>This is also available in video on YouTube: <a href="https://www.youtube.com/watch?v=syFw-_XibFE" target="_blank" rel="noopener">https://www.youtube.com/watch?v=syFw-_XibFE</a></p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Get random document in Sanity.io with the Structure Builder]]></title>
            <link>https://hdoro.dev/random-document-sanityio-structure-builder</link>
            <guid>https://hdoro.dev/random-document-sanityio-structure-builder</guid>
            <pubDate>Thu, 01 Apr 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[One of the countless ways to use Sanity's structure builder to build unique and effective UIs for your editors]]></description>
            <content:encoded><![CDATA[<div class=""><p><strong>The most complicated part of content is its long term maintainability.</strong> Sure, producing is hard - you should see how long I&#x27;m taking to write these words -, but keeping your words up-to-date with organizational &amp; product changes, market shifts and even the cultural climate is an entirely new beast.</p><p>One way you can improve this process is by <strong>re-surfacing old content</strong> to editors for review. In Sanity, a quick way to do that is to add a menu item that displays a random piece of content. It&#x27;s the perfect use-case for <a href="https://www.sanity.io/docs/overview-structure-builder" target="_self" rel="">Sanity&#x27;s structure builder</a>.</p><p>I&#x27;ve <a href="https://youtu.be/PwURT3k9ToY" target="_self" rel="">recorded a video going through it</a>, in case you prefer that format.</p><h2 id="heading-58bf04de362f"><a href="#heading-58bf04de362f" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Displaying a random document with the structure builder</h2><p>Here&#x27;s the high-level approach to getting to that:</p><ol><li>Create a new <code>S.listItem</code> in your structure</li><li>Before rendering content inside of it, we&#x27;ll fetch all ids of a given collection of documents</li><li>Then we&#x27;ll pick a random id from this list</li><li>And render a <code>S.document(chosenId)</code> with this id</li></ol><p>The code to implement is short - without comments it could fit in 12 formatted lines:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">import</span> <span class="token constant">S</span> <span class="token keyword">from</span> <span class="token string">'@sanity/desk-tool/structure-builder'</span>
<span class="token keyword">import</span> client <span class="token keyword">from</span> <span class="token string">'part:@sanity/base/client'</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span>
  <span class="token constant">S</span><span class="token punctuation">.</span><span class="token function">list</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">title</span><span class="token punctuation">(</span><span class="token string">'Content'</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">items</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
      <span class="token constant">S</span><span class="token punctuation">.</span><span class="token function">documentTypeListItem</span><span class="token punctuation">(</span><span class="token string">'idea'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">title</span><span class="token punctuation">(</span><span class="token string">'Ideas'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token comment">// New item in the desk menu:</span>
      <span class="token constant">S</span><span class="token punctuation">.</span><span class="token function">listItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token comment">// Add a recognizable title &amp; icon</span>
        <span class="token punctuation">.</span><span class="token function">title</span><span class="token punctuation">(</span><span class="token string">'Random idea'</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">icon</span><span class="token punctuation">(</span>FiHelpCircle<span class="token punctuation">)</span>
        <span class="token comment">// And use an async function to resolve its content</span>
        <span class="token punctuation">.</span><span class="token function">child</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token comment">// It'll first fetch the ids of every idea in the dataset</span>
          <span class="token keyword">const</span> ids <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">*[_type == 'idea']._id</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
          <span class="token comment">// Get a random one from that list</span>
          <span class="token keyword">const</span> chosenId <span class="token operator">=</span> <span class="token function">getRandomItem</span><span class="token punctuation">(</span>ids<span class="token punctuation">)</span>
          <span class="token comment">// And display the document for this random one</span>
          <span class="token keyword">return</span> <span class="token constant">S</span><span class="token punctuation">.</span><span class="token function">document</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span>chosenId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">schemaType</span><span class="token punctuation">(</span><span class="token string">'idea'</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token comment">// ...</span>
    <span class="token punctuation">]</span><span class="token punctuation">)</span>
    
<span class="token comment">// Used to find a random id every time the</span>
<span class="token comment">// "Random Idea" menu is clicked</span>
<span class="token keyword">function</span> <span class="token function">getRandomItem</span><span class="token punctuation">(</span><span class="token parameter">arr</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> randomIndex <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> arr<span class="token punctuation">.</span>length<span class="token punctuation">)</span>
  <span class="token keyword">return</span> arr<span class="token punctuation">[</span>randomIndex<span class="token punctuation">]</span>
<span class="token punctuation">}</span><br /></pre></div><p>Over the course of the next months I&#x27;ll try to share other examples of how the structure builder can be used to create effective editing environments.<!-- --> My goal is to show how <strong>Sanity can make content maintenance easier</strong> (or &quot;Content Ops&quot; in general)<!-- -->.</p><p>The structure above is something I&#x27;m using for myself. On a personal level, I&#x27;m hoping that by reviewing my ideas more often, I&#x27;ll learn better. After all, <a href="/life-long-wisdom-takes-time">Learning life-long wisdom takes time</a> ;)</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Designing an eco-friendly web framework (Greenconf 2020 talk)]]></title>
            <link>https://hdoro.dev/designing-an-eco-friendly-web-framework-greenconf</link>
            <guid>https://hdoro.dev/designing-an-eco-friendly-web-framework-greenconf</guid>
            <pubDate>Wed, 30 Sep 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[The written version of the talk I gave at Greenconf.io about how our developer tools could help us create greener websites and apps.]]></description>
            <content:encoded><![CDATA[<div class=""><p><strong>This is the written version of the talk I did for <a href="http://greenconf.io/">Greenconf 2020</a>.</strong> I&#x27;ll post the video here soon.</p><p>Hey there, everyone! Today I&#x27;ll show you a glimpse of what an eco-friendly web development framework could look like.</p><p>My name is Henrique and I&#x27;m starting my journey on making a positive impact on the planet through web design and development. Like you, I&#x27;m learning and often getting overwhelmed by the scary and complex sustainability topic, so feedback is very much appreciated.</p><p>First, I want to be clear about what I mean by &quot;web framework&quot;. This could go into many directions, but here we&#x27;ll focus on a developer tool for building content-driven websites and web-apps, focusing on the front-end. It&#x27;ll provide some back-end functionality but it won&#x27;t go deep in databases, permissions and complex APIs. It also won&#x27;t cover edge cases and super complex apps such as Twitter and Github.</p><p>This is still broad, but being broad is the idea. We&#x27;re covering ~95% of the use cases out there, from marketing sites to admin dashboards and small social apps. (Yes, 95% is an invention 😋)</p><p>A summary of what we want here is to conceive a developer tool that can materially reduce our websites and apps&#x27; carbon footprint while still achieving economic goals and being inclusive and accessible. To guide our decisions, we&#x27;ll focus on 3 qualitative questions:</p><ul><li>How can we power our work with renewable energy?<ul><li>This is proposed by Tim Frick in his book Designing for Sustainability</li><li>The idea is simple: data centers consume a lot of energy, if we can source that energy from renewables, then we&#x27;re already doing a great job</li></ul></li><li>How can we make it as efficient as possible?<ul><li>Also from Tim Frick, this question focuses on what can we do to reduce the overall energy consumption and digital waste of our apps?</li><li>This also includes concerns about UX design, as a bad user experience could lead to wasted energy on useless browsing</li></ul></li><li>And, even better, how can we avoid building unnecessary products and features?<ul><li>This one is especially important in our wasteful industry - think about all the features you built that users didn&#x27;t want and design extravaganzas you included in the final work because of stakeholder requests.</li><li>I know I&#x27;ve done that a lot</li><li>It hurts to think that those, besides being pointless, also have a carbon footprint.</li><li>Remember the greenest app is the one you don&#x27;t build</li></ul></li></ul><p>These are our three guiding principles that we&#x27;ll use to frame our solutions. As I know you&#x27;re wondering about the concrete numeric impact of this work, let&#x27;s take a look at an example to give you some perspective.</p><p>Danny van Kooten has <a href="https://dannyvankooten.com/website-carbon-emissions/">wrote a very nice piece on how he reduced his footprint by roughly 59 tons of CO2 per month</a> by reducing the Javascript size of his WordPress plugin by 20kb.</p><p>He lives in The Netherlands, whose citizens had an average of 10.3 tons of CO2e emitted in 2017. So with his tiny code change, he managed to offset the carbon footprint that ~70 fellow dutch people would have in a year. Even if he overshot his calculations by 50 times, this optimization would be enough to cover his personal impact for 1 year and 4 months.</p><p>This is an imperfect framing, of course, but it shows that as web developers we have a significant opportunity to use our work to make a positive impact on the world. Now that we know which questions to ask and how making our websites more sustainable could help the planet, let&#x27;s move on to how this framework could help.</p><p>This will be a rapid-fire of features and directives, I won&#x27;t go too deep into each of them. For details, go to hdoro.dev/sustainability. The goal here is to spark some inspiration and show concretely how we can help developers make sustainable choices more easily. With only 15 minutes I won&#x27;t really be able to convince you that X is materially greener than Y 😅</p><p>(I&#x27;m still writing the rest, in the meantime, refer to the repository on the features of an <a href="https://github.com/hdoro/eco-friendly-web-framework">eco-friendly-web-framework</a>)</p></div>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Speed up your Gatsby app in a day (Byteconf React 2020 talk)]]></title>
            <link>https://hdoro.dev/speed-up-gatsby-app-byteconf-react</link>
            <guid>https://hdoro.dev/speed-up-gatsby-app-byteconf-react</guid>
            <pubDate>Sat, 02 May 2020 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<div class=""><p>You can find the <a href="https://docs.google.com/presentation/d/1_OYE6qUB9WPPFs-HCRrWFw6rcov5mk0Kxa1_e9VtMkQ/edit?usp=sharing">slides here</a>. And the <a href="https://www.youtube.com/watch?v=RG2UdCCLdTs">video for the talk on YouTube</a> is below:</p><p>Unfortunately, I didn&#x27;t finish the written version yet 😥 I&#x27;ll update this post as I finish polishing my notes (they&#x27;re very messy and I couldn&#x27;t automatically generate a transcription from the video).</p><h2 id="heading-03981518312666239-04646361545292279"><a href="#heading-03981518312666239-04646361545292279" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Resources mentioned in the talk &amp; more</h2><ul><li>Tools for testing<ul><li><a href="https://developers.google.com/web/tools/lighthouse/">Google Lighthouse</a></li><li><a href="https://webpagetest.org/">WebPageTest</a></li><li><a href="https://tools.pingdom.com/">Pingdom tools</a></li><li><a href="https://developers.google.com/speed/pagespeed/insights/">Google PageSpeed Insights</a> - runs Google Lighthouse and provides real world data through <a href="https://developers.google.com/web/tools/chrome-user-experience-report/">Chrome User Experience Report</a></li></ul></li><li>Why care about performance<ul><li><a href="https://developers.google.com/web/fundamentals/performance/why-performance-matters">Why Performance Matters</a>, by Google Developers</li><li><a href="https://gatsby-starter-low-tech-blog.netlify.app/low-tech/">Gatsby Low Tech Starter</a>, by Mathieu Dutour</li><li><a href="https://blog.stephaniestimac.com/posts/10-30-2019-performance/">Location, Privilege and Performant Websites</a>, by Stephanie Stimac</li></ul></li><li><a href="https://github.com/kaordica/gatsby-sites-benchmarks">Gatsby performance benchmarks</a> - 922 Gatsby sites tested with <a href="https://developers.google.com/web/tools/lighthouse/">Google Lighthouse</a></li><li><a href="https://sia.codes/posts/making-google-fonts-faster/">Making Google Fonts faster</a> by Sia Karamalegos</li><li><a href="https://medium.com/srmkzilla/create-a-medium-like-lazy-image-loading-effect-using-gatsby-js-in-5-minutes-f6e5d2488c56">Create a Medium like Lazy Image Loading Effect Using Gatsby.js in 5 minutes</a> by Srey Sachdev</li><li><a href="https://scrimba.com/p/pRB9Hw/c4G7qPsV">Lazy loading content with useInView hook and IntersectionObserver</a> (screencast)</li><li><a href="https://v8.dev/blog/cost-of-javascript-2019">The cost of Javascript in 2018</a></li><li><a href="https://bundlephobia.com/">BundlePhobia</a></li><li><a href="https://hdoro.dev/speed-up-gatsby-site/">Speed up your Gatsby site with 1 line of code 🤯</a></li><li><a href="https://www.gatsbyjs.org/packages/gatsby-plugin-preact">gatsby-plugin-preact on Gatsbyjs.org</a></li><li><a href="https://github.com/hdorodev/gatsby-recipe-preact">Gatsby recipe to implement gatsby-plugin-preact automatically</a></li><li><a href="http://shouldiuseacarousel.com/">Should I Use a Carousel</a></li><li><a href="https://scrimba.com/p/pRB9Hw/cpqd9rta">Creating a simple slideshow / carousel app with React hooks 🥑</a></li><li><a href="https://calendar.perfplanet.com/2019/the-unseen-performance-costs-of-css-in-js-in-react-apps/">The unseen performance costs of modern CSS-in-JS libraries in React apps</a></li><li><a href="https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html">Introducing the React Profiler</a></li><li>Compiled CSS-in-JS<ul><li><a href="https://github.com/callstack/linaria">callstack/linaria</a></li><li><a href="https://github.com/atlassian-labs/compiled-css-in-js">atlassian-labs/compiled-css-in-js</a></li></ul></li><li>CSS-in-JS evolution<ul><li><a href="https://github.com/thysultan/stylis.js/releases/tag/v4.0.0">Stylis v4</a></li><li><a href="https://theme-ui.com/">Theme UI</a></li><li><a href="https://medium.com/styled-components/announcing-styled-components-v5-beast-mode-389747abd987">styled-component v5 achievements</a></li><li>Smart atomic CSS generation with new libraries such as FB’s stylex (not yet released, see <a href="https://youtu.be/WxPtYJRjLL0?t=1712">this video, minute 28</a>)</li><li><a href="https://blog.annamalai.me/posts/new-age-css-in-js/">Exploring the new age of CSS-in-JS</a> by Annamalai Saravanan</li></ul></li><li>Gatsby&#x27;s content duplication issues<ul><li><a href="https://twitter.com/devongovett/status/1222953655722110981">This thread on Twitter by Devon Govett</a></li><li><a href="https://www.gatsbyjs.org/blog/2020-01-30-why-gatsby-is-better-with-javascript/#performance">Why Gatsby is better with Javascript (note on performance)</a>, by Mikhail Novikov</li></ul></li></ul><p>Oh, and if you have 3 minutes to spare, you can help by <a href="https://docs.google.com/forms/d/e/1FAIpQLSceh0nFVMZX5u6OV5eu6fTOMWumDhmfwFxece_8bTfqgK3eqg/viewform">giving some feedback</a>, this was my first talk ever 😊</p></div>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Speed up your Gatsby site with 1 line of code 🤯]]></title>
            <link>https://hdoro.dev/speed-up-gatsby-site</link>
            <guid>https://hdoro.dev/speed-up-gatsby-site</guid>
            <pubDate>Sat, 12 Oct 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Make your site friendlier and faster by reducing Javascript with a simple trick 🐱‍👤]]></description>
            <content:encoded><![CDATA[<div class=""><h2 id="heading-04735144270193068-07010539427378761"><a href="#heading-04735144270193068-07010539427378761" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a><strong>TL;DR</strong>:</h2><p><a href="https://gatsbyjs.org">Gatsby</a> relies heavily on Javascript through the dependence on <a href="https://reactjs.org">React</a> and the addition of a router and other logic to its runtime.</p><p>Hence, one could argue it&#x27;s <strong>quite <em>bloated</em></strong> for simple static sites, delivering tons of JS that users wouldn&#x27;t otherwise need.</p><p>By changing the rendering engine from React to <a href="https://preactjs.com/">Preact</a> we can reduce up to <strong>100kb</strong> (unzipped) in bundle size, making our sites faster on mobile (runtime and loading) and more suited for SEO 🎉</p><p>And I finish with a bonus tip at the end, check it out 😉</p><h2 id="heading-023946941800775035-09364019473940368"><a href="#heading-023946941800775035-09364019473940368" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>The problem</h2><p><strong>Gatsby is great</strong>, and I <em>mean</em> it. It has allowed me to build complex sites with ease, such as <a href="https://datahackers.com.br/">Data Hackers</a>, and unlock the creative freedom provided by building from scratch without the colossal work needed to build the basis for it, as there are many plugins and great guides out there. Also, it&#x27;s just React with the added benefit of not having to care about webpack and the likes, so it&#x27;s quite easy to get productive. In short, it has <strong>amazing DX</strong>, and it has brought me tons of joy since I started working with it in 2017.</p><p>What is <em>not</em> so amazing is its performance. Yes, I&#x27;m challenging their homepage slogan.</p><div class=" lazy-img" data-alt="Shot from the Gatsby homepage featuring the phrase &quot;Fast in every way that matters&quot;" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:2.8290155440414506"><div style="width:100%;padding-bottom:35.34798534798535%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/36e6c5540866c68738083e79f70ad171b78a7709-1092x386.png?w=1300&amp;fit=max&amp;auto=format" alt="Shot from the Gatsby homepage featuring the phrase &quot;Fast in every way that matters&quot;" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><p>First, <em>who even defines what &quot;matters&quot;</em>?! Second, it likes to pride itself in being &quot;blazing fast&quot;, that it provides speed by default, to which I agree to a bunch of cases, but not all of them. For a simplistic blog like the one you&#x27;re reading, <strong>Gatsby actually <em>hurts</em></strong> its performance. I&#x27;d have to do a terrible job to ship <code>70.9kb</code> of downloadable Javascript (<code>219kb</code> uncompressed) on this very barebones page, and yet, that&#x27;s what &quot;fast in every way that matters&quot; brings to the table.</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:2.787375415282392"><div style="width:100%;padding-bottom:35.87604290822408%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/5151b607dd8324c04a2f24b9eb0fd4c4fb66adb2-1678x602.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Screenshot of Chrome&#x27;s Network tab showing 70.9kb of downloaded JS for an article in this blog</figcaption></figure><p>I have a <em>huge</em> collection of thoughts on this and have been delaying a post on the shortcomings and threats of Gatsby for a while, but that will have to wait longer. To finish driving my point home, take a look at my <a href="https://developers.google.com/web/tools/lighthouse/">Lighthouse</a> score - measured on applied slow 4G for mobile - for the default Gatsby mode:</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:2.0344827586206895"><div style="width:100%;padding-bottom:49.152542372881356%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/046c61bd23671771ac0354100abd14b47342b9f9-767x377.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Google Lighthouse test on the original Gatsby site using react, showing 340ms as &quot;max potential first input delay&quot;</figcaption></figure><p>And keep in mind that <strong>the document itself is only 10.8kb</strong>, including styles, so a TTI of 2.8s for such a simple page is unacceptable (for me, at least 😜). If you want to see it for yourself, <a href="https://5da1a86085827fa3493abee4--henrique-codes.netlify.com/learned-reading-google-search-documentation">here&#x27;s this version of the page</a>.</p><h2 id="heading-04433763405733615-02923311076292403"><a href="#heading-04433763405733615-02923311076292403" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a><strong>The solution</strong>: reduce JS through Preact</h2><p>Turns out <a href="https://github.com/preactjs/preact/releases/tag/10.0.0">Preact X just came out</a> and, with it, came stability and reliability enough to ditch React for Preact as the rendering engine and benefit from the reduced JS. They say a picture is worth a 1000 words, so I&#x27;ll give you two:</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:2.862809917355372"><div style="width:100%;padding-bottom:34.930715935334874%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/bf21dc5cde8ad5c3a0440feb608fff9d4954e091-1732x605.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Screenshot of the Network tab showing a JS download size of 40.4kb</figcaption></figure><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:2.236714975845411"><div style="width:100%;padding-bottom:44.708423326133904%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/39b2cfb60d163385cf4af1e77375e90bdb60e5e3-1389x621.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Lighthouse score of 99 with &quot;Max Potential First Input Delay&quot; of 170ms</figcaption></figure><p>Now, that&#x27;s better! Still a bit disheartening considering the simplicity of the page (which you can <a href="https://5cf2ba38b98de9000819085f--henrique-codes.netlify.com/learned-reading-google-search-documentation">find here</a>) and the fact that a few months ago <a href="https://twitter.com/hdorodev/status/1134881110587072512">this result was considerably better</a>. As Lighthouse is changing its metrics and weight attributes over time (see the <a href="https://docs.google.com/spreadsheets/d/1dXH-bXX3gxqqpD1f7rp6ImSOhobsT1gn_GQ2fGZp8UU/edit?ts=59fb61d2#gid=0">weights for V2</a> and compare them to the <a href="https://docs.google.com/spreadsheets/d/1Cxzhy5ecqJCucdf1M0iOzM8mIxNc7mmx107o5nj38Eo/edit#gid=0">weights for V3</a>), seemingly putting more emphasis on aspects that are highly affected by JS heavy set-ups, I expect <strong>Gatsby sites to perform worse and worse over time</strong>.</p><p>Anyway, it&#x27;s still a good gain that becomes more expressive on bigger sites, and it comes at no cost / collateral, so <strong>switching to Preact is an obvious choice</strong>. I&#x27;m not sure if you&#x27;d hit incompatibilities on big apps that make use of other libraries such as Apollo, etc., but in my experience I haven&#x27;t seen a single site with a problem so far. And to do it, the process is <em>super straightforward</em> and simple (alternatively, you can follow the plugin&#x27;s official docs)!</p><div class="code-block" data-explanation="false"><pre class="language-sh">npm i gatsby-plugin-preact preact</pre></div><p>And then, in your <code>gatsby-config.js</code>:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">/* ... */</span> <span class="token literal-property property">plugins</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token comment">/* ... */</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">gatsby-plugin-preact</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /></pre></div><p>There you go! 1 <code>npm install</code> and 1 line of code later, and <strong>you&#x27;ve saved 45.2% of uncompressed JS</strong> (43.02% for of the download size) 🎉🎉</p><p>Do yourself, and the internet / users / planet, a favor and do that for <em>every Gatsby site you ever make</em>! Heck, you can even bake it into your default base theme / starter, easy peasy!</p><h2 id="heading-02703427991233205-08471780104838458"><a href="#heading-02703427991233205-08471780104838458" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a><strong>Bonus tip:</strong> remove <em>ALL</em> Javascript 😈</h2><p>Now, if you&#x27;re building a simple website / blog that doesn&#x27;t benefit from the SPA aspects of Gatsby and doesn&#x27;t use any dynamic features of React, like my blog and many others I&#x27;ve seen out there, you don&#x27;t need to cope with the tons of JS nor ditch Gatsby&#x27;s data layer and templating niceties... you can <strong>remove <em>all</em> Javascript from your site</strong>!</p><p>This one is obviously not for every situation, but can make absolute sense if the average user of the site is not going to navigate to tens of pages and always come back, case in which having Gatsby&#x27;s caching and service worker capabilities would be great. However, I&#x27;m more than happy to do this tradeoff on my own blog, and here are the results:</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:3.5705128205128207"><div style="width:100%;padding-bottom:28.00718132854578%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/3b2926e6c49e6875f9b9e34a0c1343f37b375b21-1671x468.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Screenshot of the Network tab with only 17.7kb being transferred in total</figcaption></figure><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:2.4662998624484183"><div style="width:100%;padding-bottom:40.546569994422754%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/09e1ca606b4834dc3bba7f570e036787f1b15932-1793x727.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Lighthouse result of 100 with &quot;Max Potential First Input Delay&quot; of 20ms. My site is now faster than 99% of the whole web 🥳</figcaption></figure><p>They speak to themselves, but they could be even better if the <a href="https://github.com/itmayziii/gatsby-plugin-no-javascript"><code>gatsby-plugin-no-javascript</code></a> - which I used to remove JS from the site - also removed the <code>page-data.json</code> file, which increases my payload by 70,2% for no reason, as it only makes sense in the context of hydrating the app.</p><p>This is achieved with a simple <code>yarn add gatsby-plugin-no-javascript</code> and adding the following to your <code>gatsby-config.js</code>:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">/* ... */</span> <span class="token literal-property property">plugins</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token comment">/* ... */</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">gatsby-plugin-no-javascript</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">]</span><span class="token punctuation">;</span><br /></pre></div><p><strong>⚠ Note:</strong> plugins related to your hosting provider - specifically, I ran into it with Netlify - can add some specific headers to you requests that point to JS files. Make sure to delete them to avoid this behavior 😉</p><p><strong>In conclusion,</strong> Users will now save more of their bandwidth, load your site faster and feel less the effects of JS bloat (high input delay, janky scrolling, etc.). As web devs, we must be more and more vigilant to the impacts of our choices for businesses and users. Serving unnecessary JS might be comfortable for us, but it&#x27;s bad for everyone, even the planet (think about the bandwidth, and consequent electric energy, being wasted on the internet!). <strong>We can&#x27;t blind our sights to this in name of UX</strong>!</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[What I learned from reading Google Search's documentation]]></title>
            <link>https://hdoro.dev/learned-reading-google-search-documentation</link>
            <guid>https://hdoro.dev/learned-reading-google-search-documentation</guid>
            <pubDate>Sat, 01 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Reading through Google Search's developer documentation really shone a light on topics that were a bit fuzzy on my head. It also equipped me with better technical SEO skills 😉]]></description>
            <content:encoded><![CDATA[<div class=""><p>This is a compilation of information and thoughts I gathered from reading <a href="https://developers.google.com/search/docs/guides/get-started">Google Search guides</a>, from the perspective of a web developer / designer. In the end of this article, I&#x27;ve prepared a TODO to go through when delivering a website, which might be helpful to you 😉</p><p><strong>⚠ Please note:</strong> this is mostly written for myself, it&#x27;s by no means an introductory and comprehensive guide. If you&#x27;re new to SEO or want something more exhaustive, you should read the real deal!</p><h2 id="heading-01645746747948682-08216323903100153"><a href="#heading-01645746747948682-08216323903100153" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a><strong>Annotations on Google&#x27;s documentation on SEO</strong></h2><h3 id="heading-048229738930807264-0757038274638353"><a href="#heading-048229738930807264-0757038274638353" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a><strong>General notes</strong></h3><ul><li>By putting your users first and caring about the accessibility of the content, you&#x27;re already checking most of the boxes</li><li>They don&#x27;t talk much about speed here, which I found very weird 🤔</li><li>Google indexing is <a href="https://developers.google.com/search/mobile-sites/mobile-first-indexing">becoming mobile first</a>, starting July 1st 2019. This means that it&#x27;s most important than ever to optimize your site for mobile if you want to up your SEO game.</li><li>You can ping Google&#x27;s servers with a sitemap URL for re-indexing<ul><li>Example: [<code>http://www.google.com/ping?sitemap=https://hdoro.dev](http://www.google.com/ping?sitemap=URL/of/file)/sitemap.xml</code></li></ul></li><li>Google is launching &quot;miniapps&quot;, a new set of rich results that <em>we</em> can create and connect to our own API endpoints, seems really cool!<ul><li>Check out the <a href="https://www.youtube.com/watch?v=0Hyt7gjHYO4">presentation at Google I/O &#x27;19</a> on it</li><li>I&#x27;m thinking it&#x27;d be great for <a href="https://www.gororobas.com">my recipes blog</a> as I could search by ingredients directly in the search results 🤔</li></ul></li></ul><h3 id="heading-03498352573956871-046310569816616143"><a href="#heading-03498352573956871-046310569816616143" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Stuff that might hurt SEO</h3><ul><li><a href="https://support.google.com/webmasters/answer/2721311">Doorway pages</a> might hurt SEO. Gotta talk with Thiago about that</li><li><a href="https://support.google.com/webmasters/answer/66355">Cloaking</a> (presenting a different version of the page for users and search engines) is an absolutely no-no</li><li>Hiding text and links (like a hidden sidebar) can be tricky<ul><li><strong>TODO to myself:</strong> search and write about accessible and SEO-friendly off-screen menus</li></ul></li><li>If working with affiliate networks, make sure to read <a href="https://support.google.com/webmasters/answer/76465?hl=en&amp;ref_topic=6001971">this article on it</a>, as having duplicate content across the network could really hurt your ranking</li><li>Based on the <a href="https://support.google.com/webmasters/answer/66358?hl=en&amp;ref_topic=6001971">irrelevant keywords article</a>, it seems like Google is smart enough to detect when people are doing &quot;keyword stuffing&quot;. So, focus on making a good flow of content, not forcing the amount of keywords in a sentence</li><li>There are others, of course, be sure to check the <a href="https://support.google.com/webmasters/topic/6001971?hl=en&amp;ref_topic=6001981">Quality Guidelines</a></li></ul><h3 id="heading-07566562507125469-05318408255887834"><a href="#heading-07566562507125469-05318408255887834" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>The AMP problem</h3><p>Unfortunately, Google is <em>really</em> pushing for AMP, and there are huge chunks dedicated solely to it 😔 (tiny rant below)</p><ul><li>I see the AMP project as <strong>Google&#x27;s effort to further its hegemony on the web</strong>, so I&#x27;m definitely worried about this, as publishers feel obliged to comply with their specifications in order to keep relevant in search;</li><li>There&#x27;s also the verified-look it gives to fake news, losing your brand&#x27;s signature, handing content hosting to Google, etc.</li><li>If fact, there are so many problems, you can dive deep in articles such as <a href="https://www.theregister.co.uk/2017/05/19/open_source_insider_google_amp_bad_bad_bad/">Kill AMP before it kills the web</a>, <a href="https://80x24.net/post/the-problem-with-amp/">The problem with AMP</a> and even <a href="https://shkspr.mobi/blog/2019/05/a-report-from-the-amp-advisory-committee-meeting/">A report from the AMP Advisory Committee Meeting</a>.</li><li>I do like the idea of AMP Story, the richness of AMP Ads and the possibilities of AMP email, but I&#x27;d love scraping the project even more!</li></ul><h3 id="heading-03748857421199723-014866157637712774"><a href="#heading-03748857421199723-014866157637712774" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Javascript and Googlebot</h3><p>If you <strong>use too much Javascript</strong> on your site(s), you should learn more about its relationship to the Googlebot:</p><ul><li>Googlebot always declines user permission, so never count on them for critical content</li><li>It doesn&#x27;t support localStorage and always clears cookies</li><li>Do some sort of feature detection and polyfill stuff that it doesn&#x27;t support<ul><li>See <a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">Feature Detection on MDN</a></li></ul></li><li>Lazy-loading components should load when scrolled into the viewport with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">Insersection Observer API</a> and a polyfill<ul><li>Although this is becoming much easier with the <a href="https://addyosmani.com/blog/lazy-loading/">native browser lazy loading API</a> 😍</li></ul></li><li>Be careful with infinite-loading sections, as they should have some sort of history and pagination to allow for linking and better crawling. If you don&#x27;t do so, you&#x27;d better just provide regular pagination else you&#x27;ll miss out a bunch on SEO. There are probably ready-made components that do so, though 🤔</li><li>There&#x27;s a concept of <a href="https://developers.google.com/search/docs/guides/dynamic-rendering">dynamic rendering</a> for websites that rely on JS features that crawlers don&#x27;t support. However, with <a href="https://www.searchenginejournal.com/google-will-keep-googlebot-up-to-date-with-the-latest-version-of-chrome/306715/">Googlebot using the latest version of Chrome</a>, I don&#x27;t feel this is relevant for me. If other search engines are very important to you, you might want to check this out!</li></ul><h3 id="heading-09852558438329448-0313536043730537"><a href="#heading-09852558438329448-0313536043730537" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Structured data and rich results</h3><ul><li>They give a <strong>BIG</strong> emphasis to structured data<ul><li>This makes sense, as <a href="https://schema.org">Schema</a> really helps linking and interpreting content across the web, which is super important for search engines.</li><li>Specifically for Google, it&#x27;s what allows for rich results such as recipes breakdowns.</li></ul></li><li>If you want to learn more about it, I have an introductory article on <a href="https://hdoro.dev/structuring-linking-data">&quot;Structuring and linking data in webpages: why care and how-to&quot;</a></li><li>&quot;Google recommends using JSON-LD for structured data whenever possible.&quot; (<a href="https://www.notion.so/kaordica/What-I-learned-from-reading-Google-Search-s-documentation-68f9e5bef35d475fb99402f0d7734752https://developers.google.com/search/docs/guides/intro-structured-data">source</a>) → plus, using Microdata and RDFa is a bit messy</li><li>Google explicitly recommends to use fewer, but more accurate properties in your data, than many incomplete or inconsistent ones.</li><li>Structured data <em>must</em> be in the content of the website, and not hidden from users, else Google might ignore it</li><li>For list pages, follow <a href="https://developers.google.com/search/docs/guides/mark-up-listings">this reference on Carousels</a></li><li>&quot;BlogPosting&quot;, &quot;TechArticle&quot; and others are more specific types of &quot;Article&quot;, so I believe that, for Google, it doesn&#x27;t seem to make a difference.</li><li>Choose the most specific types and properties possible from the <a href="http://schema.org">Schema.org</a> definitions</li><li>Be sure to check out the <a href="https://developers.google.com/search/docs/guides/search-gallery">search gallery</a> for rich result options and follow the ones you want to add. The definitions there may overwrite schema.org&#x27;s, and that&#x27;s okay, go with Google 😉</li><li>&quot;You can include multiple structured data objects on a page, as long as they describe user-visible page content. However, if you mark up one item in a list you must mark up all items&quot; (<a href="https://developers.google.com/search/docs/guides/sd-policies">source</a>)<ul><li>For multiple objects in JSON-LD, you can either use a <code>@graph</code> object in a single script, or using multiple <code>script</code> see more on <a href="https://stackoverflow.com/questions/30505796/json-ld-schema-org-multiple-video-image-page/30506476#30506476">Stack Overflow</a>.</li></ul></li><li>The only way to create a knowledge graph card on your site/company is through <a href="https://www.google.com/business">Google My Business</a></li><li>There&#x27;s an &quot;Action&quot; markup for structured data brewing that will allow users to take specific action straight from search. This doesn&#x27;t interest me as of now, so I skipped this section.</li><li>It&#x27;s probably best to check <a href="https://developers.google.com/search/reference/overview">Google&#x27;s reference on structured data</a> than to browse endlessly through <a href="http://schema.org">Schema.org</a> in search for what types and properties to use, as many of those listed in Schema aren&#x27;t supported by Google. Oh, and the UX for the Schema site is horrible 😝</li><li>Some properties like reviews can make for <em>really huge</em> JSON objects, so I want to try adding them on client-side with JS and see if it works and it&#x27;s worth the trade-off.<ul><li>However, this approach could increase computations and slow down the experience on mobile</li></ul></li><li><code>image</code> properties have a bunch of specificities:<ul><li>must be at least 696px wide, in the formats .png, .jpg or .gif;</li><li>can be an array of strings, Google will pick accordingly;</li><li>crawlable and indexable;</li><li>image must represent the content. Not sure if this means it should be inside of the content or if Google analyzes its content 🤔</li></ul></li></ul><h3 id="heading-024964785908080311-027674989888794554"><a href="#heading-024964785908080311-027674989888794554" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Further resources</h3><ul><li><a href="https://jsonld.com/article/">jsonld.com</a> provides a bunch of ready-made Schema definitions in the JSON-LD format</li><li><a href="https://json-ld.org/playground/">json-ld.org/playground/</a> is great for testing your schema declarations</li><li><a href="https://developers.google.com/search/">Google Search for developers</a> concentrates a bunch of useful stuff for us</li><li><a href="https://codelabs.developers.google.com/codelabs/structured-data/index.html#0">Structured Data Codelab</a> a step-by-step tutorial by Google to learn how to use structured data. Pretty basic, but good enough to et started.</li><li>There&#x27;s a <a href="https://productforums.google.com/forum/#!forum/webmasters">Google Webmasters Forum</a> and a <a href="https://webmasters.stackexchange.com/">Webmasters StackExchange</a> where you can probably answer specific questions on structure data</li><li>SchemaApp <a href="https://www.schemaapp.com/tools/jsonld-schema-generator/">JSON-LD Schema Generator</a> doesn&#x27;t have the best user experience, but is still much better than browsing Schema.org endlessly<ul><li>This one is super complete as it fetches data from Schema&#x27;s API, so it&#x27;s always fresh</li></ul></li></ul><h3 id="heading-07148061218978718-06036330193389303"><a href="#heading-07148061218978718-06036330193389303" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Duplicate content, canonical, and the likes</h3><ul><li>I finally understood what is a <em>canonical</em>: in case you have multiple variants of the same content in different addresses (say, you write a post on your blog and on Medium), the canonical is the main source, which Google will crawl more often. The rest are the <em>duplicate</em> URLs</li><li>They are generated automatically if on the same domain</li><li>Reasons for using a canonical include specifying which URL you want to show-up on search, maximizing SEO and simplifying analytics</li><li>Even when you set a specific canonical URL, Google can ultimately choose another one based on performance, content or others.</li><li>For www / non-www versions, you should go to Google Search Console and set which one is the preferred. There&#x27;s no need for doing this with http / https versions, Google favors https by default.</li><li>If using hreflang for multi-language sites, you must specify a canonical URL (not sure how, though)</li><li>You can tell Google to ignore dynamic parameters (ex: <code>example.com/blog?order=ASC</code>). If needed, read this piece on <a href="https://support.google.com/webmasters/answer/6080548?visit_id=636949829039249864-59312627&amp;rd=1">Block crawling of parameterized duplicate content</a></li><li>There are many other recommendations to follow and methods to apply it in <a href="https://support.google.com/webmasters/answer/139066">Consolidate duplicate URLs</a></li></ul><h3 id="heading-05843892461359972-08112906725600253"><a href="#heading-05843892461359972-08112906725600253" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>On multi-language / region websites</h3><ul><li>You can use <code>&lt;link hreflang=&quot;...&quot;</code> tags, but this can increase the size of the HTML and be hard to manage.</li><li>You can go with HTTP headers as well, but these tends to be hard to implement for those without much knowledge on servers (myself included).</li><li>So a better alternative seems to be creating language-specific sitemaps</li><li>See the article on <a href="https://support.google.com/webmasters/answer/189077">&quot;Tell Google about localized versions of your page&quot;</a> for implementation details</li></ul><h2 id="heading-022519974388789143-016973598333120954"><a href="#heading-022519974388789143-016973598333120954" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>TODO when delivering a seo-ready website</h2><ul><li>[ ] JSON-LD structured data with Schema definitions<ul><li>[ ] Use Google&#x27;s <a href="https://search.google.com/structured-data/testing-tool/u/0/">Structure Data Testing Tool</a></li></ul></li><li>[ ] Validate the HTML with <a href="https://validator.w3.org/">W3C&#x27;s Markup Validation Service</a></li><li>[ ] Run the site through a screen reader to look for accessibility bottle-necks</li><li>[ ] Test the site with <a href="https://developers.google.com/web/tools/lighthouse/">Google Lighthouse</a>, <a href="https://tools.pingdom.com">Pingdom</a> and <a href="https://www.webpagetest.org/">WebPageTest.org</a></li><li>[ ] Use <a href="http://appetize.io">appetize.io</a> (or similar, such as <a href="https://browserstack.com">BrowserStack</a>) to test the site on other devices</li><li>[ ] On Google Search Console:<ul><li>[ ] if applicable, test the <code>robots.txt</code> file with <a href="https://www.google.com/webmasters/tools/robots-testing-tool">robots.txt tester</a></li><li>[ ] create a <code>sitemap.xml</code> and test it with <a href="https://search.google.com/search-console/sitemaps">the Sitemaps panel</a></li><li>[ ] use the URL inspection tool to see how it shows up on Googlebot<ul><li>[ ] Be sure to check out the mobile-friendly test as well</li></ul></li><li>[ ] <a href="https://support.google.com/webmasters/answer/44231">Set your preferred domain (www or non-www)</a></li><li>[ ] if using rich results, take a look at <a href="https://support.google.com/webmasters/answer/7552505">&quot;Rich result status reports&quot;</a> after the site is indexed</li></ul></li><li>[ ] If you want to dive deeper, consider going through <a href="https://frontendchecklist.io/">FrontEndChecklist.io</a></li></ul></div>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[StencilJS: the compiler UI framework to keep in your radar]]></title>
            <link>https://hdoro.dev/stenciljs-the-compiler-ui-framework</link>
            <guid>https://hdoro.dev/stenciljs-the-compiler-ui-framework</guid>
            <pubDate>Tue, 12 Mar 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[StencilJS has the potential to trigger a change in front-end development... Or at least I believe so 😁]]></description>
            <content:encoded><![CDATA[<div class=""><p>The following article is dedicated to share some thoughts on Stencil - an interesting compiler / framework / framework compiler that could have an impact in our industry.</p><p><strong>⚠ Please note:</strong> I&#x27;ve only scratched the surface of Stencil and I bet there&#x27;s much to it that I don&#x27;t know and that might change my perception, so feel free to elucidate me, feedback is always welcome 😄</p><h2 id="heading-016805218999502958-0916852347885061"><a href="#heading-016805218999502958-0916852347885061" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>TL;DR</h2><ul><li>Stencil is a mash-up of front-end frameworks (namely React, Vue and Angular) that compiles your schwifty code to web components;</li><li>Its main focus is for creating isolated components that work everywhere, but it can also be used as a full-blown framework from project set-up to testing;</li><li>Coming from React, I found the developing experience (DX) to be quite satisfying, but the software can still unstable for production work, although there are plenty of apps out there;</li><li><strong>If I&#x27;d found Stencil a year ago, I&#x27;d consider abandoning React for it</strong> as it has some very cool features and abstractions, and ships super tiny bundles, but with current Hooks and Suspense development, I&#x27;ll probably use it just for fiddling around with side projects;</li><li>Nevertheless, Stencil should be in your radar as it might help steer the JS framework community towards more compiler-based alternatives</li></ul><h2 id="heading-012219603175443039-026525794128327496"><a href="#heading-012219603175443039-026525794128327496" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>What is StencilJS</h2><blockquote></blockquote><p>In short, Stencil is a tool that compiles your react/angular-like typescript code into web components that leverage the browsers&#x27; native APIs. It has quite a lot of cool stuff packed into it, such as the generation of multiple bundles for different browsers (ES5 for IE11 and the likes vs. modern ES6+ JS), automatic code splitting and component lazy-loading, but it shines on being <strong>framework agnostic</strong>. Essentially, you can use Stencil components anywhere on the web, from <code>insert your framework here</code> apps to vanilla JS projects, and that&#x27;s <em>exactly</em> why the Ionic team built it.</p><p>You should read more about it on <a href="https://stenciljs.com/">their site</a> and watch its announcement video, but, in essence, its main goal is to provide a way for Ionic 4 components to become usable in any front-end project, eliminating the need to re-write the UI library for each different stack. However, although Stencil&#x27;s main focus is on compiling reusable components, it can also be used as a fully-fledge framework to build entire apps, and that&#x27;s the main usage I&#x27;ll cover in this article.</p><h2 id="heading-006883479436457307-06051706017171861"><a href="#heading-006883479436457307-06051706017171861" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Why it might matter to you</h2><p>If you&#x27;re building a design system for a large company with several domains, apps, teams and stacks, the benefit of Stencil is obvious: <strong>write your components once and have them work for <em>everyone</em>, <em>everywhere</em></strong>. This also applies to UI libraries: instead of having <code>material-ui</code> for React and <code>angular-material</code> for, you guessed it, Angular, we could unite efforts and resources into creating a single, universal library.</p><p>If, however, you&#x27;re building full apps and are looking for a framework, StencilJS is also an option, especially if you need to deliver tiny packages to save users&#x27; bandwidth, as it excels in that regard. <a href="https://stenciljs.com/docs/introduction">Stencil&#x27;s docs</a> are quite tiny and the software has a bunch of interesting decisions and features, so I suggest you take a look into it (should take about 1hr).</p><p>In case reading the docs is not in your plans, just know that Stencil is highly dependent upon Typescript, it uses decorators similar to Angular as well as JSX from React, and plugging in components in <code>.html</code> templates feels a bit like Vue (as far as I&#x27;m concerned). Below is a super simple sample component:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@stencil/core'</span><span class="token punctuation">;</span>

<span class="token comment">// If I wanted to isolate the CSS applied to this component from the rest of</span>
<span class="token comment">// the stylesheet, I could use `shadow: true` in the @Component options ;)</span>
@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">tag</span><span class="token operator">:</span> <span class="token string">'loading-spinner'</span><span class="token punctuation">,</span>
  <span class="token literal-property property">styleUrl</span><span class="token operator">:</span> <span class="token string">'loading-spinner.css'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">LoadingSpinner</span> <span class="token punctuation">{</span>
  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"loader"</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><br /></pre></div><p>Then, when calling it inside another stateful component, I&#x27;d use:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token comment">// ...</span>
  <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>isLoading<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token operator">&lt;</span>loading<span class="token operator">-</span>spinner <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token comment">// ...</span><br /></pre></div><p>Quite similar to React, huh? <strong>If this doesn&#x27;t appeal to your style, however, Stencil is still relevant to you</strong>. Together with <a href="https://svelte.technology/">Svelte</a>, <a href="http://imba.io/">Imba</a>, <a href="https://glimmerjs.com/">Glimmer</a>, and even Angular with its <a href="https://blog.angular.io/a-plan-for-version-8-0-and-ivy-b3318dfc19f7">Ivy project</a>, Stencil is riding the <em>compiler wave</em> in which frameworks focus more on build-time transformations in order to ship smaller and more performant code to the browser.</p><p>Personally, I&#x27;m super excited with this trend and believe it has the potential to dramatically change user experience on the web. In fact, I&#x27;m <em>already</em> benefiting from this concept at <a href="https://kaordica.design">my agency</a> with <a href="https://gatsbyjs.org">Gatsby</a>, a static site/app generator that has been improving quite a lot since I began using it in 2017: simply by upgrading my dependencies for older sites I can see performance improvements.</p><p>Eventually, if Web Assembly reaches a point in which it can access the DOM in a performant way, then compilers can shift gears and focus solely on WASM, which could yield amazing returns! So, long story short, <strong>keep Stencil on your radar</strong>. It&#x27;s backed by Ionic and for such I bet it&#x27;s coming to last, and may help steer the whole front-end community towards native browser APIs such as web components, and towards this fancy compiler movement.</p><h2 id="heading-02771834842958574-05335991041347701"><a href="#heading-02771834842958574-05335991041347701" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>My experience with Stencil</h2><p>Context set, let&#x27;s get our feet wet and actually talk about Stencil for real apps. You might have heard of the <a href="https://realworld.io">RealWorld project</a> in which people build the exact same app (a Medium-like clone) with different tools, be it for the front-end or the API. I&#x27;d been enamoring their repo for a while, so when I figured Stencil was worth the shot, it was an easy pick for testing the new framework.</p><p>So, in the pursuit of knowledge and a spot at the bleeding edge of front-end development, I created <a href="https://github.com/hdorodev/stencil-realworld-app">Stencil&#x27;s RealWorld example app</a>, which you can find <a href="https://stencil-realworld.netlify.com/">live here</a>. I&#x27;ve annotated my experience while I was at it, and summarized everything below 😉</p><h3 id="heading-06409526492854671-033838286197612844"><a href="#heading-06409526492854671-033838286197612844" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Onboarding</h3><p>As I put before, the Stencil way of working is quite similar to React, and for such its learning curve was super easy and quick to grasp... It also helps the fact that their docs are quite clear straight to the point, although it still contains some holes. Knowing Ionic&#x27;s documentation, I trust Stencil&#x27;s will become awesome over time when things mature.</p><p>There&#x27;s also a community on Slack, although I didn&#x27;t have much to say there and when I did no one paid much attention 😔 hahaha</p><h3 id="heading-01550946038221659-0998172379783663"><a href="#heading-01550946038221659-0998172379783663" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Developer Experience (DX)</h3><p>Below is a bunch of stuff hard to imagine if you&#x27;ve never used Stencil, and I was a bit lazy when deciding not to use examples... sorry for that! Feel free to skip to the next section 😉</p><p><strong>What I love:</strong></p><ul><li>Tight Typescript integration, with type-checking tied to the console;</li><li>Awesome attention to details with effective and fast hot-reloading, nice display of the app&#x27;s status through the page&#x27;s favicon, easy project set-up, CLI downloading dependencies only when needed etc.;</li><li><strong>Automatic</strong> (and effective) <strong>code splitting, period</strong>;</li><li>No need to import components into every file like you do in React: Stencil automatically generates TS types for each component in the <code>src</code> folder and allow you to insert them into JSX with automatic type assertion;</li><li><code>npm run start</code> is quite fast when compared to Create React App and Gatsby</li><li>You don&#x27;t have stateful vs. stateless components differentiation like in React, which is quite positive as you can simply add a <code>@State</code> decorator when you want to use it<ul><li>With React Hooks, however, this becomes less of an advantage haha</li><li>This could be considered a flaw as we&#x27;re adding classes for everything, but I doubt this has real impact on performance</li></ul></li><li>Instead of <code>React.Fragment</code> or <code>&lt;&gt;&lt;/&gt;</code>, if you want to render multiple elements at the root of your component, you just use an array: <code>jsx~return [&lt;hr /&gt;, &lt;button&gt;Hello&lt;/button&gt;]</code><ul><li>The comma between tags can be a hassle if you miss it, be sure to add <code>ts-lint</code> with <code>tslint-stencil</code></li></ul></li><li>This one is both a blessing and a curse: errors on specific components don&#x27;t crash the whole app<ul><li>This is good for isolating bad stuff, but can be bad for debugging</li></ul></li><li>Easy set-up of PWAs</li><li>Selecting components is super easy with the <code>@Element</code> decorator<ul><li>Although you can&#x27;t directly select tags inside the component, you can always use <code>js~this.element.querySelector(&#x27;tag-name&#x27;)</code> or something like that to go down the tree. <a href="https://stenciljs.com/docs/decorators#element-decorator">See the docs for a clearer example</a>.</li></ul></li></ul><p><strong>What could be better:</strong></p><p>The fact that you write components&#x27; class names in <code>PascalCase</code> and their tags as <code>separated-by-hash</code> can be a bit confusing;</p><p>You can only export one thing per <code>.tsx</code> file, which is understandable, as the compiler alerts numerous exports per file could cause inefficient bundling and code splitting... However, I believe Typescript interfaces and types should be exportable as they don&#x27;t get included in the final code (dunno if that&#x27;s feasible, tho 😝)</p><p>When testing, whenever you try to navigate the router goes crazy and the test crashes.</p><ul><li>In fact, the router doesn&#x27;t really integrate well into e2e testing as of <code>@stencil/router 0.3.2</code> and <code>@stencil/core 0.17.0</code>, making it impossible to run any test</li></ul><p>You can&#x27;t <code>ALT + CLICK</code> (or equivalent) on components to go to their corresponding file, instead you&#x27;re sent to its definition file, which is auto-generated by Stencil. A small price to pay for not having to import components, maybe?</p><p><strong>Tooling is still in its infancy</strong>. I had so many hurdles with VS Code that writing code became a bit cumbersome at first, and the currently available plugin options aren&#x27;t quite there yet. I&#x27;m optimistic, though!</p><p>Stencil is missing an alternative to Redux. Although they provide <code>@stencil/state-tunnel</code> (which is similar to React&#x27;s Context), the package is highly unstable as of version <code>0.0.8</code></p><ul><li>In fact, <code>state-tunnel</code> had an unreleased version <code>0.0.9-1</code> that I found not from the npm page, but rather from a code example extracted from one of Stencil&#x27;s repos, and that one actually worked without crashes during compile time...</li><li>Plus, <code>state-tunnel</code> seems to be quite a big package, although that could just be a fault of the Import Cost extension</li><li>I ended up not relying on the unstable API and went for good &#x27;ol prop drilling, passing it down the component tree 😅</li></ul><p><strong>Stuff that might be my own fault:</strong></p><ul><li>I couldn&#x27;t get auto-completion of components&#x27; tags working inside JSX, which is a bummer: it slows down development and makes it more error-prone, as I don&#x27;t get any error for wrongly-typed names;<ul><li>In fact, I spent a good half an hour trying to debug the router when I finally discovered that I had typed it wrong.</li></ul></li><li>When running e2e tests, if you don&#x27;t know what you&#x27;re doing, having to re-run the app every time is a bit cumbersome;</li><li>In JSX, when adding the tag for a component, VS Code&#x27;s auto-complete adds a bunch of useless HTML suggestions, making it hard to find the name/type of props you actually want to use;</li><li>I didn&#x27;t really find a way to have the router redirect to home if the <code>switch</code> doesn&#x27;t find a possible route, so I had to create a <code>not-found</code> component that redirected on render;</li></ul><h3 id="heading-07424174256211191-0016966336126586823"><a href="#heading-07424174256211191-0016966336126586823" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>The end result</h3><p>Even with all the hurdles, I found the experience to be quite enriching, and the end result is quite satisfying:</p><ul><li>It&#x27;s one of tiniest bundles in the RealWorld repository;</li><li>The code is quite clean and easy to understand (at least for me 👀);</li><li>By providing an ES6+ bundle for modern browsers, I don&#x27;t feel like I&#x27;m downgrading my app during compile time;<ul><li>And, for the hopefully tiny slice of my audience that accesses from IE11 and the likes, Stencil has an ES5 bundle filled with polyfills for everything you might need.</li><li>I&#x27;ve tested this behavior and got to ~15% bundle reduction when using Chrome when compared to IE</li></ul></li><li>Typescript integration made my life so much easier and, hopefully, the repository much more maintainable;</li></ul><p>Besides the small caveats presented in the previous sections, my only complaint is that, currently, <strong>Stencil depends on initial parsing of Javascript to decide which bundle to download</strong>, and that culminates in a longer times of white screens before the final app is loaded and poor loading performance on bad connections / slow CPUs 😔</p><h2 id="heading-09579436137101236-02483467175898928"><a href="#heading-09579436137101236-02483467175898928" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Stencil vs. React</h2><p><strong>Note:</strong> This comparison might serve to other frameworks, but I don&#x27;t have relevant experience to say so!</p><p>First of all, let me say it&#x27;s a very smooth transition to make and that you can easily apply Stencil to your next hobby project or maybe create components for production work. Without further ado, let&#x27;s go to pros and cons:</p><ul><li><strong>Pros for Stencil:</strong><ul><li>smaller packages;</li><li>lazy loading and code splitting without lifting a finger;</li><li>e2e tests integrated into an official and easy workflow;</li><li>promise of visual diffing in the future;</li><li>Less lock-in: if you need to change frameworks, you can reutilize part of your Stencil app without many alterations due to its compiler nature;</li><li>No need to import everything all the time and pollute your code with &quot;useless&quot; (not) code;</li><li>Tight Typescript integration and awesome CLI for a great runtime;</li><li>You can supposedly pre-render stuff, though I&#x27;m not sure how.</li></ul></li><li><strong>Cons for Stencil:</strong><ul><li>First load is a bit slow due to relying on initial parse to fetch first bundle;</li><li>Still unstable and will probably break things before <code>1.0.0</code>;</li><li>Won&#x27;t (and can&#x27;t) evolve in React&#x27;s or Angular&#x27;s speed, so not sure how long it can remain competitive;</li><li>React&#x27;s Context, Hooks and Suspense change the game quite a bit, and made developing in React super fun again and much more effective;</li><li>The ecosystem is still close to inexistent;</li><li>And maybe the most worrying: **I&#x27;m not sure developing Stencil has a high priority in Ionic&#x27;s agenda now that they released Ionic 4</li></ul></li></ul><h2 id="heading-09126156106862748-020375455179257385"><a href="#heading-09126156106862748-020375455179257385" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>The future of Stencil</h2><p>If Ionic continues to spend resources on Stencil, I see a <em>very</em> bright future for it. I doubt it will one day reach React or Angular&#x27;s ubiquity or relevance, but it can become quite a powerful and stable option for those looking for frameworks closer to the metal and better aligned to browser APIs.</p><p><strong>More important than Stencil, however, is what it can represent and unlock in the front-end scene</strong>: As put before, bringing optimizations to compile-time is a necessary step into achieving true performant apps that can reach everyone, and with Stencil&#x27;s partial lead, the rest can follow.</p><p>For the time being, however, it&#x27;s a fun tool to play, with great abstractions and promising concepts, but I&#x27;ll keep with React for the &quot;real&quot; stuff 😄</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Designing interfaces with development in mind]]></title>
            <link>https://hdoro.dev/designing-interfaces-with-development-in-mind</link>
            <guid>https://hdoro.dev/designing-interfaces-with-development-in-mind</guid>
            <pubDate>Wed, 16 Jan 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<div class=""><p>This guide aims to provide some best practices for designing interfaces with the handoff in mind, having the goal of facilitating the communication between designers and developers. This is an ever evolving guide based on our internal processes at <a href="https://kaordi.ca">Kaordica</a>, and is by no means final - use it as a basis for your own conventions 😉</p><h2 id="heading-0980439544019458-004392248336081095"><a href="#heading-0980439544019458-004392248336081095" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>TL;DR</h2><ul><li>Start your design exploration with no rules to avoid hindering your pace, but start structuring the project before doing real production work and never forget to tidy up your workspace before handing it to development teams;</li><li>Provide and follow a thorough styleguide and design around it;</li><li>Think modularly and componentize your work as much as possible;</li><li>Try to have some minimal foundational knowledge on the tech stack that will be used on the project to guide your decisions - after all, the design <em>must</em> be feasible. If in doubt, reach out to your fellow devs, they&#x27;ll be glad to help;</li><li>Explore and test every possible usage scenario - or as many as you can - for your interface: users don&#x27;t always have good pictures to upload, and might not fill every info field, for example... how will the product react to that?</li><li>When still images don&#x27;t cut it in explaining desired behaviors, use transitions. If they aren&#x27;t enough, write down the explanation and be ready to answer questions;</li><li>Name things properly;</li><li>Be open to feedback from devs and learn from it.</li></ul><p>It&#x27;s a ton of things to remember at once, but they&#x27;ll fit naturally into your workflow and actually <strong>make the design process much more rewarding</strong> and the development phase quicker - at least it did for me.</p><h2 id="heading-03779516285880704-09288278902266174"><a href="#heading-03779516285880704-09288278902266174" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Starting the design</h2><p>This section is nothing more than you&#x27;re already used to: when starting the project, provided you already have a direction for the work, <strong>begin by letting go</strong> of any rules and expectations you might have and just start exploring what you could do. This applies to both the wireframe (the disposition and functionality of things) as well as the visual design (how things look and feel), and should happen without any decision - subconscious or not - being made.</p><p>At Kaordica, we create the wireframe carefully together with the clients during a design sprint workshop, as we believe it&#x27;s the most important part of the design: it&#x27;s where we think more about UX than making things pretty, allowing for alignment of the product vision with the client. Then, with most oft he flow and copy in place, we gather the (desired) attributes of the brand and relevant references - many of which suggested by clients - and start exploring colors, font faces, and everything else concerning the visual design.</p><p>![Selleta&#x27;s site&#x27;s visual exploration](media/selleta-visual-design-tests.png &quot;We&#x27;ll have a separate page in Figma just for drafts, in which there&#x27;s no logic to the disposition of things and the organization might seem a bit chaotic. That&#x27;s okay, you&#x27;ll be fine, things can be ugly and they won&#x27;t haunt your dream, promise!&quot;)</p><p>Then, when we start seeing something that can be put forward, we usually create a couple &quot;stylegrams&quot; - a montage with a consistent visual language - and share them with clients to choose. This prevents any surprises down the line and keep us all in the same boat - oh, and clients are also big fans of regular-but-not-too-much updates. If you&#x27;re on a tight schedule, I&#x27;d still say it&#x27;s worth it 😉</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:4"><div style="width:100%;padding-bottom:25%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/0a741408cd065fbdb2d7bc896d755d8e4e75912d-800x200.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Data Hackers&#x27; stylegram</figcaption></figure><p>Finally, with these things in place, it&#x27;s time to start structuring our project for the actual production work to avoid messy documents delivered to developers later on without unnecessary rework. And let me tell you, as a developer myself: <strong>if you don&#x27;t standardize</strong> typography, colors, shadows and the like, <strong>devs will</strong>. Code gets unmanageable if we use distinct variables for each and every different implementation you have, so try to, whenever possible, <strong>follow a styleguide</strong> - besides a happy developer, you&#x27;ll also have a more consistent design that users will love!</p><h2 id="heading-01879341442325977-01551283050354304"><a href="#heading-01879341442325977-01551283050354304" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Following a styleguide</h2><p>If I&#x27;ve convinced you of the importance of properly following a structure on your projects, then you must first understand what I mean by a <em>styleguide</em>. Also known as a &quot;pattern library&quot;, this thing is an area of study in and of itself, and for such I won&#x27;t go deep into it: just know that <a href="http://styleguides.io/">it can come in all shapes and sizes</a> and has to do with standardizing the building blocks of your brand&#x27;s user interfaces. With this terribly broad description I can even fit my personal version of it: a style sheet containing every typographic variation, color schemes, box-shadows, grids and logos.</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.8649045521292218"><div style="width:100%;padding-bottom:53.62204724409449%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/b37a1f4b2487f93ca2787c704c785eb983485bf4-1270x681.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Data Hackers&#x27; overly simplified styleguide</figcaption></figure><p>A styleguide can contain a brand&#x27;s voice and tone, patterns for content creators, standardized components to use, margins, icons, filters, etc., and you&#x27;re right in saying my... <em>sorry</em> effort is none of that, but that&#x27;s beside the point. Call it whatever you want, that&#x27;s beside the point, the fact is that the above organization is perfect for what I needed for the project - a <a href="https://datahackers.com.br/">&quot;simple&quot; website</a> - and really helped me out during development.</p><p>So, before going any further in the work, gather what you got in the exploration phase and prepare your own simple stylesheet, containing whatever you know you&#x27;ll use in the project. <strong>Nothing has to be carved in stone</strong>, these things may change over the design process and you can add new ones as needed, but doing so at the beginning will force you to be more organized and test more often with different values, as tools like Figma, XD and the likes broadcast updates through the whole design. Feel a color isn&#x27;t quite there? Just tweak it and see the real results, in real time, till you&#x27;re satisfied. I grant you this is better than trying to standardize everything at the end for the handoff deadline.</p><h2 id="heading-07398119047089855-0015786367150370406"><a href="#heading-07398119047089855-0015786367150370406" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Embrace the modularity - not singularity</h2><p>Now, speaking of saving time later in the project, you should really start thinking in reusable components, else you&#x27;ll find yourself cursing when having to fix a margin on a header present in 10 pages - or worse, who knows. I talk a bit more about this philosophy in my <a href="/react-for-designers">React for designers</a> article, and I&#x27;m sure you&#x27;ve heard this quite often, so I won&#x27;t go into much detail here.</p><p>Just know that, as much as creating and naming components in your UI design tool can be a hassle at first, it pays off. It sends a clear message to devs about what you were thinking in terms of organization and reusability when you built each thing and helps you test and spread changes throughout the design much easier. Also, it tends to force you to think about the possible scenarios of usage for each of them, preventing impossible or incoherent designs...</p><p>Oh, and for often reused pieces, a component will actually speed up your time quite a lot, just look at UI libraries for design tools like <a href="https://ui-kit.co/">Figma&#x27;s Material Design Theme Kit</a> - they wouldn&#x27;t sell so much if it wasn&#x27;t easy, right?</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.187568157033806"><div style="width:100%;padding-bottom:84.20569329660239%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/8c63e4275e3b63085dad1693c655aa0dffced5fa-1089x917.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Good thing I wasn&#x27;t the one creating all of these haha</figcaption></figure><h2 id="heading-06959483887912579-07316943038400137"><a href="#heading-06959483887912579-07316943038400137" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Know the waters you surf in</h2><p>Besides the reality check brought by the modularity mindset, that drives you into thinking about which properties each component should get - and the availability of each of those -, which can help steer a decision, knowing a little bit about the platform in which the design will be brought to life is a great plus.</p><p>Say you&#x27;re creating a dashboard that will later be a web app... A decent knowledge on HTML and CSS would allow you to see that the following design, seemingly simple, is possible, but hard to pull off and might become quite brittle in some browsers:</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:4.213166144200627"><div style="width:100%;padding-bottom:23.735119047619047%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/f1a19bf9c966f8c08d15807d9bd77a0e34fdfe03-1344x319.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">There&#x27;s a very thin line with a non-trivial path behind the section.</figcaption></figure><p>The title and body are super straightforward, the problem is the grey dotted line... It&#x27;s almost invisible in the white background and, though making the layout a bit more interesting, it&#x27;s a pain to reproduce. Using an image or SVG (a vector file) is possible, but you&#x27;d have some problems with screen resize, probably forcing the creation of several different versions; using only HTML elements and CSS is also possible, but would pollute the markup and create the need for several new lines of otherwise useless code.</p><p>Other fellow developers might disagree with me in this, but I&#x27;d rather not do it: it&#x27;s an increased overhead in terms of maintenance, page size and mobile performance for a tiny visual gain. By arming yourself with knowledge about the platform, which could be as easy as ~10h of HTML and CSS courses and practices, this type of thing will happen with much less frequency, guarantee. You&#x27;ll be happier to see your design fully fledged without alterations and devs will be happy for... well, working less 😉</p><p>Of course, you won&#x27;t be able to think about every possible scenario - even myself as a front-end developer don&#x27;t really know if many designs are feasible until I try it, but having this in mind will, over time, create this comprehension, promise!</p><h2 id="heading-0812452353265662-08460098202528394"><a href="#heading-0812452353265662-08460098202528394" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Let go of your beautiful, controlled environment</h2><p>Yes, I know you want to impress clients and give a tap in your back for creating such a gorgeous visual design, but I have to warn you: that professional photography and short description won&#x27;t be there in <em>real</em> users&#x27; profiles. I&#x27;m not saying to show ugly stuff to clients or to put away all the nice screens you have, these are great to show what you&#x27;re capable of in terms of composition.</p><p>The thing is, <strong>when relying solely on these, you&#x27;ll end up with really ugly real world results</strong>. Trust me, I&#x27;ve learned this in practice, here&#x27;s what my beautiful design became:</p><div class=" lazy-img" data-alt="Screenshots of two wildly different applications of the layout." data-src="https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.3284090909090909"><div style="width:100%;padding-bottom:75.27801539777587%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/2d926b251902d364126c6595ceb348854d57cf82-1169x880.png?w=1300&amp;fit=max&amp;auto=format" alt="Screenshots of two wildly different applications of the layout." sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><p>So, when analyzing a wireframe and thinking about the upcoming visual design, be sure to plan what properties are mutable by users (or clients), how they could go missing or how they could be horribly formatted. This might give you a clue as to how to deal with scenarios such as the one above and prevent your devs from having to figure it out on their own. The result will be better and less time and energy will be spent in development 😉</p><h2 id="heading-0334696414065931-09860373426826157"><a href="#heading-0334696414065931-09860373426826157" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Guide devs through the design</h2><p><strong>Don&#x27;t just send a link or PDF and think you&#x27;re done with it.</strong></p><p>Spend some good minutes going through the design with developers in a videoconference, record a video, use transitions and share prototypes for clearer representations of what has to be done, write about what can&#x27;t be easily explained through still images. <strong>Do what you can to make them understand your vision</strong>, and be there to help them when that doesn&#x27;t happen. Be creative.</p><p>Point dismissed, next!</p><h2 id="heading-0062334248573323814-03167238788598594"><a href="#heading-0062334248573323814-03167238788598594" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Name things properly</h2><p>I&#x27;ll can write more on the subject, but, in short, <strong>names don&#x27;t have to be short, they have to be self explanatory</strong>! <code>CardOne</code> is so much worse than <code>WhiteRoundedCard</code> when you&#x27;re passing your work forward - be it to others or to yourself in 3 months - that the hassle of the extra characters is justified.</p><p>Visual design doesn&#x27;t suffer from this need as much as code, as, when you doubt, you can simply take a look at what <code>awesome-tab-1</code> is, but acquiring good naming habits will surely help your organization... Plus, if regularly working with the same development teams, you can share naming conventions and talk about each component specifically whenever needed:</p><blockquote>Hey, the secondary button has the wrong padding in the website.<br/><br/>Oh, that was intentional! I figured it was too far from the rest of the site, should I change it?</blockquote><p>With good naming conventions, it&#x27;ll all be birds, flowers and rainbows 🤗</p><h2 id="heading-06812777043744742-05791479192936577"><a href="#heading-06812777043744742-05791479192936577" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>The old &quot;open to criticism thing&quot;</h2><p>Well, after all of these points, it should be clear that there&#x27;s a ton of stuff that can be improved in your workflow when it comes to handing UIs to developers, and this list isn&#x27;t exhaustive... So, you know, <strong>if all we know is nothing, we gotta listen to others</strong>.</p><p>I try to be both a designer (though more focused on UX, I suck at visual design) and a developer, and this puts me in a place to easily find ways to improve our processes here at Kaordica, but this can only go so far. I&#x27;ve never worked in another design team and my tech stack has been only in the web space since I began coding, and, of course, there&#x27;s always the blindness to our own mistakes... So do everybody a favor and contribute to this guide with all your experience and knowledge you&#x27;ve accumulated through open listening 😄</p><p>Every <em>constructive</em> feedback is welcome, but please keep the hatred away, <em>s&#x27;il vous plaît</em>.</p></div>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[React for designers]]></title>
            <link>https://hdoro.dev/react-for-designers</link>
            <guid>https://hdoro.dev/react-for-designers</guid>
            <pubDate>Tue, 15 Jan 2019 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<div class=""><p>The goal of this guide is to explain, in the simplest and shortest way possible, what is <a href="https://reactjs.org">React</a> and what does it implies to you as a UI designer.</p><h2 id="heading-07688345530725862-04104431665751651"><a href="#heading-07688345530725862-04104431665751651" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>What is React</h2><p><strong>Definition:</strong> React is a javascript library (framework) that facilitates the construction of dynamic interfaces and websites. Going deeper into this definition:</p><ul><li><em>javascript</em> is the programming language native to browsers and the web that brings <strong>interactivity and logic</strong> to websites and apps. It allows, for example, to get a form&#x27;s data and send it to a server that will, then, update a users info.</li><li>A library is simply a repository of code (a folder with files) that attend a specific need.</li><li>Interfaces and websites, when built from scratch, can be hard to manage, and you end up writing a lot of otherwise-unnecessary code. React gives you a series of tools that facilitates this process, but there are several others in the market. There are a few reasons why we choose React:<ul><li>It was <strong>created by Facebook</strong> to structure almost all of their products (Instagram, Facebook, WhatsApp...), and, currently, the company pays for a dedicated team to manage and evolve the library;</li><li>It&#x27;s the most widely adopted web framework. Everyone in the field knows about it, tons of people use it and it has an extremely strong ecosystem around it, providing us with several tools and resources on the matter</li><li>It&#x27;s relatively easy to learn, as well as very flexible and capable, with nice concepts and abstractions that I love using</li></ul></li></ul><h2 id="heading-047829072431083386-0865956381127915"><a href="#heading-047829072431083386-0865956381127915" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>React&#x27;s core principles</h2><p>The library has three foundational principles: the <strong>components</strong>, <strong>props</strong> and <strong>state</strong>. The whole workflow and mindset of working with React revolves around these concepts, and they&#x27;re crucial even to visual design, as we&#x27;ll see ;)</p><h3 id="heading-0272873261290026-02083701346768112"><a href="#heading-0272873261290026-02083701346768112" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Components</h3><p>You can think of React as a <strong>big Lego box</strong>: the idea is that you&#x27;ll gather each individual piece and combine them to create a final, cohesive product. Each little piece, or <em>component</em>, has a format and a role in the structure in a Lego castle, for example, and each of them can be composed of smaller pieces in their own way. This division serves a great purpose: it allows for <strong>effortless reusability</strong>, as you can use the same standardized component elsewhere, as many times as needed, such as the duplication of a castle&#x27;s tower.</p><div class=" lazy-img" data-alt="Lego Castle diagram showing different components" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.3515625"><div style="width:100%;padding-bottom:73.98843930635839%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/5b03df960684e62cd96c547b3e8e0246e5a61c77-1038x768.png?w=1300&amp;fit=max&amp;auto=format" alt="Lego Castle diagram showing different components" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><p>The image above summarizes well this component relationship:</p><ul><li>You have a <code>castle</code> component that wraps it all;</li><li>This <code>castle</code> is composed, among others, by a <code>tower</code> component, that is used twice;</li><li>The <code>tower</code> has 3 subcomponents: <code>top</code>, <code>secondFloor</code> and <code>base</code>;</li><li>These subcomponents, on their behalf, are also composed by other components. For example, <code>top</code> has a few specific block-types, such as <code>blockRed</code> and <code>blockDiagonal</code>.</li></ul><h3 id="heading-0896501517444648-016450127890400612"><a href="#heading-0896501517444648-016450127890400612" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Props</h3><p>The componentization workflow helps saving quite a lot work as we don&#x27;t have to recreate individual pieces every time... but they can only go so far as to duplicate itself, in the same shape and functionality as always, making them not as useful as they could be. Say, for example, that I have a button that, in the design, could be red or green: would I need to create two components <code>buttonRed</code> and <code>buttonGreen</code>? It&#x27;d be quite grindy to do so, right? This is where <em>props</em> come in!</p><p>Props, as you&#x27;d imagine, are <strong>properties that you pass to your components</strong> for them to alter their shape or functionality accordingly. This allows you to, in the above example, pass a prop <code>color=&quot;red&quot;</code> to your button instead of making a whole new component.</p><div class=" lazy-img" data-alt="Example of two button components being used with different color props" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:2.6143790849673203"><div style="width:100%;padding-bottom:38.25%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/d983d9e8bf3cfd59efe5a8671ba04dc816572c20-800x306.png?w=1300&amp;fit=max&amp;auto=format" alt="Example of two button components being used with different color props" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><p>As simple as it its, the above example is a good indicator of the power of components that receive props: they can <strong>adapt to different data</strong> and become super flexible, turning everything even more modular and reusable! Oh, and it&#x27;s worth noting that the text between <code>&lt;Button&gt;</code> and <code>&lt;/Button&gt;</code> is, itself, a prop. The <code>Button</code> component adapts to this property by using it as the button&#x27;s internal text 😉</p><h3 id="heading-08653445604228767-0076909140430512"><a href="#heading-08653445604228767-0076909140430512" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>State</h3><p>If you&#x27;ve already grasped the <em>prop</em> concept, then <em>state</em> will be quite easy: if, on one hand, <em>props</em> come from outside - whatever calls <code>Button</code> defines its text and <code>color</code>, for example - the <strong>state is defined inside the component</strong>. It&#x27;s as if the button has a property <code>wasClicked</code> that was internal and controlled solely by the button itself - my button, my rules!</p><p><em>Stateful</em> components can have methods that change or interact with the internal state, and that allows for a series of funcionalities that could range from controlling at which point is a vector in an animation to, for example, if a user is logged in and what is their data in a complex dashboard.</p><p>In the above animation (from <a href="https://novidaconsultoria.com.br/">Novida&#x27;s website</a>), the <code>FirstSection</code> component has a state <code>shouldStart</code>, that starts as <code>false</code> and, as the site loads, is updated to <code>true</code>, making the background blue (instead of white) and allowing the <code>Tree</code> to show up. This component then has its own state that dictates at which stage the tree currently is, updating it every 150ms or so to the next one in order to simulate a growth animation.</p><p>It&#x27;s worth noting that the <em>props</em> of a component can be defined by the state of a wrapping component (the &quot;parent&quot; component). In the above example, the <code>Tree</code> receives a prop <code>canStart</code> passed down by <code>FirstSection</code>, which controls its value by its own internal state <code>shouldStart</code>. Maybe the code below will make it a bit easier to visualize:</p><div class="code-block" data-explanation="false"><pre class="language-jsx"><span class="token keyword">class</span> <span class="token class-name">FirstSection</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">{</span>
    state <span class="token operator">=</span> <span class="token punctuation">{</span>
        <span class="token literal-property property">shouldStart</span><span class="token operator">:</span> <span class="token boolean">false</span>
    <span class="token punctuation">}</span>
    <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Wrapper</span></span> <span class="token attr-name">isBackgroundWhite</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>shouldStart<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">
        	</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Tree</span></span> <span class="token attr-name">canStart</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>shouldStart<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Wrapper</span></span><span class="token punctuation">></span></span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><br /></pre></div><p>As you can see in this simplified example, the <code>FirstSection</code> is a <em>stateful</em> component - which is marked by the <code>class</code> keyword - that holds a <code>shouldStart</code> state and renders a <code>Tree</code> which <code>canStart</code> prop is defined by its parent state. It may sound complicated at first, but you&#x27;ll get the hang of it!</p><p>Following this logic, we get to a very common pattern in React apps: there&#x27;s a wrapping component that holds the whole project and that contains a huge state, with most of the mutable data of the application. This &quot;root component&quot; then passes down parts of this state to each &quot;child component&quot;, as needed. This way, we can distribute a logged-in user&#x27;s info throughout the header, to show the name and avatar, the user dashboard, and to a input field to leave a message with their name on it, for example.</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.839080459770115"><div style="width:100%;padding-bottom:54.375%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/3e460cdfdc9c5d51d4262643066bfe0f1a2535bd-800x435.jpg?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Slack is an example, it uses React</figcaption></figure><p>Slack is a good example of this - and is also built in React: the same information - my username and contact info - is stored in the app&#x27;s root state, in a central directory that distributes this data to the sidebar, the chat and the profile information.</p><h3 id="heading-09984614942103727-0010086110731577946"><a href="#heading-09984614942103727-0010086110731577946" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>In summary</h3><p>React advocates for modularity in code, with the creation of reusable components that interact between each other - able to contain and be contained by others. These can pass down props to their child components and/or have internal states, which they can use to search for data, displaying info, reacting to user interaction, controlling animations, etc. React, then, as the library&#x27;s own name suggests, <strong><em>reacts</em> to changes in state</strong> and props and <strong><em>re-renders</em> the interface</strong> to the screen. <em>C&#x27;est tout!</em></p><h2 id="heading-0793494015569892-09407025688890542"><a href="#heading-0793494015569892-09407025688890542" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>What does this mean for you as a designer</h2><p>You don&#x27;t have to know how React does it, or even what are the possibilities for sites and apps, but <strong>you need to understand the component &lt;&gt; props &lt;&gt; state dynamic</strong>. It shapes the way we developers think about the structure of the app, the formation of the data (content) and in the variation of different visual components; and, of course, our code revolves around it. And it should shape your design process as well. In the example below, I try to show you why this is relevant.</p><h3 id="heading-04461826908712896-0695440713783988"><a href="#heading-04461826908712896-0695440713783988" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Practical exemple: testimonial card</h3><p>Let&#x27;s take the testimonial card we created for <a href="https://www.gruposelleta.com/">Selleta&#x27;s site</a> as an example: the visual design below is what I got from my teammate to develop. As beautiful as it is (great job, Vítor!), it has a few problems, which we&#x27;ll discuss with the concepts you learned about React.</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.941747572815534"><div style="width:100%;padding-bottom:51.5%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/8bce5c411ddfc47eea2230352266ee3e17d6225e-400x206.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Simple testimonial card</figcaption></figure><p>There are two main things with this design:</p><ul><li>We can&#x27;t guarantee the quality, orientation or focus of the pictures that will be uploaded about the person, which makes this image mask in the shape of the logo quite <em>fragile</em>: it could be a terrible photo, which focus is outside of the mask, making it impossible for visitors to understand it. So, when using pictures in your design that could be altered by users or clients, work with bad photos and see if they work as well 😉</li><li>A testimonial is already something a bit difficult to obtain and, many times, the info contained in them will be incomplete or in and unexpected format... The props that a <code>TestimonialCard</code> could receive, therefore, aren&#x27;t very predictable:<ul><li>The person can prohibit the usage of their picture;</li><li>The text body can be HUGE - testimonials with over 2 paragraphs aren&#x27;t uncommon;</li><li>The icon should say something about the person giving the testimonial, and so we need to spend a good time providing a good selection of options for our client to choose from, else, if we leave it to them, they&#x27;ll provide icons of various styles, breaking the design language. This means quite a lot of work and time dedicated to such a small task;</li><li>The person&#x27;s title could be way too lengthy to fit into the card - &quot;Aluno de projetos arquitetônicos&quot; is already quite large, but these particular clients had people with titles such as &quot;Coordenador do projeto &#x27;Casa para todos&#x27; da UFMT&quot;, which is even bigger 😝</li></ul></li></ul><p>If I were to blindly reproduce this design in code, I could get to the following situation:</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:2.421161825726141"><div style="width:100%;padding-bottom:41.30248500428449%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/479c9ffa1e7d974c770ce072bf536641eec27ca3-1167x482.jpg?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Ugly testimonial card via bad props</figcaption></figure><p>The result isn&#x27;t catastrophic, but the building&#x27;s image is being cropped at the wrong place, the second card is way bigger than the other and the icon kept the same, making it a bit repetitive... Of course we can limit the number of characters in the content management system (CMS), make a very thorough list of icons for choosing and trying to instruct clients to only upload images with a focus in the middle... but, at the bare minimum, this approach isn&#x27;t trivial for the developer, who will have to make assumptions. This lack of communication <strong>wastes development time and leads to worse results</strong>.</p><p>And say, for example, that a <code>TestimonialCard</code> didn&#x27;t get an <code>image</code> prop passed down? Which of the options below the dev should put forward?</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:2.8443396226415096"><div style="width:100%;padding-bottom:35.157545605306794%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/12615c038c606b44db50f0f6e03fe60883dc5996-1206x424.jpg?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Options for cards without image</figcaption></figure><p>Both solutions are easy to implement, the problem isn&#x27;t code... but they have different implications. When developing, I try to avoid asking &quot;small&quot; questions to the designers in order to avoid wasting their time and to speed up my work, but there are often times I have to revise these tiny decisions later on, wearing me out and slowing the delivery of the project.</p><p>So, <strong>next time you&#x27;re creating an interface&#x27;s</strong> wireframe and visual design, think pragmatically about the props that each component needs and start to think about all the possible scenarios. It&#x27;s going to be a little bit of extra work, but the end result will satisfy you a lot more and your devs will be thankful!</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.48975791433892"><div style="width:100%;padding-bottom:67.125%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/093b0ed0576388ab80b789b3d5f2ce05f17a4f7d-800x537.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Ideal card design set-up</figcaption></figure><p>My dream is to find good organization such as the above image in every project 😄</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Structuring and linking data in webpages: why care and how-to]]></title>
            <link>https://hdoro.dev/structuring-linking-data</link>
            <guid>https://hdoro.dev/structuring-linking-data</guid>
            <pubDate>Thu, 11 Oct 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<div class=""><p><strong>TL;DR</strong>: turns out that, even with all the machine learning hype, computers are <em>dense</em> when it comes to understanding what a specific data means and how it&#x27;s related to the rest of the web... but you can help them - and help yourself with better SEO in the process - by structuring and linking your data with JSON-LD, RDFa and Microdata!</p><h2 id="heading-09076457346690838-014550447194329652"><a href="#heading-09076457346690838-014550447194329652" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Prelude: computers need clarification on content</h2><p>Manu Sporny does a great job at explaining the concept of linked data through his <a href="https://www.youtube.com/watch?v=4x_xzT5eF5Q">video on what is linked data?</a>, check it out if you want to go deeper ;) If you don&#x27;t, then all you need to know is that with so much content on the web, it&#x27;s difficult for bots to understand what each page is talking about, leave alone the relationship between them.</p><p>For example, if a blog post cites Jair Bolsonaro and links it to an URL, the crawler will have a hard time figuring out if the link refers to the personal website of this person or, say, to a <a href="https://www.vox.com/world/2018/10/10/17952948/jair-bolsonaro-brazil-elections-trump">news article on what a monster this manipulator is</a> (not related, but great read, don&#x27;t let fascism win!). To help the computer, you can use one of the available syntaxes to tell computers what exactly the link is talking about, like so:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token comment">&lt;!--
  Example done with a mixture of RFDa (vocab, typeof and property)
  and Microdata (itemscope, itemtype)
--></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">vocab</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://schema.org/<span class="token punctuation">"</span></span> <span class="token attr-name">typeof</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Jair Bolsonaro<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span>,
  member of the <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>memberOf<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>PSL party<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span>,
  is a <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>jobTitle<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>monstrous politician<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> with
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://blog.post<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>url<span class="token punctuation">"</span></span>
    <span class="token attr-name">itemscope</span> <span class="token attr-name">itemtype</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://schema.org/NewsArticle<span class="token punctuation">"</span></span>
  <span class="token punctuation">></span></span>very controversial opinions<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>.
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br /></pre></div><p>This makes it clear that the &quot;person&quot; Jair Bolsonaro is a monstrous politician from the PSL party, whose URL is a news article. In this guide, I intend to show how this is relevant and how to use it, but it&#x27;s far from a super complete and proven guide, so feel free to give feedback and read more from this articles&#x27; <a href="#references">references</a> ;)</p><h2 id="heading-07699303583241219-03991115659548412"><a href="#heading-07699303583241219-03991115659548412" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Why care</h2><p>Well, besides contributing to a better web by properly linking relevant content together, we have a very pressing and rewarding reason for structuring and linking data: <strong>SEO</strong> and <strong>social media</strong>.</p><p>Google&#x27;s <a href="https://developers.google.com/knowledge-graph/">Knowledge Graph</a> is based on these ideas, meaning you can follow their guidelines for better positioning your website on search results and better displaying data. Want to display your products&#x27; prices? Highlight specific links in your website? Then you&#x27;ll probably have to use <a href="https://developers.google.com/search/docs/guides/mark-up-content">Rich Snippets</a> for that!</p><p></p><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.8796296296296295"><div style="width:100%;padding-bottom:53.20197044334976%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/ae355b44aef2befd871b32a5ab23b6ccda411282-812x432.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><p>I&#x27;m not sure about the exact benefits to SEO ranking, but by providing a more structured and complete result for users, you&#x27;ll probably see an increase in click through rate. I, for one, always click on the quick links haha</p><p><a href="http://ogp.me/">OpenGraph Protocol</a>, Facebook&#x27;s way of understanding relevant content of a webpage to display in its social media platforms, is also used by Twitter and LinkedIn, and is a must-use if your links is ever going to be shared on social media, unless you want a boring link display with just a title ;)</p><h2 id="heading-02021708663027344-0024451720750439998"><a href="#heading-02021708663027344-0024451720750439998" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>JSON-LD vs. RDFa vs. Microdata</h2><p>You can use <a href="https://json-ld.org/">JSON-LD</a>, <a href="http://rdfa.info/">RDFa</a> or Microdata(https://www.w3.org/TR/microdata/) to structure / link your data, and these can be used interchangeably in the same document. Feel free to explore <a href="https://schema.org/Person">Schema.org examples</a> (scroll to the end of the page) and read each syntax&#x27;s documentation to figure out which is best for you. Below are some considerations I have for each:</p><h3 id="heading-03918365005948017-05355045698642005"><a href="#heading-03918365005948017-05355045698642005" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>JSON-LD</h3><p>JSON-LD: is a Resource Description Framework (<a href="https://en.wikipedia.org/wiki/Resource_Description_Framework">read more about RDF on Wikipedia</a>) based on JSON that:</p><ul><li>Can be added anywhere in the document and is not tied to the markup - it fits even outside of HTML5;</li><li>Works great with JS and even has a <a href="https://github.com/digitalbazaar/jsonld.js">JS library</a> to build it;</li><li>Might make your pages a bit heavy as the syntax is quite extensive, but with gzip this is almost irrelevant;</li><li>I&#x27;m not sure if it&#x27;s supported by all search engines;</li><li>You can get ready-to-use snippets at <a href="https://jsonld.com/">Steal Our JSON-LD</a>;</li><li>By not being directly tied to the markup, it could be that search engines give less credit to it, but that&#x27;s just a speculation I saw on Stack Overflow.</li></ul><p><strong>Example JSON-LD code</strong>:</p><div class="code-block" data-explanation="false"><pre class="language-js"><span class="token punctuation">{</span>
  <span class="token string-property property">"@context"</span><span class="token operator">:</span> <span class="token string">"http://schema.org"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"@type"</span><span class="token operator">:</span> <span class="token string">"BreadcrumbList"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"itemListElement"</span><span class="token operator">:</span>
  <span class="token punctuation">[</span>
  <span class="token punctuation">{</span>
    <span class="token string-property property">"@type"</span><span class="token operator">:</span> <span class="token string">"ListItem"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"position"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
    <span class="token string-property property">"item"</span><span class="token operator">:</span>
    <span class="token punctuation">{</span>
    <span class="token string-property property">"@id"</span><span class="token operator">:</span> <span class="token string">"https://example.com/dresses"</span><span class="token punctuation">,</span>
    <span class="token string-property property">"name"</span><span class="token operator">:</span> <span class="token string">"Dresses"</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    <span class="token string-property property">"@type"</span><span class="token operator">:</span> <span class="token string">"ListItem"</span><span class="token punctuation">,</span>
  <span class="token string-property property">"position"</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span>
  <span class="token string-property property">"item"</span><span class="token operator">:</span>
    <span class="token punctuation">{</span>
      <span class="token string-property property">"@id"</span><span class="token operator">:</span> <span class="token string">"https://example.com/dresses/real"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"name"</span><span class="token operator">:</span> <span class="token string">"Real Dresses"</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  <span class="token punctuation">]</span>
<span class="token punctuation">}</span><br /></pre></div><h3 id="heading-029578163408089986-09375591691993745"><a href="#heading-029578163408089986-09375591691993745" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>RDFa</h3><p>RDFa: another resource description framework, but comes tied to the markup:</p><ul><li>Powers Facebook&#x27;s OGP, <a href="https://www.w3.org/TR/rdfa-core/">is a W3C recommendation since 2015</a> and has been accepted by Google for a long time, so it&#x27;s quite the robust syntax;</li><li>Personally, I find it the most pleasing to write and easy to learn - RDFa in HTML is the <a href="https://www.w3.org/TR/rdfa-lite/">lite implementation</a>, which is quite nifty;</li><li>Doesn&#x27;t seem as flexible as JSON-LD in terms of environments it can be added to, but unlike Microdata, that only works with HTML5, RDFa can be included in SVG and XML documents.</li></ul><p><strong>Example RDFa code</strong>:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ol</span> <span class="token attr-name">vocab</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://schema.org/<span class="token punctuation">"</span></span> <span class="token attr-name">typeof</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>BreadcrumbList<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>itemListElement<span class="token punctuation">"</span></span> <span class="token attr-name">typeof</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>ListItem<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>item<span class="token punctuation">"</span></span> <span class="token attr-name">typeof</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>WebPage<span class="token punctuation">"</span></span>
        <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://example.com/dresses<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Dresses<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>position<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
  ›
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>itemListElement<span class="token punctuation">"</span></span> <span class="token attr-name">typeof</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>ListItem<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>item<span class="token punctuation">"</span></span> <span class="token attr-name">typeof</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>WebPage<span class="token punctuation">"</span></span>
        <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://example.com/dresses/real<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Real Dresses<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>position<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ol</span><span class="token punctuation">></span></span><br /></pre></div><h3 id="heading-03271295054112233-03319231391866533"><a href="#heading-03271295054112233-03319231391866533" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Microdata</h3><p>Being the only one that is not an RDF, the microdata way of doing things seems to be the least solid of the three. It looks like an implementation geared towards less technical people - which is great, easily reaching marketers and designers - but the fact that it froze for a while <a href="https://github.com/w3c/microdata">before becoming a W3C recommendation</a> makes me a bit uncomfortable in using it. That said, it definitely is a trustworthy spec - <a href="https://neilpatel.com/blog/get-started-using-schema/">even Neil Patel uses it</a>, so feel free to use it if you vibe with the syntax.</p><p><strong>Example microdata code</strong>:</p><div class="code-block" data-explanation="false"><pre class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ol</span> <span class="token attr-name">itemscope</span> <span class="token attr-name">itemtype</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://schema.org/BreadcrumbList<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">itemprop</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>itemListElement<span class="token punctuation">"</span></span> <span class="token attr-name">itemscope</span>
      <span class="token attr-name">itemtype</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://schema.org/ListItem<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">itemtype</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://schema.org/WebPage<span class="token punctuation">"</span></span>
       <span class="token attr-name">itemprop</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>item<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://example.com/dresses<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">itemprop</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Dresses<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">itemprop</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>position<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
  ›
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">itemprop</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>itemListElement<span class="token punctuation">"</span></span> <span class="token attr-name">itemscope</span>
      <span class="token attr-name">itemtype</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://schema.org/ListItem<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">itemtype</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://schema.org/WebPage<span class="token punctuation">"</span></span>
       <span class="token attr-name">itemprop</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>item<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://example.com/dresses/real<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">itemprop</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Real Dresses<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">itemprop</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>position<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>2<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ol</span><span class="token punctuation">></span></span><br /></pre></div><h3 id="heading-043688354573342547-0770976569785853"><a href="#heading-043688354573342547-0770976569785853" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Choosing which to use</h3><p>As you can see, in terms of syntax there&#x27;s not much difference between RDFa and Microdata, and JSON-lD is decoupled from the markup, so my framework of thought for choosing which to use is simple:</p><ul><li>Can I change the markup?<ul><li><strong>No</strong>: then go for JSON-LD</li><li><strong>Yes</strong>: then go for RDFa. Microdata doesn&#x27;t work with SVG, I find the syntax a bit less attractive and it has less maturity as an official recommendation, so RDFa seems like a more solid option.</li></ul></li></ul><h2 id="heading-03361211216662545-0388671824757995"><a href="#heading-03361211216662545-0388671824757995" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>How to incorporate this in your workflow</h2><p>Well, honestly, <strong>I&#x27;m writing this article mainly to get used to these ideas</strong> and <strong>become comfortable in using it</strong>, so I don&#x27;t have a simple-yet-effective solution for you... What I can advice you on is to slowly start incorporating schema into your HTML code until you get the hang of it. The <a href="https://schema.org/">Schema vocabulary</a> is super extense and hard to understand (for example, wtf is the difference between BlogPost and BlogPosting?), and I personally still spend hours trying to figure it out every time I need to markup a simple thing, but eventually it starts to incorporate into your work.</p><p>If you&#x27;re developing reusable components for your apps and websites, you can afford the luxury of creating perfect markup as it&#x27;s going to payoff in other projects. Else, you don&#x27;t have to be perfect, but at least mark your page with page-wide schema annotations - such as <code>https://schema.org/Blog</code> for your blog&#x27;s homepage - to boost your SEO at least a little bit.</p><h2 id="heading-08706028448038512-09864366833410796"><a href="#heading-08706028448038512-09864366833410796" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>References</h2><ul><li><a href="https://json-ld.org/">JSON-LD official website</a></li><li><a href="https://jsonld.com">JSON-LD snippets</a></li><li><a href="https://stackoverflow.com/questions/8957902/microdata-vs-rdfa/25888436#25888436">Differences between RDFa and Microdata</a> on Stack Overflow</li><li><a href="https://stackoverflow.com/questions/26906367/microdata-rdfa-or-json-ld-appropriate-or-best-usage">Microdata, RDFa or JSON-LD Appropriate or best usage?</a> on Stack Overflow</li><li>Manu Sporny&#x27;s <a href="https://www.youtube.com/watch?v=4x_xzT5eF5Q">video on what is linked data?</a></li><li><a href="https://www.youtube.com/user/msporny/feed">Manu Sporny&#x27;s videos</a></li></ul></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[Live preview changes to react websites with Sanity]]></title>
            <link>https://hdoro.dev/gatsby-live-preview-sanity</link>
            <guid>https://hdoro.dev/gatsby-live-preview-sanity</guid>
            <pubDate>Mon, 17 Sep 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to make your clients’ lives easier with a simple-to-setup live preview for their CMS with any website built using a framework like react, vue or stencil.]]></description>
            <content:encoded><![CDATA[<div class=""><p>I personally got used to present static sites to clients as a clear tradeoff between security/performance/flexibility and ease of management, as they had to wait up to 4 minutes for a change made in the CMS to go live... Thankfully, though, by combining <a href="https://sanity.io">Sanity</a>&#x27;s listener API and the flexibility provided by <a href="https://reactjs.org">React</a>, we can allow non-technical writers to experience live-preview editing in their website!</p><p><strong>Disclaimer</strong>: The set-up I have must be heavily adequated to each project, so I won&#x27;t go through as much coding, but rather present some high level understanding of all this. If you have any suggestions, please tweet me at <a href="https://twitter.com/hdorodev">@hdorodev</a> :D</p><p><strong>Second note</strong>: You can use the same idea and apply it to any front-end framework or static site generator built on them ;)</p><h2 id="heading-03377193151568443-008171451235366556"><a href="#heading-03377193151568443-008171451235366556" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Pre-requisites</h2><p>You can do this with an existing website or create a test project, but there are a couple of things you absolutely need:</p><ol><li>A Sanity project with documents for previewing. Learn the in&#x27;s and out&#x27;s of building the CMS in <a href="https://www.sanity.io/docs">their docs</a> (it&#x27;s really easy and flexible, promise);</li><li>A website whose template system is based on a front-end framework such as React or <a href="https://vuejs.org">Vue</a>. It could perfectly be a static site generator such as <a href="https://gatsbyjs.org">Gatsby</a> (which is what I&#x27;ll be using) or <a href="https://nuxtjs.org/">Nuxt.js</a>.</li></ol><p><strong>Understanding the preview process</strong>:</p><ol><li>You&#x27;ll use Sanity to build a custom preview URL for each of your documents when users access them;</li><li>This URL will point to a specific route in your website</li><li>It&#x27;ll also contain a query string that you&#x27;ll use to know which document to query and run the <code>fetch</code> and <code>listen</code> methods of <a href="https://github.com/sanity-io/sanity/tree/next/packages/%40sanity/client">@sanity/client</a> with this document&#x27;s ID;</li><li>Finally, you&#x27;ll run the data through your front-end to render the proper templates and components. Then, as soon as someone makes a change to the document, <code>sanity-client</code> will fire the callback function of the <code>listen</code> method and allow your framework to re-render everything.</li></ol><h2 id="heading-02966275608471496-05834611305994821"><a href="#heading-02966275608471496-05834611305994821" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Building the preview URLs</h2><p><strong>Note:</strong> Sanity has a <a href="https://www.sanity.io/docs/preview-content-on-site">brief piece on previewing</a> in their docs, feel free to read it detail.</p><p>Setting Sanity up for the preview is quite simple as all you want is to display a link on the document page (image below, credits to Sanity.io). To do so, you&#x27;ll have to install the <code>@sanity/production-preview</code> plugin and implement a new <code>part</code> in your <code>sanity.json</code> file (at the root of the project) and point its <code>path</code> to a JS document. All this file does is returning a string with the preview URL.</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:0.8885714285714286"><div style="width:100%;padding-bottom:112.54019292604502%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/587c9b26e9c03a85b79f4e105f470eefae572811-311x350.png?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Open preview prompt. Credits: https://sanity.io</figcaption></figure><p>First, run <code>sanity install @sanity/production-preview</code> and the CLI will automatically add this plugin to your project. Then, you&#x27;ll want to edit your <code>sanity.json</code> file and make sure it contains the following:</p><div class="code-block" data-explanation="false"><pre class="language-js"><span class="token comment">// In sanity.json</span>
<span class="token punctuation">{</span>
  <span class="token string-property property">"plugins"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token comment">//...</span>
    <span class="token string">"@sanity/production-preview"</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token string-property property">"parts"</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token comment">//...</span>
    <span class="token punctuation">{</span>
      <span class="token string-property property">"implements"</span><span class="token operator">:</span> <span class="token string">"part:@sanity/production-preview/resolve-production-url"</span><span class="token punctuation">,</span>
      <span class="token string-property property">"path"</span><span class="token operator">:</span> <span class="token string">"./getPreviewUrl.js"</span> <span class="token comment">// or the path to the proper file</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">]</span>
<span class="token punctuation">}</span><br /></pre></div><p>Finally, create a <code>getPreviewUrl.js</code> file that follows something like this:</p><div class="code-block" data-explanation="false"><pre class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">isDraft</span> <span class="token operator">=</span> <span class="token parameter">id</span> <span class="token operator">=></span> id<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'drafts'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">resolveProductionUrl</span><span class="token punctuation">(</span><span class="token parameter">document</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// First, we select a specific type of document</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>document<span class="token punctuation">.</span>_type <span class="token operator">===</span> <span class="token string">'page'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// Then we get its ID</span>
    <span class="token keyword">let</span> id <span class="token operator">=</span> document<span class="token punctuation">.</span>_id<span class="token punctuation">;</span>
    <span class="token comment">// if it's a draft, we split its _id with the "drafts." substring, which will return an array,</span>
    <span class="token comment">// and get the second item in it, which will be the isolated _id without "drafts."</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isDraft</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      id <span class="token operator">=</span> document<span class="token punctuation">.</span>_id<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'drafts.'</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// And return a template string reflecting the URL structure we want. In this case, we're doing a</span>
    <span class="token comment">// simple conditional to return '&amp;isDraft=true' as a param for drafts as we'll query them</span>
    <span class="token comment">// differently in the front-end</span>
    <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://yourUrl.com/preview?pageId=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>id<span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>
      <span class="token function">isDraft</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span>_id<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string">'&amp;isDraft=true'</span> <span class="token operator">:</span> <span class="token string">''</span>
    <span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><br /></pre></div><p><strong>Understanding the code above:</strong></p><ol><li>We return a function that receives a document object from Sanity and should return a string. If it doesn&#x27;t return a value the &quot;Open preview&quot; prompt simply won&#x27;t show in the menu;</li><li>This document object has an <code>_id</code> and <code>type</code> properties. You can use these to build your URLs as you want, but you absolutely need to return the <code>_id</code> to get the proper document in the front-end;</li><li>In this example, I&#x27;m only interested in documents of type <code>page</code>, but you could return a <code>&amp;pageType=${document._type}</code> parameter in your query string and process it accordingly in the front-end to return the proper template for the document.</li><li>I&#x27;m also interested to know if the document is a draft or not. If it&#x27;s, the function extracts only the id from the <code>_id</code> property (it would look like <code>drafts.58abec-dd1238d-(...)</code>) as I only want the actual identifier for the front-end.</li><li>Finally, we build an URL from this information and return it for displaying on the document page ;)</li></ol><p>This is it for the Sanity part, now let&#x27;s move on to your front-end structure...</p><h2 id="heading-02016136541993867-049781616615692026"><a href="#heading-02016136541993867-049781616615692026" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Using the URL on the website</h2><p><strong>Note:</strong> This step will depend on your tech stack, in this example I&#x27;m using Gatsby, but maybe you can get some ideas from reading on... Else, feel free to <a href="#caveats-and-limitations">jump to the conclusions</a> ;)</p><p>The first step you need is to create a route for the URL structure you provided before... for Gatsby, that means creating a <code>preview.jsx</code> file in your <code>/src/pages</code> directory and configuring <code>gatsby-plugin-create-client-paths</code> to tell the build system this page is client-only (or <a href="https://www.gatsbyjs.org/docs/building-apps-with-gatsby/#client-only-routes--user-authentication">configure it manually through <code>gatsby-node</code></a>). This page needs to render a component that takes the window location (which could be provided by a router), parses the document id, fetches the data and listens to its changes and renders a template passing down the data. The following is my own implementation:</p><div class="code-block" data-explanation="false"><pre class="language-jsx"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">PreviewPage</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">LayoutBasis</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token punctuation">{</span><span class="token comment">/* I use React Helmet to add a meta tag that avoids indexing of this page */</span><span class="token punctuation">}</span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>robots<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>noindex, nofollow<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Helmet</span></span><span class="token punctuation">></span></span><span class="token plain-text">
      </span><span class="token punctuation">{</span><span class="token comment">/*
        @reach/router offers a &lt;Match /> component that passes down router-related props
        to a child function. We use this function to either render the template if we're in
        the proper path - and pass the location prop to it -, or navigate to the homepage
      */</span><span class="token punctuation">}</span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Match</span></span> <span class="token attr-name">path</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/preview<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token plain-text">
        </span><span class="token punctuation">{</span><span class="token parameter">props</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
          <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>props<span class="token punctuation">.</span>match<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token function">navigate</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
          <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
            <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">PreviewTemplate</span></span> <span class="token attr-name">location</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>props<span class="token punctuation">.</span>location<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span>
          <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token plain-text">
      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Match</span></span><span class="token punctuation">></span></span><span class="token plain-text">
    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">LayoutBasis</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span><br /></pre></div><p>Then, in your <code>PreviewTemplate</code> component, you&#x27;ll get the <code>pageId</code> from the URL you built with Sanity through <code>query-strings</code> and fetch the proper document data with the proper query. <strong>You can also use paths for document IDs</strong>, but personally I had a hard time figuring it out on the server and ended up migrating to query strings, which are also super easy to use and flexible. It boils down to personal preference.</p><p>My <code>PreviewTemplate</code> component holds an internal state with a <code>logged</code> <code>boolean</code> that I use for simple (and insecure) client-side authentication, in which the user is prompted with a login form with hard-coded credentials: if they submit the correct user/pswd, this internal state gets updated with <code>logged: true</code> and we save this info to <code>localStorage</code>... but this is outside of the scope of this tutorial (lemme know if you wanna learn more about this), so here&#x27;s a summarized version of this component:</p><div class="code-block" data-explanation="false"><pre class="language-jsx"><span class="token comment">// ...</span>
<span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> queryString <span class="token keyword">from</span> <span class="token string">'query-string'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">PreviewTemplate</span> <span class="token keyword">extends</span> <span class="token class-name">React<span class="token punctuation">.</span>Component</span> <span class="token punctuation">{</span>
  <span class="token comment">// Initial state</span>
  state <span class="token operator">=</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">isLoading</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
    <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  <span class="token comment">// Method for fetching data and updating state</span>
  <span class="token keyword">public</span> <span class="token function-variable function">fetchData</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token comment">// Parse the query from the location prop</span>
    <span class="token keyword">const</span> query <span class="token operator">=</span> queryString<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// Get the pageId and isDraft from the generated object</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span> pageId<span class="token punctuation">,</span> isDraft <span class="token punctuation">}</span> <span class="token operator">=</span> query<span class="token punctuation">;</span>
    <span class="token comment">// Fetch data from Sanity by using a helper function</span>
    <span class="token keyword">const</span> sanityData <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetchDataFromSanity</span><span class="token punctuation">(</span>pageId<span class="token punctuation">,</span> isDraft<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// If there's data, send it to state</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>sanityData<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        <span class="token literal-property property">isLoading</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
        <span class="token literal-property property">data</span><span class="token operator">:</span> sanityData<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        <span class="token literal-property property">isLoading</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  <span class="token comment">// When the component is first rendered, fetch data and,</span>
  <span class="token comment">// if it's a draft, listen to changes</span>
  <span class="token keyword">public</span> <span class="token function">componentDidMount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> query <span class="token operator">=</span> queryString<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span> pageId<span class="token punctuation">,</span> isDraft <span class="token punctuation">}</span> <span class="token operator">=</span> query<span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">fetchData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>isDraft<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// the subscribeToData helper function runs sanity-client's listen</span>
      <span class="token comment">// method to create an observable that runs a callback function</span>
      <span class="token comment">// every time the data is changed (in this case, this.fetchData)</span>
      <span class="token function">subscribeToData</span><span class="token punctuation">(</span>pageId<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>fetchData<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">public</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">state</span><span class="token operator">:</span> <span class="token punctuation">{</span> isLoading<span class="token punctuation">,</span> data <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>isLoading<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// If the data is still being fetched for the first time, return a loading</span>
      <span class="token comment">// component (just a centralized h1 with "Loading..." as content)</span>
      <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">LoadingDiv</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>data<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Data not found :('</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token function">navigate</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token comment">// Finally, if it's not loading and there's data, render the desired</span>
      <span class="token comment">// PageTemplate. Here you could do conditionals on the document type</span>
      <span class="token comment">// and render different templates as needed ;)</span>
      <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">PageTemplate</span></span> <span class="token attr-name">data</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>data<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><br /></pre></div><p><strong>Breaking down the above component:</strong></p><ol><li>We hold a <code>isLoading</code> <code>boolean</code> and a <code>data</code> object in the state to update the <code>PageTemplate</code> component accordingly;</li><li>Upon mounting, the component runs a <code>fetchData</code> internal method and, if the document is a draft (told by the query param <code>isDraft</code>), also subscribe to changes to the document;</li><li>The <code>fetchData</code> method parses the query string and send the <code>pageId</code> and <code>isDraft</code> variables to a helper function <code>fetchDataFromSanity</code>, stored in another file, that grabs the data based on the Sanity client and normalizes it to my needs.</li><li><strong>This step is crucial:</strong> you must configure your client properly to handle drafts, get images&#x27; URLs with <code>@sanity/image-url</code> if you want to preserve hotspots and crops, normalize the data as your components need, etc.</li><li>I didn&#x27;t include this function here because, as I&#x27;ve said before, it&#x27;s highly specific to my use-case, but if you want you can take a look at a <a href="https://gist.github.com/hdoro/fc02049d78c41dee45eebfa0a0e8e908">gist containing both</a>;</li><li>If you&#x27;re using <code>gatsby-image</code> you won&#x27;t be able to use <code>gatsby-plugin-sharp</code> to generate fixed or fluid sources, so be sure to have a conditional on your components with images in order to render regular <code>&lt;img&gt;</code> tags!</li><li>Then we render a template for our page, passing down the data from state. If it&#x27;s still loading or there&#x27;s no data, we handle them accordingly with simple conditionals in the rendering.</li></ol><p>And that&#x27;s it! You should be good to go if you&#x27;ve did a good job at normalizing the data for your page template&#x27;s needs. To test in development, go into your <code>getPreviewUrl.js</code> file in the Sanity repo/folder and change <code>https://yourUrl.com</code> with your localhost equivalent, run <code>sanity start</code> and navigate to a document you want to open the preview of ;)</p><h2 id="heading-01819191514198939-09138285803514496"><a href="#heading-01819191514198939-09138285803514496" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Caveats and limitations</h2><p>As much as this is liberating for me and amazing for the clients, adding more value to my work without much effort, there are a few caveats I should point:</p><ul><li>It&#x27;s not a one-size-fit-all type of solution, and potentially requires a lot of time to deal with the data coming in. That&#x27;s why you should understand the process before applying it, so then you can replicate it easily to other projects. Unfortunately, though, as far as I&#x27;m concerned this can&#x27;t be turned into a plugin of sorts;</li><li>You can&#x27;t do server-side manipulation of the data, such as Gatsby&#x27;s <code>gatsby-transformer-sharp</code> plugin that allows you to process images and serve them super optimized. Make sure you have fallbacks for components that rely on these;</li><li>Sanity&#x27;s observables can&#x27;t return referenced documents&#x27; data in the <code>result</code> property from the returned object emitted by the <code>listen</code> function. That&#x27;s why I pass down the <code>fetchData</code> callback to the listener, essentially re-querying my API twice. This comes at obvious performance issues but is nothing major in my experience.</li></ul><p>That&#x27;s all I know for now, hit me up if you have anything to add! I hope this guide has been useful to put you on the path of making &quot;static&quot; websites even more amazing and abandoning traditional CMSs for good, hehe</p></div><link rel="stylesheet" href="/prism.css"/>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
        <item>
            <title><![CDATA[The insane quest for the perfect CMS ends in Sanity]]></title>
            <link>https://hdoro.dev/insane-quest-perfect-cms-sanity</link>
            <guid>https://hdoro.dev/insane-quest-perfect-cms-sanity</guid>
            <pubDate>Mon, 06 Aug 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[Why I think Sanity.io has the best offering for developers in search of a flexible, powerful and future-proof tool for content management.]]></description>
            <content:encoded><![CDATA[<div class=""><h2 id="heading-09490870082882019-048455462796914195"><a href="#heading-09490870082882019-048455462796914195" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>TL;DR</h2><ol><li>Traditional CMSs might offer a great ecosystem (namely, WordPress) and/or awesome editing experience (<a href="https://ghost.org">Ghost</a>, <a href="https://getdirectus.com/">Directus</a>), but they hinder my workflow, limit some design decisions and - for the most part - end up in not-so-performant websites;</li><li>Therefore, <a href="https://headlesscms.org">headless CMSs</a> are the answer, but they can be costly, hard to model the data or offer a poor editing interface;</li><li><a href="https://sanity.io">Sanity</a> is the product that checks all boxes and more. From amazing front-end to incredibly easy and capable data modeling through JS objects, from their own simple and flexible query language (GROQ) to easy extensibility through React components, it&#x27;s my dream CMS, and its free plan is <a href="https://www.sanity.io/pricing">quite generous</a>.</li></ol><p>In my quest to find the perfect CMS to fit my workflow with clients, I&#x27;ve came across a ton of different options, but they all had their flaws that either decreased my design freedom and content possibilities or made it hard - sometimes impossible - for the client to update parts of the content.</p><div class="Callout_root__Mdx5a "><div class="Callout_icon__MHehf">Note</div><div class=""><p>This post isn’t sponsored by Sanity, I genuinely love the product.</p><p>My point of view is that of a web dev/designer creating content-heavy websites. Sanity won&#x27;t be so magical for everybody.</p></div></div><h2 id="heading-006786730587426826-019849316214833057"><a href="#heading-006786730587426826-019849316214833057" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>The traditional CMS&#x27;s pitfall</h2><p>Even though I understand the value of traditional CMSs that force you to use their coupled front-end infrastructure, such as WordPress and Drupal, I&#x27;ve came to a conclusion that <strong>they hinder my workflow</strong>.</p><p>Whereas using a static site generator such as <a href="https://gohugo.io">Hugo</a> or <a href="https://gatsbyjs.org">Gatsby</a> I can bootstrap a project from a custom starter and have a complex landing page live - with styles, data fetching from a CMS and all - without worrying about servers in less than 8 hours, with WordPress (which is the one I&#x27;m most used to) I&#x27;d spend at least 1 hour just figuring out the server, installing a starter theme like <a href="https://underscores.me/">Underscores</a> and setting a .sass workflow. And that&#x27;s without all the awesome hot module replacement and templating freedom that I get with static sites!</p><p>I gave up on WordPress when our agency, <a href="https://kaordi.ca">Kaordica</a>, started going after bigger clients and doing more customized websites. Before, I went along fine with WP as I could find a cool theme, customize it as the client wanted and deliver it fast, but then, when our process began including building the design from scratch, themes couldn&#x27;t help me anymore. I had to create my own themes, and <strong>that was painful</strong>.</p><p>I have a lot to say about this, especially because following 2 courses wasn&#x27;t enough to make me proficient in creating themes, but I&#x27;ll keep this to another possible post on why I dropped WordPress from my life. Point is, <strong>our business couldn&#x27;t afford the shackles of traditional CMSs</strong> with such a small team.</p><h2 id="heading-06523252978530678-010117619905695907"><a href="#heading-06523252978530678-010117619905695907" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Headless CMSs: a fresh and bloated market</h2><p>Turns out I&#x27;m not the only person coming to the same conclusion (<em>duh</em>) and opting for decoupled CMSs (which I&#x27;m using as an interchangeable term for <em>headless</em>, although it appears to have <a href="https://en.wikipedia.org/wiki/Headless_CMS#Decoupled_CMS_vs._Headless_CMS">some differences</a>)... in fact, the <a href="https://jamstack.org">JAMStack</a> approach is getting a lot of traction, there&#x27;s a new static site generator popping every day - with over 230 of them listed on <a href="https://www.staticgen.com/">staticgen</a> - and several businesses are seeing the opportunity in the headless CMS field.</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:3.122340425531915"><div style="width:100%;padding-bottom:32.02725724020443%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/2d9037444771a60a8a9bfdde62333b729284afef-1761x564.jpg?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">Image showing the amount of static site generators listed on staticgen.com</figcaption></figure><p>This is amazing, of course, as it gives us plenty of choices, from CMSs with data stored in Firebase (<a href="https://flamelink.io/">Flamelink</a>, <a href="https://www.pushtable.com/">Pushtable</a> and <a href="https://www.canner.io/">Canner</a>) to git-based solutions that update markdown or JSON files, preserving version control (<a href="https://www.siteleaf.com/">SiteLeaf</a>, <a href="https://prose.io/">Prose</a>, etc.), we&#x27;ve got it all... But with 50 options (and growing) in <a href="https://headlesscms.org/">headlesscms.org</a>, it becomes nearly impossible to: A. know which is the best for your use case; and B. rely on the companies behind some of the CMSs.</p><p>Of course this comes from the fact that this market is new and far from consolidated - I have no historical background on this, but I assure this idea is quite young - and for such we must be careful in the solutions we choose for our businesses. Choosing a hosted CMS that goes out of business a year later will get you into trouble, as well as investing heavily in processes and tooling around an open source option that will stop evolving as soon as the hype dies will possibly make you wish you stayed with good &#x27;ol WordPress.</p><p>Before this post becomes unbearably long, I just want to say this: most CMSs I took a look at (I&#x27;d say around 10 - 15 of them) had good ideas going on, but, as a whole, were <strong>lacking in very serious aspects</strong> such as UX for content editors, flexibility for devs or pricing for businesses.</p><p><a href="https://strapi.io/">Strapi</a> felt super buggy and incomplete (and hosting a server is always a pain), <a href="https://www.netlifycms.org/">NetlifyCMS</a> was a pain to manage media (which <a href="https://www.netlifycms.org/blog/2018/09/netlify-cms-2-1-0-adds-external-media-support-with-uploadcare">they seemed to have fixed</a>) and was quite slow to do everything in it, with the developing experience being based in a (super painful) trial and error, and <a href="https://www.contentful.com/">Contentful</a> is clearly geared towards bigger organizations, with absurd pricing for anyone that doesn&#x27;t earn in USD or another strong currency - plus, I didn&#x27;t find the editing interface very friendly and extensibility didn&#x27;t seem so flexible for many use cases...</p><figure><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.5106132075471699"><div style="width:100%;padding-bottom:66.19828259172522%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/ec50f9fc5fbbe7de12ce2b90fbf50368c9194b9e-1281x848.jpg?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><figcaption class="text-mono">NetlifyCMS&#x27;s homepage</figcaption></figure><p>The people behind NetlifyCMS are doing a great job by providing an amazing tool, but the design language of the project is far from user-friendly.</p><h2 id="heading-09402455170741948-04507152677063835"><a href="#heading-09402455170741948-04507152677063835" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>The <em>sane</em> answer</h2><p>But then, when I was almost settling down with an unhappy choice, I found Sanity, which hopefully will get you excited with the following:</p><ul><li>It&#x27;s cheap and reliable<ul><li>For most build-time generated static sites, you&#x27;ll never leave the free plan, with <strong>10k documents and 5GB assets for free</strong>;</li><li>The CMS comes from an established and mature agency from Oslo, Norway called <a href="http://bengler.no/">Bengler</a> which was extremely capable when it came to data (or at least they did a good job at selling themselves in videos and their website haha);</li><li>Bengler closed its doors to focus on Sanity, with a proper infrastructure, a solid team and some <a href="https://www.crunchbase.com/organization/sanity">seed funding</a> behind their backs. It&#x27;s probably not going anywhere.</li></ul></li><li>It&#x27;s modern and flexible<ul><li>They have their own query language - <a href="https://www.sanity.io/docs/data-store/how-queries-work">GROQ</a> - that is both super easy to pick-up (you&#x27;ll be writing queries in less then 5 minutes, guarantee) and EXTREMELY flexible. Personally, I like the syntax better than GraphQL and its philosophy fits my workflow better (yes yes, I know they&#x27;re fundamentally different and bla bla bla).</li><li>It&#x27;s built on top of <a href="https://reactjs.org">React</a> - meaning you can easily modify the studio (how they call the editor UI) and add new components for editing - and it&#x27;s easily pluggable into any front-end framework. Oh, and if you want you can always create your own UI for specific use-cases, such as a landing page builder, who knows.</li></ul></li><li>It&#x27;s easy for both devs and editors<ul><li>Their studio UI is super clean, clear and <em>tasty</em> to use. Honestly, after buggy Strapi, bloated WP, inconsistent NetlifyCMS, i-dont-know-what-bothers-me-in-it Contentful and other traumatic experiences, Sanity&#x27;s editing experience is so glorious I knew from the very moment that was the CMS for me - like love in first sight. Ok, yeah, It&#x27;s not perfect, but they&#x27;ve just hired a designer to work on it and you can always make it your own, give it a chance, they&#x27;re trying!</li><li>And you can easily build your schema with JS objects that you can re-utilize for other projects, how awesome is that? I&#x27;m starting to build a &quot;CMS library&quot; for our company and let me tell you, it&#x27;s helping a ton! You could plug objects and document types to <a href="http://bitsrc.io">bit</a> and publish them to npm, for example, and then use them universally throughout your projects. <strong>It&#x27;s an agency&#x27;s dream, guarantee.</strong></li></ul></li></ul><h3 id="heading-014364276217514638-06267859307681758"><a href="#heading-014364276217514638-06267859307681758" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Example of things you can do with Sanity</h3><ul><li>Live preview changes in static site (which requires a front-end framework and wouldn&#x27;t work with, say, Hugo or Jekyll) - <a href="https://hdoro.dev/live-preview-changes-to-react-websites-with-sanity">learn how here</a>;</li><li>Create your own editor UI without having to worry about the infrastructure of the back-end - Sanity&#x27;s documentation is quite easy to follow and personally I&#x27;ve found it very easy to write stuff to the database.</li><li>Code custom components, based in React, for modeling the data as you see fit.</li><li>Create different tools for the CMS with specific routes that could gather and manipulate data from anywhere.</li><li>Easily customize the look of the editor to fit your clients&#x27; brands.</li><li>Realtime collaboration on documents.</li><li>Extend the block editor with your own components. A good example of this is a <a href="https://github.com/sanity-io/latex-input">LaTeX input</a> that automatically renders a preview in the editor.</li><li>Do some really crazy validations with a <a href="https://www.sanity.io/docs/content-studio/validation">super clear and flexible validation API</a>.</li><li>Have a collection of SVG icons that editors can browse and use easily (image below).</li><li>And much more, as always, the sky is the limit!</li></ul><div class=" lazy-img" data-src="https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1300&amp;fit=max&amp;auto=format" data-srcset=" https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" data-sizes="(max-width: 800px) 100vw, 800px" style="--img-ratio:1.966403162055336"><div style="width:100%;padding-bottom:50.85427135678392%" aria-hidden="true" class="lazy-img__spacer"></div><div class="lazy-img__container"></div><noscript><img srcSet=" https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=100&amp;fit=max&amp;auto=format 100w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=300&amp;fit=max&amp;auto=format 300w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=500&amp;fit=max&amp;auto=format 500w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=700&amp;fit=max&amp;auto=format 700w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=900&amp;fit=max&amp;auto=format 900w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1100&amp;fit=max&amp;auto=format 1100w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1300&amp;fit=max&amp;auto=format 1300w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1500&amp;fit=max&amp;auto=format 1500w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1700&amp;fit=max&amp;auto=format 1700w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1900&amp;fit=max&amp;auto=format 1900w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=2100&amp;fit=max&amp;auto=format 2100w, https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=2300&amp;fit=max&amp;auto=format 2300w" src="https://cdn.sanity.io/images/bmj8cwsg/production/9205752bea22c616446141e5578f16cb6e7bff7b-995x506.jpg?w=1300&amp;fit=max&amp;auto=format" alt="" sizes="(max-width: 800px) 100vw, 800px"/></noscript></div><h3 id="heading-046752191031841495-094435061448501"><a href="#heading-046752191031841495-094435061448501" aria-hidden="true" class="heading-anchor"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-linecap="square" stroke-width="48" d="M200.66 352H144a96 96 0 010-192h55.41m113.18 0H368a96 96 0 010 192h-56.66m-142.27-96h175.86"></path></svg></a>Of course it&#x27;s not all flowers, I&#x27;m not (that) idealist</h3><p>I&#x27;ve had a couple of bugs before (one of them quite serious and infuriating), and there are several stuff I&#x27;d like to see different, but I keep my feet on the ground with my expectations: <strong>there will be no perfect CMS, but at least we can ask for a sane one!</strong></p><p>And honestly Sanity is working hard towards the utopic perfection, their team is super friendly, helping even with free riders like me in using their solution, keeping an open mind and active ear on listening for feedbacks and exploring novel ideas that can one day drive our industry forward. I don&#x27;t know about you, but I&#x27;m pretty sold on this.</p><p><strong>NOTE:</strong> Again, this is not a sponsored post, I really just want to help you as a fellow dev to fulfill your wildest CMS dreams, which, if you have similar use cases, I believe you will by choosing Sanity ;)</p></div>]]></content:encoded>
            <author>meet@hdoro.dev (henrique doro)</author>
        </item>
    </channel>
</rss>