<?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 · 인재덕</title>
    <link>https://jared.lynskey.co.nz/ko/tags/frontend/</link>
    <description>소프트웨어 엔지니어링과 개발 조직 리더십에 대한 이야기. 성과 리뷰, 채용과 팀 확장, CI/CD, 데브옵스 모니터링, 에이전틱 AI. 서울에서 씁니다.</description>
    <generator>Hugo</generator>
    <language>ko</language>
    <copyright>© 2026 인재덕</copyright>
    <lastBuildDate>Wed, 28 Oct 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/ko/tags/frontend/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Orval 사용법: OpenAPI 스펙에서 타입이 잡힌 API 클라이언트와 목(mock) 생성하기</title>
      <link>https://jared.lynskey.co.nz/ko/posts/2024/2024-04-10-openapitools/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ko/posts/2024/2024-04-10-openapitools/</guid>
      <pubDate>Wed, 10 Apr 2024 00:30:03 +0000</pubDate>
      <dc:creator>Jared Lynskey</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은 OpenAPI 스펙을 읽어 타입이 잡힌 TypeScript 클라이언트와 React Query 훅, MSW 목을 생성해 줍니다. API 계층을 손으로 짤 일이 없어지죠. 설정 방법, 목이 동작하는 방식, 그리고 openapi-generator, openapi-typescript, Hey API와의 비교까지 정리했습니다.</description>
      <content:encoded><![CDATA[<p>Orval은 OpenAPI(Swagger) 스펙을 읽어서 프론트엔드의 API 계층을 대신 써 주는 코드 생성기입니다. 엔드포인트마다 타입이 잡힌 함수나 React Query 훅, 계약과 정확히 일치하는 TypeScript 모델, 그리고 원하면 Faker 데이터로 채운 MSW 목 핸들러까지 만들어 줍니다. 설정 파일 하나를 커밋하고 <code>orval</code>을 실행하면 되고, 스펙이 바뀔 때마다 다시 실행하면 됩니다.</p>
<p>이 시리즈의 첫 글에서 &ldquo;코드를 쓰기 전에 OpenAPI 계약부터 합의하자&quot;는 이야기를 했습니다. 이번 글은 그 계약이 실제로 본전을 뽑게 해주는 도구에 대한 이야기입니다.</p>
<p>프론트엔드의 API 계층을 손으로 짜는 건, 즐겁다고 말하는 사람을 본 적이 없는 종류의 일입니다. 백엔드 문서에서 타입을 베껴 적고, 마흔 번째 똑같은 요청 래퍼를 쓰고, 3주 뒤에 누군가 필드명을 바꾸면 호출의 절반이 조용히 깨지죠. Orval은 이 일 자체를 없애줍니다. OpenAPI 스펙을 가리키기만 하면 클라이언트 코드를 생성해줍니다. 모든 엔드포인트에 대한 타입이 잡힌 함수, 계약과 정확히 일치하는 모델, 그리고 백엔드가 존재하기도 전에 개발에 쓸 수 있는 목(mock)까지.</p>

<h2 class="relative group">프론트엔드 팀이 좋아하는 이유
    <div id="프론트엔드-팀이-좋아하는-이유" 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="#%ed%94%84%eb%a1%a0%ed%8a%b8%ec%97%94%eb%93%9c-%ed%8c%80%ec%9d%b4-%ec%a2%8b%ec%95%84%ed%95%98%eb%8a%94-%ec%9d%b4%ec%9c%a0" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>목 생성이 은근히 저평가된 부분입니다. 스펙만 합의되면 프론트엔드는 가짜 서버를 띄워놓고 UI 전체를 거기에 대고 만들 수 있습니다. 영원히 &ldquo;거의 다 됐어요&rdquo; 상태인 엔드포인트를 기다릴 필요가 없죠. 진짜 백엔드가 나오면 클라이언트의 방향만 돌려주면 대부분 그대로 동작합니다. 양쪽이 같은 문서에서 만들어졌으니까요.</p>
<p>또 하나의 수확은, 지루한 코드가 구조적으로 올바르게 나온다는 점입니다. 손으로 짠 요청 바디에서 필드명을 잘못 치는 사람이 없어집니다. 애초에 손으로 요청 바디를 짜는 사람이 없어졌으니까요.</p>

<h2 class="relative group">백엔드 팀도 좋아하는 이유
    <div id="백엔드-팀도-좋아하는-이유" 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="#%eb%b0%b1%ec%97%94%eb%93%9c-%ed%8c%80%eb%8f%84-%ec%a2%8b%ec%95%84%ed%95%98%eb%8a%94-%ec%9d%b4%ec%9c%a0" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>스펙이 단일 진실 공급원이 되면 일상적인 대화가 달라집니다. &ldquo;이 엔드포인트가 뭘 반환하더라?&ldquo;라는 채팅 스레드 대신, 프론트엔드는 생성된 타입을 읽으면 됩니다. 백엔드 개발자는 덜 끊기고, 피드백은 더 빨리 받습니다. 프론트엔드가 목을 상대로 만들어보다가 API가 어딘가 어색하다고 느끼면, 그걸 알게 되는 시점이 프로덕션 이후가 아니라 아직 고치기 싼 시점입니다.</p>

<h2 class="relative group">우리 워크플로에 녹이는 방법
    <div id="우리-워크플로에-녹이는-방법" 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="#%ec%9a%b0%eb%a6%ac-%ec%9b%8c%ed%81%ac%ed%94%8c%eb%a1%9c%ec%97%90-%eb%85%b9%ec%9d%b4%eb%8a%94-%eb%b0%a9%eb%b2%95" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>별다른 건 없습니다. 백엔드가 스펙을 제안하고, 양 팀이 리뷰하고, 프론트엔드는 Orval로 클라이언트와 목을 생성해 개발을 시작하고, 백엔드는 같은 계약에 맞춰 실제 엔드포인트를 구현합니다. 스펙이 바뀌면 양쪽에서 다시 생성하고, 뭐가 깨졌는지는 컴파일러가 정확히 알려줍니다. 사실 이 마지막 부분이 핵심입니다. 프론트와 백엔드 사이의 어긋남이 프로덕션 버그가 아니라 컴파일 에러가 되는 거죠.</p>

<h2 class="relative group">설정하기
    <div id="설정하기" 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="#%ec%84%a4%ec%a0%95%ed%95%98%ea%b8%b0" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>개발 의존성으로 설치하고, 생성된 코드가 import할 패키지도 함께 설치합니다. React Query 훅과 목을 쓸 거라면 TanStack Query, MSW, 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>그다음 프로젝트 루트에 <code>orval.config.ts</code>를 추가합니다. 아래 설정은 Orval 공식 문서의 예제와 거의 같습니다.</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>가 정합니다. 그냥 <code>fetch</code>나 <code>axios</code> 함수, React Query·Vue Query·Svelte Query·SWR용 훅, Angular 서비스, Zod 스키마 중에서 고를 수 있습니다. <code>includeHttpResponseReturnType: false</code> 줄은 fetch 클라이언트가 <code>{ data, status }</code> 대신 응답 바디 자체를 반환하게 하는데, 컴포넌트 입장에서는 대개 이쪽이 편합니다.</p>
<p>명령어를 아무도 외울 필요가 없도록 스크립트도 추가해 둡니다.</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>를 실행하면 훅과 모델이 생성됩니다. 컴포넌트에서는 여느 React Query 훅처럼 쓰면 되고, 타입은 이미 다 채워져 있습니다.</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>스펙에서 필드 이름을 바꾸고 <code>npm run api</code>를 다시 돌리면, <code>pet.name</code>을 쓰는 곳마다 빨간 줄이 그어집니다. 앞에서 말한 어긋남 검사가 바로 이것이고, 돌리는 데 드는 비용은 0입니다.</p>

<h2 class="relative group">목을 상대로 개발하기
    <div id="목을-상대로-개발하기" 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="#%eb%aa%a9%ec%9d%84-%ec%83%81%eb%8c%80%eb%a1%9c-%ea%b0%9c%eb%b0%9c%ed%95%98%ea%b8%b0" aria-label="앵커">#</a>
    </span>
    
</h2>
<p><code>mock: true</code>를 켜면 Orval은 엔드포인트마다 MSW 요청 핸들러도 만듭니다. 응답 스키마 모양 그대로의 Faker 데이터를 돌려주는 핸들러들이고, 이걸 전부 모아 반환하는 함수도 하나 생깁니다. 그 목록을 브라우저에서는 MSW의 <code>setupWorker</code>에, 테스트에서는 <code>setupServer</code>에 넘기면, 앱은 이미 계약과 일치하는 가짜 백엔드를 상대로 돌아갑니다. 핸들러마다 오버라이드도 받을 수 있어서, 특정 페이로드나 빈 목록, 에러 상태를 돌려주는 테스트를 누가 픽스처를 손으로 짜지 않아도 만들 수 있습니다.</p>
<p>프론트엔드가 첫날부터 작업을 시작할 수 있는 건 이 덕분입니다. 목은 누군가 따로 관리해야 하는 별도의 산출물이 아닙니다. 클라이언트와 같은 스펙에서 매번 다시 생성되니, 스펙보다 뒤처질 수가 없죠.</p>

<h2 class="relative group">Orval과 openapi-generator, openapi-typescript, Hey API 비교
    <div id="orval과-openapi-generator-openapi-typescript-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%ea%b3%bc-openapi-generator-openapi-typescript-hey-api-%eb%b9%84%ea%b5%90" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>넷 다 OpenAPI 스펙에서 출발하지만, 멈추는 지점이 다릅니다.</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>생성하는 것</th>
					<th>잘 맞는 경우</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>Orval</strong></td>
					<td>타입이 잡힌 클라이언트 함수나 프레임워크 훅(React Query, SWR, Vue, Svelte, Angular), 모델, MSW 목, Zod 스키마</td>
					<td>설정 하나로 훅과 목을 같이 뽑고 싶은 TypeScript 프론트엔드</td>
			</tr>
			<tr>
					<td><strong>openapi-generator</strong></td>
					<td>수십 개 언어의 클라이언트와 서버 스텁(TypeScript 포함)</td>
					<td>같은 스펙을 Java, Kotlin, Swift, TypeScript로 모두 뽑아야 하는 팀</td>
			</tr>
			<tr>
					<td><strong>openapi-typescript</strong></td>
					<td>타입만. 작은 타입 fetch 래퍼인 <code>openapi-fetch</code>와 짝을 이룸</td>
					<td>런타임 코드는 거의 없이 타입만 원할 때</td>
			</tr>
			<tr>
					<td><strong>Hey API</strong> (<code>@hey-api/openapi-ts</code>)</td>
					<td>타입이 잡힌 SDK. TanStack Query, Zod 등을 위한 플러그인 제공</td>
					<td>Orval과 비슷한 영역을 플러그인 시스템 중심으로 풀어낸 도구</td>
			</tr>
	</tbody>
</table>
<p>타입만 필요하다면 openapi-typescript가 가장 가볍고, 딱히 반박할 거리도 없습니다. openapi-generator는 스펙 하나로 여러 언어를 먹여 살려야 할 때 제값을 합니다. TypeScript 출력도 동작은 하지만, 읽다 보면 Java를 먼저 염두에 두고 만든 티가 납니다. Orval과 Hey API 사이에서는 대체로 취향 문제이고, 어느 쪽이 내놓는 코드를 읽는 게 더 편한지로 갈립니다. 제가 계속 Orval을 쓰는 건 목이 훅과 같은 실행에서 함께 나오기 때문이고, 두 팀이 일하는 방식을 바꾼 게 바로 그 기능이었습니다.</p>
<p>화려한 도구는 아닙니다. 그저 팀 사이의 말다툼 한 종류를 통째로 조용히 없애줄 뿐인데, 저는 그 거래라면 언제든 응할 생각입니다.</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 우선 개발(API-First)과 OpenAPI: 프론트엔드와 백엔드를 병렬로 개발하기</title>
      <link>https://jared.lynskey.co.nz/ko/posts/2023/2023-10-08-open-api/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ko/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 우선 개발을 실무에서 하는 법: OpenAPI 계약에 먼저 합의하고 목 서버를 띄우면, 프론트엔드가 더 이상 백엔드를 기다리지 않아도 됩니다.</description>
      <content:encoded><![CDATA[<p>지금까지 본 것 중 일정을 가장 확실하게 망치는 건 어려운 엔지니어링이 아닙니다. &ldquo;거의 다 됐다&quot;는 엔드포인트를 프론트엔드가 하염없이 기다리는 상황이죠. 회사에서 정착시킨 해법은 지루하지만 잘 듣습니다. 코드를 쓰기 전에 OpenAPI로 API 계약부터 합의하는 겁니다.</p>

<h3 class="relative group">기다림의 게임
    <div id="기다림의-게임" 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="#%ea%b8%b0%eb%8b%a4%eb%a6%bc%ec%9d%98-%ea%b2%8c%ec%9e%84" aria-label="앵커">#</a>
    </span>
    
</h3>
<p>전통적인 흐름은 직렬입니다. 백엔드가 API를 설계하고 구현한 뒤, 엔드포인트가 실제로 동작해야 프론트엔드가 통합을 시작합니다. 백엔드의 지연은 전부 프론트엔드 일정에 그대로 얹히는데, 릴리스 날짜가 적혀 있는 곳이 대개 프론트엔드 일정표입니다.</p>

<h3 class="relative group">코드보다 계약이 먼저
    <div id="코드보다-계약이-먼저" 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="#%ec%bd%94%eb%93%9c%eb%b3%b4%eb%8b%a4-%ea%b3%84%ec%95%bd%ec%9d%b4-%eb%a8%bc%ec%a0%80" aria-label="앵커">#</a>
    </span>
    
</h3>
<p>스키마 우선 흐름에서는 양쪽이 거의 동시에 출발합니다. 코드가 한 줄도 없는 시점에 API 구조를 OpenAPI 스키마로 적어 둡니다. 모든 엔드포인트, 요청·응답 모델, 인증 방식, 에러 코드까지요. 이 문서가 프론트엔드와 백엔드 사이의 계약서가 됩니다.</p>
<p>효과는 바로 나타납니다. 만들기 전에 모두가 기능을 같은 모양으로 이해하게 됩니다. 백엔드가 구현 중인 동안 프론트엔드는 UI를 만들 수 있고요. 합의된 단일 진실 공급원 하나만 고치면 되니 변경도 빨리 돕니다. 게다가 스키마에서 바로 목 서버를 생성할 수 있어서, 진짜 엔드포인트가 하나도 없는 단계부터 프론트엔드 테스트가 시작됩니다.</p>

<h3 class="relative group">실제로 돌리는 순서
    <div id="실제로-돌리는-순서" 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="#%ec%8b%a4%ec%a0%9c%eb%a1%9c-%eb%8f%8c%eb%a6%ac%eb%8a%94-%ec%88%9c%ec%84%9c" aria-label="앵커">#</a>
    </span>
    
</h3>
<ol>
<li>요구사항을 모읍니다. 뭘 만들든 똑같죠.</li>
<li>스키마를 설계합니다. 엔드포인트, 요청/응답 모델, 인증, 에러 코드.</li>
<li>프론트엔드와 백엔드 양쪽이 함께 리뷰합니다. 계약이 제값을 하는 순간이 바로 여기입니다.</li>
<li>목 서버를 생성하고(Swagger, Postman 둘 다 됩니다) 프론트엔드는 그걸 상대로 통합을 진행합니다.</li>
<li>백엔드는 같은 스키마에 맞춰 진짜 엔드포인트를 구현합니다.</li>
<li>완성되는 대로 목을 진짜 엔드포인트로 갈아 끼우고, 계약이 지켜지는지 계속 테스트합니다.</li>
<li>대화를 이어갑니다. 스키마는 살아 있는 문서지 석판이 아니니까요.</li>
</ol>

<h3 class="relative group">걸리는 부분
    <div id="걸리는-부분" 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="#%ea%b1%b8%eb%a6%ac%eb%8a%94-%eb%b6%80%eb%b6%84" aria-label="앵커">#</a>
    </span>
    
</h3>
<p>공짜는 아닙니다. 꼼꼼한 스키마를 미리 쓰는 데는 실제로 시간이 듭니다. 요구사항이 바뀌면 스키마부터 고쳐야 하고, 그 파장은 두 팀 모두에 미칩니다. 그리고 전원이 OpenAPI와 주변 도구에 최소한의 감은 갖고 있어야 하죠.</p>
<p>그래도 이 선행 투자가, 그 덕에 막을 수 있는 통합 지옥에 지는 걸 본 적이 없습니다. 프론트엔드는 첫날부터 달리기 시작하고, 논쟁은 깨진 빌드가 아니라 문서 위에서 벌어지게 됩니다.</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: 유사 3D JavaScript 라이브러리로 광복절 태극기 띄우기</title>
      <link>https://jared.lynskey.co.nz/ko/posts/2019/2019-08-08-gwangbokjeol/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ko/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은 canvas와 SVG용 작은 유사 3D 엔진입니다. 도형은 3D 공간에 있지만 평면으로 그려지죠. 최소한의 예제 코드와, 광복절을 맞아 태극기를 띄운 CodePen을 소개합니다.</description>
      <content:encoded><![CDATA[<p>다음 주는 광복절입니다. 이날이면 다들 집이나 아파트 창밖에 태극기를 내걸죠. 이날을 기념할 겸, JavaScript 실력도 갈고닦을 겸, 눈여겨보던 유사 3D 라이브러리를 한번 써 보기로 했습니다.</p>
<p>광복절(8월 15일)은 1945년 일제강점기가 끝난 것을 기념하는 날이죠. Zdog은 <code>&lt;canvas&gt;</code>나 SVG에 3D 느낌의 일러스트를 그리는 작은 JavaScript 라이브러리입니다. 도형은 실제로 3D 공간 안에 놓이고 3D 물체처럼 돌아가지만, 그려지는 건 평면 도형이라 3D 렌더링이라기보다 돌려 볼 수 있는 평면 일러스트에 가깝습니다. 깃발처럼 단순한 걸 만들기에 딱 맞죠.</p>

<h2 class="relative group">Zdog 최소 예제
    <div id="zdog-최소-예제" 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="#zdog-%ec%b5%9c%ec%86%8c-%ec%98%88%ec%a0%9c" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>도형 두 개, 드래그로 회전, 그리고 애니메이션 루프입니다.</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>도형은 <code>addTo</code>로 장면에 붙이고, 3D가 되는 건 <code>translate.z</code> 덕분입니다. 장면을 돌리면 원이 사각형 앞으로 돌아 나오죠. <code>updateRenderGraph()</code>가 전체를 갱신하고 다시 그리기 때문에 루프 안에 들어가 있습니다.</p>

<h2 class="relative group">공식 문서
    <div id="공식-문서" 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="#%ea%b3%b5%ec%8b%9d-%eb%ac%b8%ec%84%9c" aria-label="앵커">#</a>
    </span>
    
</h2>
<p><a href="https://zzz.dog/"  target="_blank" rel="noreferrer">https://zzz.dog/</a></p>
<p>사용한 코드는 CodePen에서 볼 수 있습니다.</p>

<h2 class="relative group">제가 만든 예제
    <div id="제가-만든-예제" 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="#%ec%a0%9c%ea%b0%80-%eb%a7%8c%eb%93%a0-%ec%98%88%ec%a0%9c" aria-label="앵커">#</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>Hugo에서 SCSS 사용하는 법: Hugo Pipes로 컴파일하기</title>
      <link>https://jared.lynskey.co.nz/ko/posts/2019/2019-08-06-understandingsass/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ko/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>Hugo에서 SCSS(Sass)를 쓰는 방법입니다. 이 사이트에 Hugo Pipes로 SCSS 컴파일을 붙인 과정을 짧은 네 단계로 정리했습니다.</description>
      <content:encoded><![CDATA[<p>이 사이트의 CSS가 어느새 길고 반복투성이인 파일 하나로 자라 버려서, 드디어 SASS로 옮겼습니다. GoHugo로 사이트를 운영하고 있다면, 15분만 들이면 그 뒤로 스타일시트를 만질 때마다 본전을 뽑는 종류의 작업이죠.</p>
<p>제가 설정한 과정을 정리해 둡니다.</p>

<h2 class="relative group">굳이 SASS를 쓰는 이유
    <div id="굳이-sass를-쓰는-이유" 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="#%ea%b5%b3%ec%9d%b4-sass%eb%a5%bc-%ec%93%b0%eb%8a%94-%ec%9d%b4%ec%9c%a0" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>순수 CSS는 페이지가 조금만 늘어나도 금세 지저분해집니다. SASS를 쓰면 변수, 중첩 규칙, 믹스인, 함수를 쓸 수 있어서 색상이나 공통 레이아웃 정의를 여기저기 복붙하지 않고 한곳에 모아둘 수 있습니다. Hugo 사용자에게 제일 반가운 점은, Hugo Pipes가 SCSS를 알아서 컴파일해 준다는 것. 별도의 빌드 도구도, 워처 스크립트도 필요 없습니다.</p>
<p>작업하는 동안 열어두면 좋은 페이지 두 곳:</p>
<ul>
<li><a href="https://sass-lang.com/"  target="_blank" rel="noreferrer">SASS 공식 문서</a></li>
<li>Hugo의 <a href="https://gohugo.io/functions/css/sass/"  target="_blank" rel="noreferrer">SCSS/SASS 지원 문서</a></li>
</ul>

<h2 class="relative group">설정하기
    <div id="설정하기" 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="#%ec%84%a4%ec%a0%95%ed%95%98%ea%b8%b0" aria-label="앵커">#</a>
    </span>
    
</h2>

<h3 class="relative group">1. assets 디렉토리 준비
    <div id="1-assets-디렉토리-준비" 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-assets-%eb%94%94%eb%a0%89%ed%86%a0%eb%a6%ac-%ec%a4%80%eb%b9%84" aria-label="앵커">#</a>
    </span>
    
</h3>
<p>Hugo는 프로젝트 루트의 <code>assets</code> 폴더를 찾기 때문에, 그 위치에 폴더를 만들거나 옮겨 주세요. <code>resources</code> 폴더 안에 두고 싶다면 <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. <code>sass</code> 폴더 만들기
    <div id="2-sass-폴더-만들기" 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-sass-%ed%8f%b4%eb%8d%94-%eb%a7%8c%eb%93%a4%ea%b8%b0" aria-label="앵커">#</a>
    </span>
    
</h3>
<p><code>assets</code> 안에 <code>sass</code>라는 폴더를 만듭니다. SCSS 파일은 전부 여기에 둡니다.</p>

<h3 class="relative group">3. 진입점 파일 추가
    <div id="3-진입점-파일-추가" 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-%ec%a7%84%ec%9e%85%ec%a0%90-%ed%8c%8c%ec%9d%bc-%ec%b6%94%ea%b0%80" aria-label="앵커">#</a>
    </span>
    
</h3>
<p><code>sass</code> 폴더 안에 <code>main.scss</code>를 만듭니다. Hugo가 컴파일하는 파일이 바로 이것이니, 동작 확인 겸 SCSS 코드를 조금 넣어 봅시다.</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">// 변수
</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">// 믹스인
</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">// 스타일
</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. 템플릿에 연결
    <div id="4-템플릿에-연결" 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-%ed%85%9c%ed%94%8c%eb%a6%bf%ec%97%90-%ec%97%b0%ea%b2%b0" aria-label="앵커">#</a>
    </span>
    
</h3>
<p><code>head.html</code> 파셜에서 컴파일된 스타일시트를 불러와 압축합니다.</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>여기 적은 파일명이 실제 진입점(<code>main.scss</code>)과 일치하는지만 확인하면 됩니다.</p>

<h2 class="relative group">스타일 파일 쪼개기
    <div id="스타일-파일-쪼개기" 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="#%ec%8a%a4%ed%83%80%ec%9d%bc-%ed%8c%8c%ec%9d%bc-%ec%aa%bc%ea%b0%9c%ea%b8%b0" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>프로젝트가 커지면 SCSS를 파셜로 나누고 <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>정말 이게 전부입니다. 네 단계면 끝나고, 빌드할 때마다 Hugo가 스타일을 다시 컴파일해 줍니다. 에셋 파이프라인과 한바탕 씨름할 각오였는데, 허무할 만큼 순순히 돌아가더군요.</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>
