<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cloudflare-Workers on kastori</title><link>http://blog.kastori.dev/tags/cloudflare-workers/</link><description>Recent content in Cloudflare-Workers on kastori</description><generator>Hugo -- gohugo.io</generator><language>ko-kr</language><lastBuildDate>Wed, 20 May 2026 00:00:00 +0900</lastBuildDate><atom:link href="http://blog.kastori.dev/tags/cloudflare-workers/index.xml" rel="self" type="application/rss+xml"/><item><title>Hugo + Stack 테마로 기술 블로그 만들기</title><link>http://blog.kastori.dev/log/2026-05-20-hugo-blog-setup/</link><pubDate>Wed, 20 May 2026 00:00:00 +0900</pubDate><guid>http://blog.kastori.dev/log/2026-05-20-hugo-blog-setup/</guid><description>&lt;h2 id="왜-hugo인가"&gt;&lt;a href="#%ec%99%9c-hugo%ec%9d%b8%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;왜 Hugo인가
&lt;/h2&gt;&lt;p&gt;정적 사이트 생성기(SSG)를 고를 때 Next.js, Gatsby, Astro도 검토했다. 결국 Hugo를 선택한 이유는 단 하나다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;개발 블로그를 만드는 데 Node.js 생태계의 복잡함이 필요하지 않다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm install&lt;/code&gt; 없이 바이너리 하나로 실행되고, 글 수백 개도 수십 ms에 빌드되며, 배포는 &lt;code&gt;git push&lt;/code&gt; 한 번으로 끝난다. 블로그가 아니라 블로그를 세팅하는 데 시간을 쏟는 상황을 피하고 싶었다.&lt;/p&gt;
&lt;h2 id="레퍼런스-liuhouliangcom"&gt;&lt;a href="#%eb%a0%88%ed%8d%bc%eb%9f%b0%ec%8a%a4-liuhouliangcom" class="header-anchor"&gt;&lt;/a&gt;레퍼런스: liuhouliang.com
&lt;/h2&gt;&lt;p&gt;테마만 가져다 쓰지 않고 &lt;strong&gt;레이아웃 레퍼런스&lt;/strong&gt;를 먼저 정했다. 참고한 사이트는 &lt;a class="link" href="https://liuhouliang.com/en/" target="_blank" rel="noopener"
 &gt;liuhouliang.com&lt;/a&gt;이다. 같은 Stack 테마를 쓰면서도 레이아웃이 훨씬 넓고 카드 간격이 쾌적하게 느껴졌다. 직접 DevTools로 측정해서 맞춘 수치는 다음과 같다.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;항목&lt;/th&gt;
 &lt;th&gt;liuhouliang.com&lt;/th&gt;
 &lt;th&gt;내 블로그&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;전체 컨테이너 너비&lt;/td&gt;
 &lt;td&gt;1600px&lt;/td&gt;
 &lt;td&gt;1600px&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;좌측 사이드바 너비 비율&lt;/td&gt;
 &lt;td&gt;~17%&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;--left-sidebar-max-width: 17%&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;카드 gap&lt;/td&gt;
 &lt;td&gt;30px&lt;/td&gt;
 &lt;td&gt;30px&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Stack 테마 기본값은 컨테이너가 1280px 이하다. &lt;code&gt;custom.scss&lt;/code&gt;에서 &lt;code&gt;.container.extended&lt;/code&gt;를 오버라이드해 맞췄다.&lt;/p&gt;
&lt;h2 id="스택-구성"&gt;&lt;a href="#%ec%8a%a4%ed%83%9d-%ea%b5%ac%ec%84%b1" class="header-anchor"&gt;&lt;/a&gt;스택 구성
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;구성 요소&lt;/th&gt;
 &lt;th&gt;역할&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Hugo v0.161.1 extended&lt;/td&gt;
 &lt;td&gt;정적 사이트 생성기&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Stack 테마 (git submodule)&lt;/td&gt;
 &lt;td&gt;블로그 테마&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;GitHub Pages&lt;/td&gt;
 &lt;td&gt;호스팅&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;GitHub Actions&lt;/td&gt;
 &lt;td&gt;CI/CD 자동 배포&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cloudflare&lt;/td&gt;
 &lt;td&gt;DNS, 커스텀 도메인&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cloudflare Workers + KV&lt;/td&gt;
 &lt;td&gt;글별 조회수 API&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Busuanzi&lt;/td&gt;
 &lt;td&gt;사이트 총 방문자 수&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Giscus&lt;/td&gt;
 &lt;td&gt;댓글 (GitHub Discussions 기반)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Cloudflare Workers는 처음부터 계획한 것이 아니라 &amp;ldquo;글마다 조회수를 보여주고 싶다&amp;quot;는 생각에서 나중에 추가했다. 정적 사이트라 서버가 없으니 Workers + KV 조합이 유일한 선택지였다.&lt;/p&gt;
