처음부터 Hugo + Tailwind 기술 블로그 구축하기: INFINI Labs Blog를 사례로

발행: (2025년 12월 20일 오후 03:40 GMT+9)
14 min read
원문: Dev.to

I’m happy to translate the article for you, but I need the full text of the post. Could you please paste the content you’d like translated (excluding the source line you’ve already provided)? Once I have the article text, I’ll translate it into Korean while preserving the original formatting, markdown, and code blocks.

Source:

1. 디렉터리 구조: 콘텐츠, 테마, 설정, 에셋은 어디에 있나요?

이 저장소는 네 개의 레이어로 생각할 수 있습니다:

1) 콘텐츠 레이어 – content/

content/english/posts/      # 블로그 포스트 (Markdown + front matter)
content/english/authors/    # 저자 페이지 (Markdown + front matter)

각 포스트는 front matter 로 시작합니다 (YAML/TOML/JSON 모두 지원; 이 레포는 YAML을 사용). 일반적인 필드 예시:

필드목적
title / description페이지 제목 및 SEO
date발행 시간 (정렬 및 표시 방식에 영향)
categories / tags카테고리 및 태그 페이지에 사용
image커버 이미지
author저자 이름 (해당 저자 페이지와 연결)

템플릿은 이러한 필드를 읽습니다. 예를 들어 themes/hugoplate/layouts/posts/single.html 은 커버 이미지, 저자, 카테고리, 발행일, 본문 내용, 목차(TOC)를 표시합니다.

