<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Frontend · Jared Lynskey</title>
    <link>https://jared.lynskey.co.nz/en/tags/frontend/</link>
    <description>Short stories of value on software engineering and engineering leadership — performance reviews, hiring and scaling teams, CI/CD, DevOps monitoring and agentic AI. Written from Seoul.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <copyright>© 2026 Jared Lynskey</copyright>
    <lastBuildDate>Wed, 28 Oct 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/en/tags/frontend/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Orval: Generate a Typed API Client and Mocks From Your OpenAPI Spec</title>
      <link>https://jared.lynskey.co.nz/en/posts/2024/2024-04-10-openapitools/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/en/posts/2024/2024-04-10-openapitools/</guid>
      <pubDate>Wed, 10 Apr 2024 00:30:03 +0000</pubDate>
      <dc:creator>Me</dc:creator>
      <category>API Development</category>
      <category>Orval</category>
      <category>OpenAPI</category>
      <category>TypeScript</category>
      <category>React Query</category>
      <category>MSW</category>
      <category>Frontend</category>
      <category>Backend</category>
      <category>Teamwork</category>
      <category>Efficiency</category>
      <description>Orval reads an OpenAPI spec and generates a typed TypeScript client, React Query hooks and MSW mocks, so nobody hand-writes the API layer. How to set it up, how the mocks work, and how it compares with openapi-generator, openapi-typescript and Hey API.</description>
      <content:encoded><![CDATA[<p>Orval is a code generator that reads an OpenAPI (Swagger) spec and writes your frontend&rsquo;s API layer for you: a typed function or React Query hook for every endpoint, TypeScript models that match the contract exactly, and, if you ask for them, MSW mock handlers filled with Faker data. You commit one config file, run <code>orval</code>, and run it again whenever the spec changes.</p>
<p>In the first post of this series I made the case for agreeing on an OpenAPI contract before anyone writes code. This one is about the tool that makes the contract actually pay off.</p>
<p>Hand-writing the frontend&rsquo;s API layer is one of those jobs nobody admits to enjoying. You copy types out of the backend docs, write the same request wrapper for the fortieth time, and three weeks later someone renames a field and half your calls quietly break. Orval deletes that job. Point it at your OpenAPI spec and it generates the client code for you: typed functions for every endpoint, models that match the contract exactly, and mocks you can develop against before the backend exists.</p>

<h2 class="relative group">Why the frontend team likes it
    <div id="why-the-frontend-team-likes-it" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-the-frontend-team-likes-it" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The mock generation is the underrated part. Once the spec is agreed, the frontend can spin up a fake server and build the entire UI against it, instead of waiting for endpoints that are perpetually &ldquo;almost done&rdquo;. When the real backend lands, you point the client at it and things mostly just work, because both sides were built from the same document.</p>
<p>The other win is that the boring code is now correct by construction. Nobody fat-fingers a field name in a hand-rolled request body, because nobody writes a hand-rolled request body anymore.</p>

<h2 class="relative group">Why the backend team likes it too
    <div id="why-the-backend-team-likes-it-too" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-the-backend-team-likes-it-too" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>The spec being the single source of truth changes the day-to-day conversations. Instead of chat threads asking &ldquo;what does this endpoint return again?&rdquo;, the frontend just reads the generated types. Backend devs get interrupted less, and they get feedback sooner: if the frontend builds against the mocks and something about the API feels awkward, you find out while it&rsquo;s still cheap to change, not after it&rsquo;s in production.</p>

<h2 class="relative group">How it fits the workflow
    <div id="how-it-fits-the-workflow" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-fits-the-workflow" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Nothing fancy. The backend proposes the spec, both teams review it, the frontend generates the client and mocks with Orval and starts building, and the backend implements the real endpoints to the same contract. When the spec changes, both sides regenerate, and the compiler tells you exactly what broke. That last part is the whole point: drift between frontend and backend becomes a compile error instead of a production bug.</p>

<h2 class="relative group">Setting it up
    <div id="setting-it-up" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#setting-it-up" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Install it as a dev dependency, along with whatever the generated code will import. For React Query hooks and mocks that&rsquo;s TanStack Query, MSW and Faker:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install --save-dev orval msw @faker-js/faker
</span></span><span class="line"><span class="cl">npm install @tanstack/react-query</span></span></code></pre></div></div>
<p>Then add an <code>orval.config.ts</code> at the root of the project. This one is close to the example in Orval&rsquo;s own docs:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;orval&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">petstore</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">input</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">target</span><span class="o">:</span> <span class="s1">&#39;./openapi.yaml&#39;</span><span class="p">,</span> <span class="c1">// a path or a URL
</span></span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">output</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">mode</span><span class="o">:</span> <span class="s1">&#39;single&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">target</span><span class="o">:</span> <span class="s1">&#39;./src/api/petstore.ts&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">schemas</span><span class="o">:</span> <span class="s1">&#39;./src/api/model&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">client</span><span class="o">:</span> <span class="s1">&#39;react-query&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">httpClient</span><span class="o">:</span> <span class="s1">&#39;fetch&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">mock</span>: <span class="kt">true</span><span class="p">,</span> <span class="c1">// MSW handlers plus Faker data
</span></span></span><span class="line"><span class="cl">      <span class="nx">override</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">fetch</span><span class="o">:</span> <span class="p">{</span> <span class="nx">includeHttpResponseReturnType</span>: <span class="kt">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div>
<p><code>client</code> decides what you get: plain <code>fetch</code> or <code>axios</code> functions, hooks for React Query, Vue Query, Svelte Query or SWR, Angular services, or Zod schemas. The <code>includeHttpResponseReturnType: false</code> line makes the fetch client return the response body itself rather than <code>{ data, status }</code>, which is usually what a component wants.</p>
<p>Add a script so nobody has to remember the command:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;api&#34;</span><span class="p">:</span> <span class="s2">&#34;orval&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><code>npm run api</code> writes the hooks and models. A component then uses them like any other React Query hook, with the types already filled in:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useListPets</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;./api/petstore&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">PetList() {</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">data</span>: <span class="kt">pets</span><span class="p">,</span> <span class="nx">isLoading</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useListPets</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">isLoading</span><span class="p">)</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="nx">Loading</span><span class="err">…</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">pets</span><span class="o">?</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">pet</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">&lt;</span><span class="nt">li</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">pet</span><span class="p">.</span><span class="nx">id</span><span class="p">}&gt;{</span><span class="nx">pet</span><span class="p">.</span><span class="nx">name</span><span class="p">}&lt;/</span><span class="nt">li</span><span class="p">&gt;)}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>Rename a field in the spec, run <code>npm run api</code> again, and <code>pet.name</code> turns red everywhere it&rsquo;s used. That&rsquo;s the drift check from above, and it costs nothing to run.</p>

<h2 class="relative group">Developing against the mocks
    <div id="developing-against-the-mocks" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#developing-against-the-mocks" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>With <code>mock: true</code>, Orval also writes an MSW request handler for each endpoint, returning Faker data shaped like the response schema, plus a function that returns all of them. Hand that list to MSW&rsquo;s <code>setupWorker</code> in the browser or <code>setupServer</code> in tests and the app runs against a fake backend that already matches the contract. Each handler also takes an override, so a test can return a specific payload, an empty list or an error state without anyone writing a fixture by hand.</p>
<p>That&rsquo;s the part that lets the frontend start on day one. The mocks aren&rsquo;t a separate artefact someone has to maintain; they&rsquo;re regenerated from the same spec as the client, so they can&rsquo;t fall behind it.</p>

<h2 class="relative group">Orval vs openapi-generator, openapi-typescript and Hey API
    <div id="orval-vs-openapi-generator-openapi-typescript-and-hey-api" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#orval-vs-openapi-generator-openapi-typescript-and-hey-api" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>They all start from an OpenAPI spec, but they stop at different places.</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>What it generates</th>
					<th>Best fit</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>Orval</strong></td>
					<td>Typed client functions or framework hooks (React Query, SWR, Vue, Svelte, Angular), models, MSW mocks, Zod schemas</td>
					<td>A TypeScript frontend that wants hooks and mocks from one config</td>
			</tr>
			<tr>
					<td><strong>openapi-generator</strong></td>
					<td>Clients and server stubs in dozens of languages, TypeScript included</td>
					<td>Teams that need the same spec turned into Java, Kotlin, Swift and TypeScript</td>
			</tr>
			<tr>
					<td><strong>openapi-typescript</strong></td>
					<td>Types only, paired with <code>openapi-fetch</code> for a small typed fetch wrapper</td>
					<td>You want the types and nothing else, with almost no runtime code</td>
			</tr>
			<tr>
					<td><strong>Hey API</strong> (<code>@hey-api/openapi-ts</code>)</td>
					<td>A typed SDK, with plugins for TanStack Query, Zod and others</td>
					<td>Similar ground to Orval, built around a plugin system</td>
			</tr>
	</tbody>
</table>
<p>If you only need types, openapi-typescript is the lightest option and it&rsquo;s hard to argue with. openapi-generator earns its place when the spec has to feed more than one language; its TypeScript output works, but it reads like it was written for Java first. Between Orval and Hey API it mostly comes down to taste and which one&rsquo;s output you&rsquo;d rather read. I keep using Orval because the mocks come out of the same run as the hooks, and that&rsquo;s the feature that changed how the two teams work together.</p>
<p>It&rsquo;s not glamorous tooling. It just quietly removes a whole category of arguments between teams, and I&rsquo;ll take that trade every time.</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2024/2024-04-10-openapitools/featured.jpg" medium="image" type="image/jpeg" />
    </item>
    <item>
      <title>API-First Development with OpenAPI: Frontend and Backend in Parallel</title>
      <link>https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-open-api/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-open-api/</guid>
      <pubDate>Sun, 08 Oct 2023 00:30:03 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>OpenAPI</category>
      <category>backend</category>
      <category>frontend</category>
      <category>API design</category>
      <category>software development</category>
      <category>team collaboration</category>
      <description>API-first development in practice: agree on the OpenAPI contract first, stand up a mock server, and the frontend stops waiting on the backend.</description>
      <content:encoded><![CDATA[<p>The most reliable schedule-killer I&rsquo;ve seen isn&rsquo;t hard engineering — it&rsquo;s the frontend sitting around waiting for an endpoint that&rsquo;s &ldquo;almost done&rdquo;. The fix we&rsquo;ve settled on at work is boring and effective: agree on the API contract first, in OpenAPI, before anyone writes code.</p>

<h3 class="relative group">The waiting game
    <div id="the-waiting-game" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-waiting-game" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The traditional flow is sequential. Backend designs and builds the API, and only once the endpoints actually work does frontend start integrating. Every backend delay lands directly on the frontend&rsquo;s calendar, and the frontend&rsquo;s calendar is usually where the release date lives.</p>

<h3 class="relative group">Contract first, code second
    <div id="contract-first-code-second" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#contract-first-code-second" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>With a schema-first flow, both sides start almost at the same time. Before any code exists, the API&rsquo;s structure goes into an OpenAPI schema — every endpoint, request and response model, auth method and error code. That document becomes the contract between frontend and backend.</p>
<p>The payoff is immediate. Everyone understands the feature the same way before it&rsquo;s built. Frontend can build UI while backend is still implementing. Changes move faster because there&rsquo;s one agreed source of truth to update. And you can generate a mock server straight from the schema, so frontend testing starts before a single real endpoint exists.</p>

<h3 class="relative group">How it runs in practice
    <div id="how-it-runs-in-practice" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-runs-in-practice" aria-label="Anchor">#</a>
    </span>
    
</h3>
<ol>
<li>Gather requirements, same as for anything.</li>
<li>Design the schema: endpoints, request/response models, authentication, error codes.</li>
<li>Have both frontend and backend review it — this is where the contract earns its keep.</li>
<li>Generate a mock server (Swagger and Postman both do this) and let frontend integrate against it.</li>
<li>Backend implements the real endpoints to the same schema.</li>
<li>Swap mocks for real endpoints as they land, testing continuously so the contract holds.</li>
<li>Keep talking. The schema is a living document, not a stone tablet.</li>
</ol>

<h3 class="relative group">The catches
    <div id="the-catches" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-catches" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>It isn&rsquo;t free. Writing a thorough schema upfront takes real time. When requirements change, the schema has to change first, and the ripples hit both teams. And everyone needs at least a working familiarity with OpenAPI and the tooling around it.</p>
<p>Even so, I&rsquo;ve never seen the upfront cost lose to the integration chaos it prevents. Frontend starts on day one, and the arguments happen over a document instead of over broken builds.</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-open-api/featured.jpg" medium="image" type="image/jpeg" />
    </item>
    <item>
      <title>Zdog: A Pseudo-3D JavaScript Library, and a Korean Flag for Gwangbokjeol</title>
      <link>https://jared.lynskey.co.nz/en/posts/2019/2019-08-08-gwangbokjeol/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/en/posts/2019/2019-08-08-gwangbokjeol/</guid>
      <pubDate>Thu, 08 Aug 2019 00:00:00 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>Zdog</category>
      <category>JavaScript</category>
      <category>Frontend</category>
      <category>Canvas</category>
      <category>Korea</category>
      <description>Zdog is a small pseudo-3D engine for canvas and SVG: shapes live in 3D space but are drawn flat. A minimal example, and the CodePen where I used it to fly a Korean flag for Gwangbokjeol.</description>
      <content:encoded><![CDATA[<p>Next week is Gwangbokjeol in South Korea. Traditionally people fly the flag outside their home or apartment room. To commemorate this day and to sharpen my JavaScript skills, I decided to take this as an opportunity to apply a nice pseudo 3D library.</p>
<p>Gwangbokjeol (광복절), on 15 August, is Korea&rsquo;s National Liberation Day, marking the end of Japanese colonial rule in 1945. Zdog is a small JavaScript library for drawing 3D-ish illustrations on a <code>&lt;canvas&gt;</code> or in SVG. Its shapes have real positions in 3D space and turn like 3D objects, but they&rsquo;re drawn as flat shapes, so you get something that looks like a flat illustration you can spin rather than a 3D render. That suits something simple like a flag.</p>

<h2 class="relative group">A minimal Zdog example
    <div id="a-minimal-zdog-example" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#a-minimal-zdog-example" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Two shapes, drag to rotate, and an animation loop:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">canvas</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;zdog-canvas&#34;</span> <span class="na">width</span><span class="o">=</span><span class="s">&#34;240&#34;</span> <span class="na">height</span><span class="o">=</span><span class="s">&#34;240&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">canvas</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://unpkg.com/zdog@1/dist/zdog.dist.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">illo</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Zdog</span><span class="p">.</span><span class="nx">Illustration</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">element</span><span class="o">:</span> <span class="s1">&#39;.zdog-canvas&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">dragRotate</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// drag to turn the scene
</span></span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// a square, pushed back
</span></span></span><span class="line"><span class="cl">  <span class="k">new</span> <span class="nx">Zdog</span><span class="p">.</span><span class="nx">Rect</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">addTo</span><span class="o">:</span> <span class="nx">illo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">width</span><span class="o">:</span> <span class="mi">80</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">height</span><span class="o">:</span> <span class="mi">80</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">translate</span><span class="o">:</span> <span class="p">{</span> <span class="nx">z</span><span class="o">:</span> <span class="o">-</span><span class="mi">30</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">stroke</span><span class="o">:</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">color</span><span class="o">:</span> <span class="s1">&#39;#0047a0&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// a filled circle, pulled forward
</span></span></span><span class="line"><span class="cl">  <span class="k">new</span> <span class="nx">Zdog</span><span class="p">.</span><span class="nx">Ellipse</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">addTo</span><span class="o">:</span> <span class="nx">illo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">diameter</span><span class="o">:</span> <span class="mi">60</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">translate</span><span class="o">:</span> <span class="p">{</span> <span class="nx">z</span><span class="o">:</span> <span class="mi">30</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">stroke</span><span class="o">:</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">color</span><span class="o">:</span> <span class="s1">&#39;#cd2e3a&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fill</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">function</span> <span class="nx">animate</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">illo</span><span class="p">.</span><span class="nx">rotate</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mf">0.02</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">illo</span><span class="p">.</span><span class="nx">updateRenderGraph</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">requestAnimationFrame</span><span class="p">(</span><span class="nx">animate</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="nx">animate</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div>
<p>Each shape joins the scene with <code>addTo</code>, and <code>translate.z</code> is what makes it 3D: turn the scene and the circle swings round in front of the square. <code>updateRenderGraph()</code> updates and redraws everything, which is why it sits inside the loop.</p>

<h2 class="relative group">Official Docs
    <div id="official-docs" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#official-docs" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p><a href="https://zzz.dog/"  target="_blank" rel="noreferrer">https://zzz.dog/</a></p>
<p>The code used is available to view on CodePen.</p>

<h2 class="relative group">My Example
    <div id="my-example" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#my-example" aria-label="Anchor">#</a>
    </span>
    
</h2>
<div class="codepen" data-height="700" data-theme-id="light" data-default-tab="result" data-user="jared-lynskey" data-slug-hash="voRGqp" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="광복절">
  <span>See the Pen <a href="https://codepen.io/jared-lynskey/pen/voRGqp/">
  광복절</a> by Jared Lynskey (<a href="https://codepen.io/jared-lynskey">@jared-lynskey</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</div>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2019/2019-08-08-gwangbokjeol/featured.jpeg" medium="image" type="image/jpeg" />
    </item>
    <item>
      <title>How to Use SCSS in Hugo with Hugo Pipes</title>
      <link>https://jared.lynskey.co.nz/en/posts/2019/2019-08-06-understandingsass/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/en/posts/2019/2019-08-06-understandingsass/</guid>
      <pubDate>Tue, 06 Aug 2019 00:00:00 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>css</category>
      <category>frontend</category>
      <category>webdev</category>
      <category>gohugo</category>
      <description>How I set up SCSS compilation on this Hugo site with Hugo Pipes, in four short steps.</description>
      <content:encoded><![CDATA[<p>The CSS for this site had slowly grown into one long, repetitive file, so I finally moved it over to SASS. If you&rsquo;re running a GoHugo site, this is one of those changes that costs you fifteen minutes and pays for itself every time you touch a stylesheet afterwards.</p>
<p>Here&rsquo;s how I set it up.</p>

<h2 class="relative group">Why SASS at all?
    <div id="why-sass-at-all" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-sass-at-all" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Plain CSS gets messy once a site grows past a handful of pages. SASS gives you variables, nested rules, mixins and functions, so things like colours and common layout snippets live in one place instead of being copy-pasted everywhere. The best part for Hugo users is that Hugo Pipes compiles SCSS for you — no separate build tool, no watcher scripts.</p>
<p>Two pages worth keeping open while you work:</p>
<ul>
<li>The <a href="https://sass-lang.com/"  target="_blank" rel="noreferrer">SASS documentation</a></li>
<li>Hugo&rsquo;s <a href="https://gohugo.io/functions/css/sass/"  target="_blank" rel="noreferrer">SCSS/SASS support docs</a></li>
</ul>

<h2 class="relative group">Setting it up
    <div id="setting-it-up" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#setting-it-up" aria-label="Anchor">#</a>
    </span>
    
</h2>

<h3 class="relative group">1. Sort out the assets directory
    <div id="1-sort-out-the-assets-directory" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#1-sort-out-the-assets-directory" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Hugo looks for an <code>assets</code> folder at the root of your project, so create one (or move yours) there. If you&rsquo;d rather keep it nested inside <code>resources</code>, point Hugo at it in <code>config.toml</code>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">assetDir</span> <span class="p">=</span> <span class="s2">&#34;resources/assets&#34;</span></span></span></code></pre></div></div>

<h3 class="relative group">2. Make a <code>sass</code> folder
    <div id="2-make-a-sass-folder" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#2-make-a-sass-folder" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Inside <code>assets</code>, create a folder called <code>sass</code>. Your SCSS files live here.</p>

<h3 class="relative group">3. Add the entry point
    <div id="3-add-the-entry-point" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#3-add-the-entry-point" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>Create <code>main.scss</code> inside the <code>sass</code> folder. This is the file Hugo will compile, so drop in a bit of SCSS to prove it works:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="c1">// assets/sass/main.scss
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Variables
</span></span></span><span class="line"><span class="cl"><span class="nv">$primary-color</span><span class="o">:</span> <span class="mh">#3498db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Mixin
</span></span></span><span class="line"><span class="cl"><span class="k">@mixin</span><span class="nf"> center</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="na">display</span><span class="o">:</span> <span class="ni">flex</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">justify-content</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">align-items</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Styles
</span></span></span><span class="line"><span class="cl"><span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="na">font-family</span><span class="o">:</span> <span class="n">Arial</span><span class="o">,</span> <span class="ni">sans-serif</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">background-color</span><span class="o">:</span> <span class="nv">$primary-color</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">h1</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">@include</span><span class="nd"> center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">color</span><span class="o">:</span> <span class="ni">white</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>

<h3 class="relative group">4. Wire it into your template
    <div id="4-wire-it-into-your-template" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#4-wire-it-into-your-template" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>In your <code>head.html</code> partial, load and minify the compiled stylesheet:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="err">$</span><span class="nx">style</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">resources</span><span class="p">.</span><span class="nx">Get</span><span class="w"> </span><span class="s">&#34;sass/main.scss&#34;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">resources</span><span class="p">.</span><span class="nx">ToCSS</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">resources</span><span class="p">.</span><span class="nx">Minify</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nx">link</span><span class="w"> </span><span class="nx">rel</span><span class="p">=</span><span class="s">&#34;stylesheet&#34;</span><span class="w"> </span><span class="nx">href</span><span class="p">=</span><span class="s">&#34;{{ $style.Permalink }}&#34;</span><span class="p">&gt;</span></span></span></code></pre></div></div>
<p>Just make sure the filename here matches your actual entry point (<code>main.scss</code>).</p>

<h2 class="relative group">Splitting your styles up
    <div id="splitting-your-styles-up" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#splitting-your-styles-up" aria-label="Anchor">#</a>
    </span>
    
</h2>
<p>Once things grow, split the SCSS into partials and import them from <code>main.scss</code>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="c1">// assets/sass/_variables.scss
</span></span></span><span class="line"><span class="cl"><span class="nv">$primary-color</span><span class="o">:</span> <span class="mh">#3498db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// assets/sass/_mixins.scss
</span></span></span><span class="line"><span class="cl"><span class="k">@mixin</span><span class="nf"> center</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="na">display</span><span class="o">:</span> <span class="ni">flex</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">justify-content</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">align-items</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// assets/sass/main.scss
</span></span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;variables&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;mixins&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="na">background-color</span><span class="o">:</span> <span class="nv">$primary-color</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">h1</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">@include</span><span class="nd"> center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">color</span><span class="o">:</span> <span class="ni">white</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>That&rsquo;s honestly all there is to it. Four steps, and Hugo recompiles your styles on every build. I went in expecting a fight with the asset pipeline, and it just worked.</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2019/2019-08-06-understandingsass/featured.jpeg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