&lt;h2 id="초기-세팅"&gt;&lt;a href="#%ec%b4%88%ea%b8%b0-%ec%84%b8%ed%8c%85" class="header-anchor"&gt;&lt;/a&gt;초기 세팅
&lt;/h2&gt;&lt;h3 id="1-hugo-설치-macos"&gt;&lt;a href="#1-hugo-%ec%84%a4%ec%b9%98-macos" class="header-anchor"&gt;&lt;/a&gt;1. Hugo 설치 (macOS)
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install hugo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# hugo v0.161.1+extended darwin/arm64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;extended&lt;/code&gt; 버전이 필수다. SCSS 컴파일에 필요하다.&lt;/p&gt;
&lt;h3 id="2-새-사이트-생성--stack-테마-적용"&gt;&lt;a href="#2-%ec%83%88-%ec%82%ac%ec%9d%b4%ed%8a%b8-%ec%83%9d%ec%84%b1--stack-%ed%85%8c%eb%a7%88-%ec%a0%81%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;2. 새 사이트 생성 + Stack 테마 적용
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo new site my-blog
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; my-blog
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git init
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule add https://github.com/CaiJimmy/hugo-theme-stack themes/stack
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;테마를 git submodule로 관리하면 &lt;code&gt;git submodule update --remote&lt;/code&gt;로 업데이트할 수 있다. 단, 테마 파일을 직접 수정하면 업데이트 시 충돌이 난다. Hugo의 오버라이드 시스템을 활용해야 한다 — 아래 커스터마이징 섹션 참고.&lt;/p&gt;
&lt;h3 id="3-github-actions-배포"&gt;&lt;a href="#3-github-actions-%eb%b0%b0%ed%8f%ac" class="header-anchor"&gt;&lt;/a&gt;3. GitHub Actions 배포
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt; 이름으로 GitHub 레포를 만들고, Settings → Pages에서 &lt;strong&gt;GitHub Actions&lt;/strong&gt;를 소스로 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.github/workflows/hugo.yml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy Hugo site to Pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;main&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id-token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pages&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HUGO_VERSION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.161.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Hugo CLI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; wget -O hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo dpkg -i hugo.deb&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Checkout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;submodules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;recursive &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 이게 없으면 테마가 빠져서 빌드 실패&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup Pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/configure-pages@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build with Hugo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hugo --minify --baseURL &amp;#34;${{ steps.pages.outputs.base_url }}/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Upload artifact&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/upload-pages-artifact@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./public&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;github-pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ steps.deployment.outputs.page_url }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;needs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy to GitHub Pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/deploy-pages@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="커스텀-도메인-연결-cloudflare"&gt;&lt;a href="#%ec%bb%a4%ec%8a%a4%ed%85%80-%eb%8f%84%eb%a9%94%ec%9d%b8-%ec%97%b0%ea%b2%b0-cloudflare" class="header-anchor"&gt;&lt;/a&gt;커스텀 도메인 연결 (Cloudflare)
&lt;/h2&gt;&lt;h3 id="cname-레코드-추가"&gt;&lt;a href="#cname-%eb%a0%88%ec%bd%94%eb%93%9c-%ec%b6%94%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;CNAME 레코드 추가
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Type&lt;/th&gt;
 &lt;th&gt;Name&lt;/th&gt;
 &lt;th&gt;Content&lt;/th&gt;
 &lt;th&gt;Proxy&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;CNAME&lt;/td&gt;
 &lt;td&gt;blog&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;DNS only (회색 구름)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Proxy(주황 구름) 상태로 두면 안 된다.&lt;/strong&gt; Cloudflare가 트래픽을 가로채면 GitHub Pages의 HTTPS 인증서 발급이 실패한다. &lt;code&gt;DNS only&lt;/code&gt;로 설정해야 GitHub가 직접 TLS를 발급한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;static/CNAME&lt;/code&gt; 파일에 커스텀 도메인을 한 줄로 적어두면 Hugo 빌드 시 &lt;code&gt;public/CNAME&lt;/code&gt;으로 복사되어 GitHub Pages에 전달된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="hugotoml-주요-설정"&gt;&lt;a href="#hugotoml-%ec%a3%bc%ec%9a%94-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;hugo.toml 주요 설정
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;baseURL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://blog.kastori.dev/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ko-kr&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;kastori&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;stack&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;paginate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;hasCJKLanguage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c"&gt;# 한국어 읽기 시간 계산에 필수&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mainSections&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tech&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;til&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;pageviewWorkerUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://pageview.&amp;lt;계정&amp;gt;.workers.dev&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# 조회수 Worker URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sidebar&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;subtitle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;풀스택 개발자의 기술 블로그&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;avatar&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/Kastori1206.png&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;toc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;readingTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colorScheme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;toggle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;auto&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;homepage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;grid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;giscus&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="콘텐츠-구조"&gt;&lt;a href="#%ec%bd%98%ed%85%90%ec%b8%a0-%ea%b5%ac%ec%a1%b0" class="header-anchor"&gt;&lt;/a&gt;콘텐츠 구조
&lt;/h2&gt;&lt;p&gt;Stack 테마는 &lt;code&gt;content/&lt;/code&gt; 아래 디렉터리가 곧 섹션이 된다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── tech/ # 기술 포스트
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── _index.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── 글.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── til/ # Today I Learned
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── log/ # 일상 기록
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── archives/ # 아카이브 페이지
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── search.md # 검색 페이지
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="커스터마이징"&gt;&lt;a href="#%ec%bb%a4%ec%8a%a4%ed%84%b0%eb%a7%88%ec%9d%b4%ec%a7%95" class="header-anchor"&gt;&lt;/a&gt;커스터마이징
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;핵심 원칙&lt;/strong&gt;: 테마 파일은 절대 건드리지 않는다. 프로젝트 루트의 &lt;code&gt;layouts/&lt;/code&gt;에 같은 경로로 파일을 만들면 Hugo가 테마보다 우선 적용한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;themes/stack/layouts/_partials/sidebar/left.html ← 원본
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;layouts/_partials/sidebar/left.html ← 내 오버라이드
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;CSS는 &lt;code&gt;assets/scss/custom.scss&lt;/code&gt;를 생성하면 Stack이 자동으로 포함시켜준다.&lt;/p&gt;
&lt;h3 id="실제로-오버라이드한-것들"&gt;&lt;a href="#%ec%8b%a4%ec%a0%9c%eb%a1%9c-%ec%98%a4%eb%b2%84%eb%9d%bc%ec%9d%b4%eb%93%9c%ed%95%9c-%ea%b2%83%eb%93%a4" class="header-anchor"&gt;&lt;/a&gt;실제로 오버라이드한 것들
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;레이아웃 너비&lt;/strong&gt; — &lt;code&gt;.container.extended&lt;/code&gt;를 &lt;code&gt;max-width: 1600px&lt;/code&gt;로 확장. liuhouliang.com 기준으로 맞췄다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;홈 그리드&lt;/strong&gt; — &lt;code&gt;article-list.-grid&lt;/code&gt;에서 gap을 30px로, 카드 hover 시 translateY(-5px) 애니메이션 추가.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;사이드바 검색창&lt;/strong&gt; — 기본 Stack 사이드바엔 검색창이 없다. &lt;code&gt;left.html&lt;/code&gt;을 오버라이드해서 추가했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;사이드바 방문자 수&lt;/strong&gt; — Busuanzi 서비스로 총 UV를 가져와 사이드바 하단에 눈 아이콘과 함께 표시한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;카드 조회수&lt;/strong&gt; — &lt;code&gt;article/components/details.html&lt;/code&gt;을 오버라이드해서 날짜/읽기 시간과 같은 줄에 눈 아이콘 + 조회수를 표시한다. Cloudflare Workers API를 호출한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;macOS 코드 블록&lt;/strong&gt; — &lt;code&gt;.highlight:before&lt;/code&gt;에 SVG base64로 신호등 버튼을 그려 넣는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;페이지네이션 버튼&lt;/strong&gt; — 기본 스타일이 &lt;code&gt;flex: 1 1 auto&lt;/code&gt;라 버튼이 전체 너비로 늘어났다. &lt;code&gt;flex: 0 0 auto&lt;/code&gt;로 수정.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sticky footer&lt;/strong&gt; — 글이 짧을 때 footer가 중간에 떠오르는 문제. &lt;code&gt;main.main&lt;/code&gt;에 &lt;code&gt;align-self: stretch&lt;/code&gt;, &lt;code&gt;&amp;gt; .site-footer&lt;/code&gt;에 &lt;code&gt;margin-top: auto&lt;/code&gt;를 적용해 항상 하단에 고정되도록 했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Archives 섹션 타일&lt;/strong&gt; — 기본 테마엔 없다. &lt;code&gt;layouts/archives.html&lt;/code&gt;을 만들어 Stack의 &lt;code&gt;article-list/tile&lt;/code&gt; 파셜을 활용했고, Tech/TIL/Log 각각에 그라디언트 색상을 입혔다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;섹션 내 카테고리 카드&lt;/strong&gt; — Tech 페이지 상단에 카테고리 목록을 카드로 표시. Hugo 택소노미를 순회해서 현재 섹션에 속한 카테고리만 필터링하고 포스트 수와 함께 보여준다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="cloudflare-workers로-조회수-구현"&gt;&lt;a href="#cloudflare-workers%eb%a1%9c-%ec%a1%b0%ed%9a%8c%ec%88%98-%ea%b5%ac%ed%98%84" class="header-anchor"&gt;&lt;/a&gt;Cloudflare Workers로 조회수 구현
&lt;/h2&gt;&lt;p&gt;정적 사이트라 서버가 없다. 글별 조회수를 저장하려면 외부 저장소가 필요하다. Cloudflare Workers + KV 조합을 썼다.&lt;/p&gt;
&lt;h3 id="구조"&gt;&lt;a href="#%ea%b5%ac%ec%a1%b0" class="header-anchor"&gt;&lt;/a&gt;구조
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;브라우저 → GET /api/pageviews?url=http://blog.kastori.dev/tech/xxx → Worker → KV에서 count 조회
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;브라우저 → POST /api/pageviews?url=http://blog.kastori.dev/tech/xxx → Worker → KV count++ 후 반환
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;글 페이지에서는 POST(조회수 증가), 목록 페이지에서는 GET(조회수 조회)만 한다. &lt;code&gt;document.body.classList.contains('article-page')&lt;/code&gt;로 구분한다.&lt;/p&gt;
&lt;h3 id="worker-코드-핵심"&gt;&lt;a href="#worker-%ec%bd%94%eb%93%9c-%ed%95%b5%ec%8b%ac" class="header-anchor"&gt;&lt;/a&gt;Worker 코드 핵심
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ALLOWED_ORIGINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://blog.kastori.dev&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://localhost:1313&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;corsHeaders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Access-Control-Allow-Origin&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ALLOWED_ORIGINS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ALLOWED_ORIGINS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Access-Control-Allow-Methods&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;GET, POST, OPTIONS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; 헤더는 &lt;strong&gt;단일 문자열&lt;/strong&gt;이어야 한다. 배열을 직접 넣으면 CORS가 터진다. 요청 origin이 허용 목록에 있으면 그걸 그대로 반환하고, 없으면 기본값을 반환하는 방식으로 해결했다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="giscus-댓글-설정"&gt;&lt;a href="#giscus-%eb%8c%93%ea%b8%80-%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;Giscus 댓글 설정
&lt;/h2&gt;&lt;p&gt;GitHub Discussions 기반 댓글 시스템. 서버가 필요 없고 GitHub 계정으로 로그인한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GitHub 레포 Settings → General → Discussions 활성화&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/apps/giscus" target="_blank" rel="noopener"
 &gt;Giscus 앱&lt;/a&gt; 설치&lt;/li&gt;
