Hugo를 이용해 블로그 만들기
Git과 markdown을 이용하여 git을 블로그처럼 이용하는 사람들이 있다는 것을 알았다.
게다가 UI를 보기 좋게 꾸며줄 수 있는 툴들도 찾았는데, 그 중 Hugo를 사용해 보았다.
Hugo는 Go 언어로 짜여져 있어 apt-get으로도 설치가 가능하고, 소스 코드를 받아 빌드하여 쓸 수도 있다.
내 컴퓨터에는 Go가 이미 설치되 있던 터라 apt-get으로 hugo를 받아서 사용해 보았다.
설치는 정상적으로 되었고, 처음에는 잘 동작하는 듯 했는데, theme을 적용하니 ERROR들이 뜨기 시작했다.
인터넷 검색을 아무리 해 봐도 해결책이 보이지 않고, 해당 git에 issue를 날려보기도 했는데 응답이 없어서 혼자 이것저것 뒤져 보았다.
알고보니 내 hugo의 버전이 너무 낮아서 발생한 현상이었고, 덩달아 Go의 버전도 낮다는 것을 알아냈다.
Go언어는 apt-get 대신 인터넷에서 tar파일을 받아서 압축을 풀어 사용했고, Hugo는 소스코드를 받아 Go 언어로 빌드하여 사용하였다.
(brew를 이용해 보라고도 해서 brew를 설치 해 보기도 했는데, 잘 동작 하지 않아서 그만뒀다.)
버전을 최신으로 맞추고 나니 모두 정상동작, git에 올려놓은 issue를 뻘쭘하게 혼자 close했다.
링크
hugo 환경설정 및 사용방법 가이드
아래 주소의 글쓴이도 hugo로 블로그를 만들어 관리하고 있다. 이분의 글을 토대로 환경을 세팅했다.
Hugo git 사이트
Hugo의 소스파일을 다운받을 수 있다.
내가 사용한 theme의 git 주소
설명대로 theme을 다운받고, config파일을 수정해 주어야 최종적으로 적용이 된다.
Go 언어 설치 가이드
Hugo 설치 가이드
환경 세팅 (window)
리눅스 환경세팅은 apt, yum을 이용하면 간단하게 수행 가능하여 생략한다.
- Go 언어를 설치한다.
- 설치파일로 받아서 설치하면 간단하다.
- 리눅스에 설치할 경우, apt를 사용하면 낮은 버전이 설치될 수 있으니 코드를 받아 설치하는걸 추천한다.
- Hugo를 설치한다.
- 압축파일 형태로 제공되며, 압축을 푼 후 path 설정만 해주면 된다.
- 리눅스의 경우 코드를 이용해 설치할 수 있다.
- git 레퍼지토리를 2개 생성한다.
- 한개는 글 작성용, 한개는 publish용이다. (이 경우,
<GITHUB_ID>.github.io
이름으로 repository를 생성한다. 그렇지 않으면 정상동작하지 않는다.)ex) github.com/AswinBlue/AswinBlue.github.io
- 작성용은 소스코드에 해당하며 publish용은 컴파일된 바이너리에 해당한다고 보면 된다.
- 로컬PC에서 Hugo 프로젝트를 생성한다.
hugo new site <SITE>
명령으로 새로운 hugo 프로젝트를 생성한다.
- 생성한 프로젝트를 git과 연동시킨다.
- 프로젝트 root디렉터리에 (3)에서 만든 소스용 git을 연동시킨다.
- /public 디렉터리에 (3)에서 만든 publish용 git을 연동시킨다.
- git 폴더 안에 git을 연동하려면
git submodule add <URL>
명령어를 이용한다.
- git 폴더 안에 git을 연동하려면
- 테마를 선택한다.
- 인터넷에서 hugo 테마를 검색하여, 원하는 테마의 git repository를
/themes
경로에 clone 한다. - 이후, 해당 테마에서 지원하는 config 파일을 root 경로에 복사한다.
- config 파일은 toml, yaml, json 형태로 작성이 가능하며, hugo에서는 toml -> yaml -> json 순서대로 config파일을 찾아 적용한다. (즉, config.toml파일이 있으면 config.yml파일은 적용되지 않음)
- 기본 탬플릿을 설정한다.
/archtypes/default.md
파일을 수정하면,hugo new NEW_POST.md
를 이용해 새로운 파일을 생성할 때 사용되는 기본 md파일 탬플릿을 정의할 수 있다.- 파일 생성시 .md 확장자가 붙여야 정상 동작함에 주의한다.
Quick Start
프로젝트 생성
새로운 Hugo 사이트를 생성하는 명령어,
폴더 안에 Hugo 구조에 맞게 폴더 및 파일이 자동 생성된다. hugo new site <NAME>
생성된
폴더에서 새로운 post를 생성하는 명령어, content 폴더 안에 archtype에 맞게 내용을 적어 파일을 생성한다. hugo new <POST>.md ex) hugo new post/hugo.md
테마에 맞는 형식으로 content 폴더 안의 내용을 이용해 public 폴더 안에 내용을 생성한다. :
hugo -t <THEME>
config파일에 따라
hugo --config config.yml
와 같이 명령어를 사용할 수도 있다.- config파일에서 baseURL 을 페이지 주소로 설정해야 css 및 javascript가 정상 동작 한다.
- html에서 hugo문법으로 config파일에 선언된 baseURL을 가져오는 것은
{{ .Page.Site.BaseURL }}
와 같이 사용하면 된다.
본 페이지는 hugo-PaperMod 테마를 사용했다.
문서 최상단에 +++로 둘러쌓인 부분은 설정 부분이다. draft=false로 설정을 해야 화면에 표시됨에 주의한다.
- toml을 사용한다면
+++
로 formatter를 구성 하고, yaml은---
, json은{}
을 사용한다.
- toml을 사용한다면
public 폴더에 생성한 내용을 push 하기 전 테스트 해 본다.
hugo server [--theme <THEME_PATH>]
서버 실행 후 http://localhost:1313 경로에서 웹 브라우저로 내용을 확인할 수 있다.
내용이 완벽하다면 public 폴더 안의 git을 push 하면
<GIT_ID>.github.io
주소에서 방금 본 내용을 볼 수 있다.ex : aswinblue.github.io
Go와 Hugo의 설치만 잘 하면 사용 가이드는 인터넷에 잘 정리된 글들이 많다. 참조하면 활용에 문제는 없을 것이다.
Adsense 추가
구글 애드센스를 휴고 Blog에 넣고싶다면, 아래와 같은 절차를 거치면 된다.
themes/원하는_테마/layouts/partials/
디렉터리 안에adsense.html
파일을 만들고, 애드센스에 필요한 script를 붙여넣은 후 저장한다.themes/원하는_테마/layouts/partials/
디렉터리 안에head.html
파일을 열고,{{- partial "adsense.html" . -}}
한줄을 추가하고 저장한다.hugo -t 원하는_테마
명령으로 다시 빌드하고, 서버에 push한다.
sidebar 형태의 구문을 넣고싶다면,
baseof.html
파일을 수정해야 한다.<div class="grid-container">
태그로 main을 감싸고, main과 sidebar을 동일한 level에 배치한다.
# before
<main class="main">
{{- block "main" . }}{{ end }}
</main>
# after
<div class="grid-container">
{{ partial "sidebar.html" . }}
<main class="main">
{{- block "main" . }}{{ end }}
</main>
</div>
github page 자동화
github에서 제공하는 CI/CD 인 github actions 를 사용하면 push시 자동으로 배포를 할 수 있다.
- github actions -> Categories:Pages -> Hugo 선택
- 템플릿 파일로
HugoBlog/.github/workflows/hugo.yml
파일을 제공한다. 본인의 상황에 맞게 작성한 후 remote branch에 반영한다.
# Sample workflow for building and deploying a Hugo site to GitHub Pages
name: Deploy Hugo site to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["master"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
# Default to bash
defaults:
run:
shell: bash
jobs:
# Build job
build:
runs-on: ubuntu-latest
env:
HUGO_VERSION: 0.108.0
steps:
- name: Install Hugo CLI
run: |
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
- name: Install Dart Sass Embedded
run: sudo snap install dart-sass-embedded
- name: Checkout
uses: actions/checkout@v3
with:
submodules: recursive
- name: Setup Pages
id: pages
uses: actions/configure-pages@v3
- name: Install Node.js dependencies
run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
- name: Build with Hugo
env:
# For maximum backward compatibility with Hugo modules
HUGO_ENVIRONMENT: production
HUGO_ENV: production
run: |
hugo --minify --config config.yml
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: ./public
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
문법
- Hugo 문법
- hugo는 html 안에
{{ }}
형태로 hugo용 구문을 넣을 수 있다.{{ }}
안에--
,%%
,<>
를 넣어 용도에 따라 다양한 변형이 있을 수 있다.{{- }}
,{{ -}}
,{{- -}}
를 사용하면 앞/뒤쪽의 줄바꿈 및 빈 여백을 모두 제거해 준다.
- hugo는 html 안에
- 변수 선언
- 변수는 site, page 에 따라 다르게 선언 할 수 있다. config.yml 파일에 선언하면 site 단위로 선언되며, 전역 변수처럼 모든 page에서 참조 가능하다.
.Params
은 page 변수를 참조하며,site.Params
은 site 변수를 참조하는 방식이다.- page 변수는
hugo new
를 사용하여 만든 각 페이지(.md파일) 최상단에 작성된 메타데이터(+++ 혹은 — 로 감싸진 구역의 데이터)를 의미한다.
{{- $isHidden := Params.cover.hidden | default site.Params.cover.hiddenInSingle | default site.Params.cover.hidden }}
: page 변수로 ‘isHidden’ 를 선언하는 예시
- 조건문
{{- if (.Param "ShowToc") }}
: page변수에서 ShotToc가 있는지 체크
RelPermalink vs Permalink
- YOUR_CUSTOM_PATH.RelPermalink : baseurl을 / 로 처리한 링크 (/YOUR_CUSTOM_PATAH)
- YOUR_CUSTOM_PATH.Permalink : baseurl을 앞에 붙인 링크 (https://localhost:1313/YOUR_CUSTOM_PATAH)
파일 import
- 페이지 내부에서 다른 파일을 import 해오려면
{{template 파일명}}
혹은{{partial 파일명}}
을 사용하면 된다. - 이때 인자로 dictionary 데이터를 전달 할 수 있다.
{{ template 파일명 dict ("Pages" .Site.RegularPages )}}
와 같이 사용하면 호출된다. - 이런 방식으로 subpage들을 recursive하게 참조할 수 있다.
- 페이지 내부에서 다른 파일을 import 해오려면
{{ define "sample" }}
...
{{- $subSections := where .Site.Sections "Parent" .Sections }}
{{ template sample dict ("Sections" $subSections) }}
...
{{ end }}
define
{{- define 이름 -}} {{ end }}
을 세트로 사용하여 사이에 있는 모든 구문을 “이름” 으로 정의한다.- template이나 partial을 통해 define 된 구문을 호출할 수 있다. :
{{ tempalte 이름 }}
반복문
{{ range $itr := LIST}} {{ end }}
: LIST 안의 내용을 하나씩 itr 에 담아서 반복한다. 이때, range 문 안에서는 ‘.’ 문자는 $itr 을 의미하게 되므로, 주의한다.
구조
Scope
.
혹은$.
으로 변수를 호출하면 context 에 선언된 변수를 호출하는 것이다.- context는 page를 생성할 때 전달해 줄 수 있다.
{{ template FILE_NAME CONTEXT_NAME CONTEXT_VALUE}}
와 같이 사용하면 해당 파일 안에서.
은 CONTEXT_VALUE를 의미하게 된다. - 여러 데이터를 주고 싶다면
dict()
함수를 사용해서 dictionary 형태를 전달할 수도 있다.{{ template FILE_NAME dict("v1" v1 "v2" v2)}}
Site & Page & Section
- Site는 전체 프로젝트, Page는 내용물을 담고 있는 파일, Section은 폴더(디렉터리) 로 이해하면 편하다.
- 각각 변수 scope를 갖고 있어서
.Site.변수이름
,.Page.변수이름
,Section.변수이름
으로 변수를 참조 및 선언할 수 있다. - 하지만, config파일에서 선언하지 않는 한, 다른 파일에서 선언한 변수는 같은 page라도 참조가 되지 않는다. (필요시 전달할 수 있는 방법은 있다)
- Site (https://gohugo.io/variables/site/)
- Page (https://gohugo.io/variables/page/)
- Section (https://gohugo.io/content-management/sections/)
- Section이 되기위한 조건으로는 1) content 폴더의 직속 디렉터리 이거나, 2) 안에 _index.md 파일을 지녀야 한다.
- _index.md 파일을 지닌 디렉터리는 중간 가지 역할을 하고, index.md 파일을 지닌 디렉터리는 leaf node 역할을 하며, index.md 파일과 동일한 디렉터리에 들어있는 파일들은 각각의 페이지가 생성되지 않는다.
이미지
- 이미지는 /static 폴더에 넣으면 컴파일 시 /public 폴더로 이동된다.
- 즉, /static/image/sample.png 파일은 컴파일시 /public/image/sample.png 로 이동되며,

구문으로 markdown에서 이미지를 참조할 수 있다. - 단, 이미지 경로에 특수문자나 띄어쓰기가 없어야 함에 주의한다.
config
- config.yml, config.toml 등 설정 파일을 통해 hugo 동작시 옵션들을 세팅할 수 있다.
- baseURL: 최상위 root 경로를 설정하는 값이다.
hugo server --baseURL "/"
로 테스트 할 때 baseURL만 따로 세팅할 수도 있다.
오류 해결
배포 페이지 CSS 동작 오류
- hugo server로 로컬에서 동작시키면 css가 정상적으로 나오지만, 배포한 github page에서 css가 제대로 동작하지 않는다면 stylesheet를 선언하는 부분(보통 theme/선택한_테마/layouts/partials/head.html에 있음) 에서 baseurl이 정상적으로 설정되었는지 확인한다.
- stylesheet는
href="{{ $stylesheet.RelPermalink }}"
와 같이 정상적으로 설정했는지 확인한다. (url을 직접 string으로 입력하기보다 url을 세팅해주는 함수를 사용하는것을 권장)
Failed to find a valid digest in the ‘integrity’ attribute for resource
- hugo에서 제공하는
.Data.Integrity
기능을 사용하여 html tag에integrity
속성을 부여했을 경우 발생한다. - config파일에서 minify 설정을 해 놓으면, 파일의 불필요한 줄바꿈, 공백을 제거하는데, minify 하기 전 값을 sha256으로 인코딩 하여, 결과가 틀려지는 것이다.
# minify 설정
minify:
disableXML: true
minifyOutput: true
- 위와 같은 설정이 config파일에 있다면, fingerprint를 사용하기 전에 minify를 먼저 수행하라.
{{- $stylesheet := $stylesheet | minify | fingerprint "sha256"}}
. fingerprint의 default값은 sha256이므로, sha256은 제거해도 무관
github page 자동화 오류
fatal: remote error: upload-pack: not our ref 7821df1a10579b4a62917f0f07d3a5c482e872f6
- github actions/checkout@v3 에서 submodule의 특정 commit으로 checkout 이 안되는 현상이다.
render of "page" failed: "C:\HugoBlog\themes\hugo-PaperMod\layouts\_default\baseof.html:5:8": execute of template failed: template: _default/single.html:5:8: executing "_default/single.html" at <partial "head.html" .>: error calling partial: execute of template failed: template: partials/templates/opengraph.html:5:14: executing "partials/templates/opengraph.html" at <.Params.cover.image>: can't evaluate field image in type string
- 빌드 했을 때 위와같은 오류가 발생 한다면, golang과 hugo 버전 차이에 따라 페이지가 파싱이 제대로 되지 않는 경우이다. hugo 문법에 따라 페이지를 수정하거나 golang, hugo 버전을 최신으로 업데이트 해 본다.
image url 오류
- config.yml 파일에서 baseURL 을 설정하면 /static 디렉터리에 들어간 이미지들을 post 디렉터리의 markdown 으로 작성한 이미지 link가 정상적으로 세팅되지 않는다.
- 실제 코드 예시 :

- static 디렉터리 안의 이미지 예시 : /static/POST1/image1.png
- baseUrl 예시 :
http://mypage.com
- 빌드시 실제 image 파일 경로 : http://mypage.com/POST1/image1.png
- 빌드후 page에서 세팅된 image url : /POST1/image1.png
- 실제 코드 예시 :
- 이를 해결하려면 config.yml 에
canonifyurls: true
세팅을 하나 더 추가 해 준다. - 그러면 빌드시 page 에서 세팅된 image url 앞에 baseURL이 추가되어 경로 미스매칭이 해결된다.