2) 설정 레이어 – hugo.toml + config/_default/*

Hugo는 config/ 디렉터리 아래에서 설정을 분할하는 것을 지원합니다. 이 프로젝트는 다음을 사용합니다:

  • 루트 설정: hugo.toml

    theme = "hugoplate"
    
    [outputs]
      home = ["HTML", "JSON"]   # public/index.json 생성
    
    [build]
      [build.buildStats]
        enable = true           # 정확한 Tailwind 스캔 (아래 설명)
  • 언어 설정: config/_default/languages.toml

    [languages.en]
      languageName = "English"
      weight = 1
      contentDir = "content/english"
  • 사이트 파라미터: config/_default/params.toml – 로고, 파비콘, 테마 색상, 공지 배너, 쿠키 배너, 사이드바 위젯 등을 정의합니다.

  • Hugo 모듈: config/_default/module.toml – 다음과 같은 모듈을 가져옵니다

    [[imports]]
      path = "github.com/gethugothemes/hugo-modules/images"
    
    [[imports]]
      path = "github.com/gethugothemes/hugo-modules/pwa"
    
    [[imports]]
      path = "github.com/gethugothemes/hugo-modules/seo-tools/basic-seo"
    
    [[imports]]
      path = "github.com/hugomods/mermaid"

    (이러한 import 때문에 CI에 Go가 필요합니다.)

3) 테마 레이어 – themes/hugoplate/

테마는 페이지 구조와 Hugo Pipes 를 정의합니다:

파일목적
themes/hugoplate/layouts/_default/baseof.html기본 레이아웃 스켈레톤
themes/hugoplate/layouts/index.html메인 페이지 리스트
themes/hugoplate/layouts/posts/single.html포스트 상세 페이지
themes/hugoplate/layouts/index.jsonJSON 검색 인덱스를 생성하는 템플릿 (중요)

Note: 템플릿에서 {{ partial "image" ... }} 를 볼 수 있지만, 테마 디렉터리 안에 partials/image.html 파일은 없습니다. 이 partial 은 module.toml 에서 import 된 hugo‑modules/images 모듈에서 제공됩니다. 이것은 흔히 쓰이는 패턴으로, 테마 + 모듈형 기능 입니다.

4) 에셋 레이어 – assets/ vs static/

이 두 디렉터리는 Hugo에서 동작 방식이 다릅니다:

디렉터리Hugo가 처리하는 방식
assets/Hugo Pipes 로 처리 (컴파일, 지문 추가, 압축 등).
* 이미지 파일은 assets/images/... 아래에 두며, 빌드 시 public/images/... 로 출력됩니다.
* 스타일/스크립트 소스 파일은 themes/hugoplate/assets/* 아래에 두고 Hugo Pipes 로 번들링합니다.
static/그대로 public/ 로 복사됩니다.
static/assets/index-*.css, static/assets/index-*.js, pizza_wasm_bg-*.wasm 파일이 포함됩니다.
baseof.html/assets/index-*.css/assets/index-*.js 를 하드코딩해서 import 합니다.

2. Build Pipeline: What Happens From Markdown to Final HTML/CSS/JS?

1) Build entry points – package.json scripts

{
  "scripts": {
    "dev": "hugo server",
    "build": "hugo --gc --minify --templateMetrics --templateMetricsHints --forceSyncStatic",
    "project-setup": "node ./scripts/projectSetup.js"
  }
}
  • dev는 로컬 Hugo 서버를 실행합니다.
  • build는 전체 프로덕션 빌드를 담당하며, Node는 주로 PostCSS/Tailwind 용으로 사용됩니다.

project-setup 스크립트는 Hugoplate 템플릿에서 제공되며 exampleSite/ 레이아웃을 실제 프로젝트 레이아웃으로 이동시킵니다. 이 저장소에는 이미 themes/ 디렉터리가 존재하므로 스크립트는 보통 “Project already setup” 라는 메시지를 출력하고 아무 작업도 하지 않습니다—CI에 포함해도 안전합니다.

2) Hugo Modules – Why does CI install Go?

config/_default/module.toml은 여러 모듈(이미지, pwa, seo‑tools, mermaid 등)을 import합니다. 이 모듈들은 Hugo 빌드 중에 가져와지며 렌더링에 참여합니다. 저장소의 go.mod는 모듈 버전을 고정하는 역할을 하며, Hugo Modules의 의존성 매니페스트 역할을 합니다.

역할 분담

ToolRole
Hugo사이트를 렌더링
GoHugo Module 의존성을 가져오고 관리

그래서 CI에서는 Hugo Go 둘 다 설치합니다( netlify.toml.github/workflows/hugo.yml 참고).

3) How does Tailwind generate only the CSS you actually use?

두 가지 설정이 이를 가능하게 합니다:

  1. Hugo build statshugo.toml에서 활성화

    [build.buildStats]
      enable = true

    Hugo는 템플릿을 렌더링하면서 hugo_stats.json을 생성하고, 생성된 HTML에 사용된 모든 클래스/토큰을 기록합니다.

  2. Tailwind config – stats 파일을 읽음

    // tailwind.config.js
    module.exports = {
      content: ["./hugo_stats.json"],
      // …other Tailwind settings…
    };

Tailwind는 hugo_stats.json을 읽어 필요한 유틸리티 클래스만 매우 정확하게 생성할 수 있습니다. 이는 전체 트리를 스캔하면서 발생하는 많은 false positive와 큰 CSS 번들을 방지합니다.

추가로, hugo.toml은 stats 파일을 마운트하여 Tailwind가 접근할 수 있도록 합니다:

[[module.mounts]]
  source = "hugo_stats.json"
  target = "assets/watching/hugo_stats.json"

요약

  • Content → Markdown (in content/) → Hugo renders HTML.
  • Theme & Modulesthemes/hugoplate/ + Hugo Modules (imported via module.toml).
  • Assets → Processed by Hugo Pipes (assets/) or copied verbatim (static/).
  • Tailwind → Uses hugo_stats.json for precise CSS generation.
  • Searchindex.json generated from a dedicated template, consumed by a pre‑built UI in static/assets/.

이 구조를 사용하면 사이트를 완전히 정적 도구만으로 빌드하고, 어떤 정적 호스트에도 배포할 수 있으며, 이미지 처리, PWA 지원, SEO 도우미, 오프라인 검색과 같은 강력한 기능도 그대로 활용할 수 있습니다.

Cache‑busting config so CSS rebuilds trigger correctly

이것은 Hugoplate‑based 설정에서 널리 사용되는 통합 방식입니다.

1) CSS/JS는 어떻게 번들링·압축·지문화(fingerprinting) 되나요?

themes/hugoplate/layouts/partials/essentials/style.html 에서 Hugo Pipes 흐름을 확인할 수 있습니다:

  1. 플러그인 CSS 수집hugo.tomlparams.plugins.css 에서 가져옵니다.
  2. scss/main.scss 컴파일 – Hugo Extended가 필요합니다.
  3. resources.Concat – 파일들을 병합합니다.
  4. css.PostCSS – PostCSS(Tailwind + autoprefixer)를 실행합니다.
    프로덕션에서는 minify | fingerprint | resources.PostProcess 를 수행합니다.

출력은 다음과 같이 렌더링됩니다:

<link href="/assets/css/main-<hash>.css" integrity="..." rel="stylesheet">

JS도 themes/hugoplate/layouts/partials/essentials/script.html 에서 유사하게 처리됩니다.

런타임 사실

  • 배포 후 사이트는 순수 정적 파일(HTML/CSS/JS/이미지)만 존재하며, 런타임 컴파일이 일어나지 않습니다.
  • 지문화(fingerprinting) 은 캐시를 안정화합니다: 내용이 바뀌면 자산 URL도 바뀌어 브라우저·CDN이 오래된 자산을 제공하지 않게 됩니다.

Source:

3. Search: index.json/static/assets/*는 어떻게 함께 작동하나요?

1) 인덱스는 어디서 오는가?

hugo.toml

outputs.home = ["HTML", "RSS", "WebAppManifest", "JSON"]

가 설정되어 있고, JSON 출력은 baseName = "index" 로 지정됩니다. 빌드 과정에서 Hugo는 themes/hugoplate/layouts/index.json 템플릿을 사용해 public/index.json 을 생성합니다. 이 템플릿은 사이트 페이지들을 순회하면서 title, URL, tags, category, description, 그리고 순수 텍스트 콘텐츠와 같은 필드를 JSON 배열에 담습니다. 이 파일은 정적 사이트에서 클라이언트‑사이드 검색을 위한 훌륭한 데이터 소스가 됩니다.

2) 검색 UI는 어디서 오는가?

themes/hugoplate/layouts/_default/baseof.htmlstatic/assets/ 에 있는 자산들을 하드코딩으로 import 합니다. 이 파일들은 Hugo에 의해 public/assets/ 로 복사되므로, 배포 후 브라우저가 직접 요청할 수 있습니다.

런타임 흐름

  1. 브라우저가 정적 페이지를 로드합니다.
  2. 검색 UI의 CSS/JS 를 로드합니다.
  3. UI 가 index.json (및 필요 시 WASM 자산) 을 가져와 브라우저 내에서 인덱스를 구축합니다.
  4. 검색은 로컬에서 실행되며, 백엔드 API 가 필요하지 않습니다.

이 “빌드 시 생성하고 런타임에 소비한다”는 패턴은 정적 사이트를 강화하는 데 흔히 사용됩니다.

Source:

4. 비슷한 프로젝트를 처음부터 만들기 (최소 실행 단계)

1) 툴체인 준비

  • Hugo Extended (>= 0.139.2)
  • Go (CI에서는 Hugo Modules용으로 1.23.3 사용)
  • Node (PostCSS/Tailwind용; CI에서는 Node 20 사용, 로컬에서는 LTS 버전이면 모두 OK)

2) 의존성 설치 및 개발 시작

레포지토리에는 pnpm이 선언돼 있지만, 스크립트는 npm/yarn에서도 동작합니다.

pnpm install
pnpm dev          # 또는: npm install && npm run dev

이 명령으로 Hugo 개발 서버가 시작됩니다.

3) 새 포스트 추가

content/english/posts/YYYY/ 아래에 Markdown 파일을 생성합니다. 예시:

---
title: "My First Post"
description: "A short summary"
date: "2025-12-20T09:00:00+08:00"
categories: ["Engineering"]
tags: ["Hugo"]
image: "/images/posts/2025/some-folder/cover.jpg"
author: "Rain9"
lang: "en"
category: "Technology"
subcategory: "Engineering"
draft: true
---

# Hello

Write something here.

Tip: 이미지 파일은 assets/images/posts/... 아래에 두고 /images/posts/... 형태로 참조합니다. 빌드 후에는 public/images/posts/... 경로에 출력됩니다.

4) 프로덕션 빌드

pnpm build

출력 디렉터리는 public/이며 (netlify.toml: publish = "public" 참고).

5. 배포 및 CI/CD: Netlify / GitHub Pages / Vercel이 빌드하는 방식

이 저장소는 여러 호스팅 플랫폼을 지원합니다.

1) Netlify

netlify.toml

[build]
  command = "yarn project-setup; yarn build"
  publish = "public"

[build.environment]
  HUGO_VERSION = "0.139.2"
  GO_VERSION   = "1.23.3"

2) GitHub Pages (GitHub Actions)

.github/workflows/hugo.yml 은 다음을 수행합니다:

  1. 저장소를 체크아웃합니다.
  2. Node를 설치합니다.
  3. Hugo Extended를 다운로드합니다.
  4. Go를 설치합니다.
  5. npm run project-setup을 실행합니다.
  6. 의존성을 설치합니다 (npm install).
  7. 빌드합니다 (npm run build).
  8. public을 Pages 아티팩트로 업로드하고 배포합니다.

3) Vercel

vercel-build.sh 는 빌드 머신에서 다음을 실행합니다:

  1. Go를 설치합니다.
  2. Hugo Extended를 설치합니다.
  3. npm run project-setup을 실행합니다.
  4. 의존성을 설치합니다 (npm install).
  5. 빌드합니다 (npm run build).

모든 플랫폼에서 최종적으로 수행되는 작업:

  • 툴체인(Hugo + Go + Node)을 프로비저닝합니다.
  • 정적 public/ 디렉터리를 생성합니다.

Wrap‑up: The Boundary Between Build‑time and Run‑time

Hugo 기반 블로그의 핵심 아이디어는 간단합니다:

PhaseWhat Happens
Build‑timeHugo가 콘텐츠, 템플릿, 모듈 및 자산을 정적 파일로 컴파일합니다. 또한 최소화, 지문 생성, 인덱스 생성을 수행합니다.
Run‑timeCDN/정적 서버는 그 파일들만 제공합니다. 브라우저는 소량의 프론트‑엔드 JS(예: 검색 UI)를 실행합니다. 백엔드 API는 필요하지 않습니다.

이 경계를 이해하면 사이트 확장이 자연스러워집니다: 새로운 기능을 추가할 때 먼저 “빌드 타임에 데이터를 생성할 수 있나요?” 를 묻고, 그 다음 “브라우저가 이를 소비할 수 있나요?” 를 확인하세요.

Back to Blog

관련 글

더 보기 »