&lt;li&gt;GitHub GraphQL API로 &lt;code&gt;repoID&lt;/code&gt;와 &lt;code&gt;categoryID&lt;/code&gt; 조회&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-graphql" data-lang="graphql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="py"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Kastori1206&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;kastori1206.github.io&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="py"&gt;discussionCategories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="py"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="py"&gt;nodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="4"&gt;
&lt;li&gt;&lt;code&gt;hugo.toml&lt;/code&gt;에 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;giscus&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;repo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Kastori1206/kastori1206.github.io&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;repoID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;R_...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Announcements&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;categoryID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DIC_...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mapping&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;lightTheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;light&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;darkTheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dark_dimmed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="삽질-모음"&gt;&lt;a href="#%ec%82%bd%ec%a7%88-%eb%aa%a8%ec%9d%8c" class="header-anchor"&gt;&lt;/a&gt;삽질 모음
&lt;/h2&gt;&lt;h3 id="toml-위젯-형식-오류"&gt;&lt;a href="#toml-%ec%9c%84%ec%a0%af-%ed%98%95%ec%8b%9d-%ec%98%a4%eb%a5%98" class="header-anchor"&gt;&lt;/a&gt;TOML 위젯 형식 오류
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 잘못된 방법 (문자열 배열로 쓰면 Hugo가 무시한다)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;widgets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;search&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;archives&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 올바른 방법 (객체 배열)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;widgets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;homepage&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;search&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;widgets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;homepage&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;archives&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="stack-아이콘-이름"&gt;&lt;a href="#stack-%ec%95%84%ec%9d%b4%ec%bd%98-%ec%9d%b4%eb%a6%84" class="header-anchor"&gt;&lt;/a&gt;Stack 아이콘 이름
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;themes/stack/assets/icons/&lt;/code&gt;에 있는 SVG 파일명이 그대로 아이콘 이름이다. &lt;code&gt;code&lt;/code&gt;, &lt;code&gt;pencil&lt;/code&gt; 같은 이름은 없다.&lt;/p&gt;
&lt;p&gt;사용 가능한 예: &lt;code&gt;home&lt;/code&gt;, &lt;code&gt;archives&lt;/code&gt;, &lt;code&gt;tag&lt;/code&gt;, &lt;code&gt;hash&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;infinity&lt;/code&gt;, &lt;code&gt;brand-github&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="github-pages-빌드-실패"&gt;&lt;a href="#github-pages-%eb%b9%8c%eb%93%9c-%ec%8b%a4%ed%8c%a8" class="header-anchor"&gt;&lt;/a&gt;GitHub Pages 빌드 실패
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error: Unable to locate config file or config directory
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;actions/checkout@v4&lt;/code&gt;에 &lt;code&gt;submodules: recursive&lt;/code&gt;가 빠지면 테마 디렉터리가 비어 있어서 발생한다.&lt;/p&gt;
&lt;h3 id="hascjklanguage-미설정"&gt;&lt;a href="#hascjklanguage-%eb%af%b8%ec%84%a4%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;hasCJKLanguage 미설정
&lt;/h3&gt;&lt;p&gt;이 설정이 없으면 한국어 글의 읽기 시간이 3배 이상 부풀려진다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;hasCJKLanguage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="cloudflare-workers-cors-오류-status-0"&gt;&lt;a href="#cloudflare-workers-cors-%ec%98%a4%eb%a5%98-status-0" class="header-anchor"&gt;&lt;/a&gt;Cloudflare Workers CORS 오류 (status 0)
&lt;/h3&gt;&lt;p&gt;브라우저 콘솔에 &lt;code&gt;status: 0&lt;/code&gt; CORS 에러가 떴다. 원인은 Worker에서 &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;에 배열을 넘기고 있었던 것.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 잘못된 코드 — 배열은 HTTP 헤더가 될 수 없다
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;Access-Control-Allow-Origin&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://blog.kastori.dev&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://localhost:1313&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 올바른 코드
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;Access-Control-Allow-Origin&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ALLOWED_ORIGINS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ALLOWED_ORIGINS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;curl -sv -H &amp;quot;Origin: http://localhost:1313&amp;quot; &amp;lt;Worker URL&amp;gt;&lt;/code&gt;로 응답 헤더를 직접 확인해서 원인을 찾았다.&lt;/p&gt;
&lt;h3 id="cloudflare-analytics의-localhost-cors-오류"&gt;&lt;a href="#cloudflare-analytics%ec%9d%98-localhost-cors-%ec%98%a4%eb%a5%98" class="header-anchor"&gt;&lt;/a&gt;Cloudflare Analytics의 localhost CORS 오류
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;beacon.min.js&lt;/code&gt;가 &lt;code&gt;cloudflareinsights.com&lt;/code&gt;에 POST 요청을 보내는데, Cloudflare Analytics는 &lt;code&gt;http://localhost&lt;/code&gt; (포트 없는 경우)만 허용한다. &lt;code&gt;http://localhost:1313&lt;/code&gt;은 허용 목록에 없어서 로컬 개발 중 콘솔이 CORS 에러로 도배됐다.&lt;/p&gt;
&lt;p&gt;해결: &lt;code&gt;custom.html&lt;/code&gt;에서 스크립트 로딩 조건을 추가했다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{- if not hugo.IsServer -}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;https://static.cloudflareinsights.com/beacon.min.js&amp;#39;&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{- end -}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;hugo.IsServer&lt;/code&gt;는 &lt;code&gt;hugo server&lt;/code&gt;로 실행 중일 때 &lt;code&gt;true&lt;/code&gt;다. 프로덕션 빌드에서만 로드된다.&lt;/p&gt;
&lt;h3 id="hugo-서버가-템플릿-변경을-반영-안-할-때"&gt;&lt;a href="#hugo-%ec%84%9c%eb%b2%84%ea%b0%80-%ed%85%9c%ed%94%8c%eb%a6%bf-%eb%b3%80%ea%b2%bd%ec%9d%84-%eb%b0%98%ec%98%81-%ec%95%88-%ed%95%a0-%eb%95%8c" class="header-anchor"&gt;&lt;/a&gt;Hugo 서버가 템플릿 변경을 반영 안 할 때
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;layouts/&lt;/code&gt; 파일을 수정해도 로컬 서버에 반영이 안 되는 경우가 있다. 예전에 띄운 Hugo 프로세스가 살아있어서 캐시된 템플릿을 계속 쓰는 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkill -f &lt;span class="s2"&gt;&amp;#34;hugo server&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo server --disableFastRender
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--disableFastRender&lt;/code&gt;는 매 요청마다 전체 페이지를 다시 렌더링한다. 개발 중엔 이걸 켜두는 게 낫다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="완성된-기능"&gt;&lt;a href="#%ec%99%84%ec%84%b1%eb%90%9c-%ea%b8%b0%eb%8a%a5" class="header-anchor"&gt;&lt;/a&gt;완성된 기능
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Hugo + Stack 테마 세팅&lt;/li&gt;
&lt;li&gt;GitHub Actions 자동 배포&lt;/li&gt;
&lt;li&gt;커스텀 도메인 (Cloudflare DNS)&lt;/li&gt;
&lt;li&gt;Tech / TIL / Log 섹션 구조&lt;/li&gt;
&lt;li&gt;2컬럼 그리드 홈 레이아웃 (1600px 컨테이너)&lt;/li&gt;
&lt;li&gt;Archives 페이지 — 섹션 타일 카드 (그라디언트)&lt;/li&gt;
&lt;li&gt;섹션 페이지 — 카테고리 카드 목록&lt;/li&gt;
&lt;li&gt;macOS 스타일 코드 블록&lt;/li&gt;
&lt;li&gt;좌측 사이드바 검색창&lt;/li&gt;
&lt;li&gt;사이드바 총 방문자 수 (Busuanzi)&lt;/li&gt;
&lt;li&gt;카드/글 페이지 조회수 (Cloudflare Workers + KV)&lt;/li&gt;
&lt;li&gt;Giscus 댓글&lt;/li&gt;
&lt;li&gt;Sticky footer&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="회고"&gt;&lt;a href="#%ed%9a%8c%ea%b3%a0" class="header-anchor"&gt;&lt;/a&gt;회고
&lt;/h2&gt;&lt;h3 id="레퍼런스를-먼저-정한-게-잘한-일이었다"&gt;&lt;a href="#%eb%a0%88%ed%8d%bc%eb%9f%b0%ec%8a%a4%eb%a5%bc-%eb%a8%bc%ec%a0%80-%ec%a0%95%ed%95%9c-%ea%b2%8c-%ec%9e%98%ed%95%9c-%ec%9d%bc%ec%9d%b4%ec%97%88%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;레퍼런스를 먼저 정한 게 잘한 일이었다
&lt;/h3&gt;&lt;p&gt;테마만 골랐다면 &amp;ldquo;기본값이 이상한데&amp;hellip;&amp;rdquo; 하면서 감각 없이 수정했을 것 같다. liuhouliang.com을 레퍼런스로 잡고 DevTools로 수치를 측정해서 맞추니 방향이 명확했다. &amp;ldquo;왜 내 블로그가 좁아 보이지?&amp;ldquo;가 아니라 &amp;ldquo;1600px로 넓혀야 한다&amp;quot;가 됐다.&lt;/p&gt;
&lt;h3 id="hugo-오버라이드-시스템은-잘-설계되어-있다"&gt;&lt;a href="#hugo-%ec%98%a4%eb%b2%84%eb%9d%bc%ec%9d%b4%eb%93%9c-%ec%8b%9c%ec%8a%a4%ed%85%9c%ec%9d%80-%ec%9e%98-%ec%84%a4%ea%b3%84%eb%90%98%ec%96%b4-%ec%9e%88%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;Hugo 오버라이드 시스템은 잘 설계되어 있다
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;layouts/&lt;/code&gt; 디렉터리 하나로 테마 전체를 건드리지 않고 필요한 부분만 교체할 수 있다. 테마를 업데이트해도 내 오버라이드는 그대로다. 이 덕분에 Stack 테마의 부분만 바꾸면서 전체적인 구조는 유지할 수 있었다.&lt;/p&gt;
&lt;h3 id="정적-사이트에-동적-기능을-붙이는-비용"&gt;&lt;a href="#%ec%a0%95%ec%a0%81-%ec%82%ac%ec%9d%b4%ed%8a%b8%ec%97%90-%eb%8f%99%ec%a0%81-%ea%b8%b0%eb%8a%a5%ec%9d%84-%eb%b6%99%ec%9d%b4%eb%8a%94-%eb%b9%84%ec%9a%a9" class="header-anchor"&gt;&lt;/a&gt;정적 사이트에 동적 기능을 붙이는 비용
&lt;/h3&gt;&lt;p&gt;조회수 하나를 붙이는 데 Cloudflare Workers를 세팅하고, CORS를 디버깅하고, Hugo 템플릿을 오버라이드해야 했다. 동적 기능 하나가 생각보다 많은 레이어를 건드린다. 그래도 서버 없이 무료로 운영할 수 있다는 장점이 이 복잡함을 상쇄한다.&lt;/p&gt;
&lt;h3 id="삽질의-패턴"&gt;&lt;a href="#%ec%82%bd%ec%a7%88%ec%9d%98-%ed%8c%a8%ed%84%b4" class="header-anchor"&gt;&lt;/a&gt;삽질의 패턴
&lt;/h3&gt;&lt;p&gt;이번 세팅에서 겪은 삽질의 대부분은 &lt;strong&gt;암묵적인 제약을 모르고 시작한 것&lt;/strong&gt;들이었다. CORS 헤더는 단일 문자열이어야 한다는 것, Cloudflare Analytics는 포트 없는 localhost만 허용한다는 것, Hugo 서버는 프로세스가 살아있으면 캐시를 쓴다는 것. 문서에 있긴 한데, 직접 부딪히기 전까지는 보이지 않는 것들이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="참고"&gt;&lt;a href="#%ec%b0%b8%ea%b3%a0" class="header-anchor"&gt;&lt;/a&gt;참고
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://gohugo.io/documentation/" target="_blank" rel="noopener"
 &gt;Hugo 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack" target="_blank" rel="noopener"
 &gt;Stack 테마 GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://liuhouliang.com/en/" target="_blank" rel="noopener"
 &gt;liuhouliang.com&lt;/a&gt; — 레이아웃 레퍼런스&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://giscus.app/" target="_blank" rel="noopener"
 &gt;Giscus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://developers.cloudflare.com/workers/" target="_blank" rel="noopener"
 &gt;Cloudflare Workers 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>