Quartz 블로그 개설 방법
마지막 업데이트: 2026-01-26
예상 소요 시간: 2-3시간 (TrueNAS 설치 제외)
목차
개요
이 가이드는 다음과 같은 방식으로 독립적인 블로그를 구축하는 방법을 설명합니다:
- Git Server: Self-hosted Gitea (TrueNAS 또는 VPS)
- Static Site Generator: Quartz 4
- Content Source: 옵시디언 Vault (Git Submodule)
- CI/CD: Gitea Actions
- Hosting: Cloudflare Pages (무료)
장점
- ✅ 완전한 데이터 소유권
- ✅ 플랫폼 종속성 없음
- ✅ 옵시디언에서 작성 → 자동 배포
- ✅ 무료 호스팅 (Cloudflare Pages)
- ✅ 빠른 속도 (CDN 활용)
사전 준비
필수 계정
- Cloudflare 계정 (무료)
- GitHub 계정 (Quartz fork용, 선택)
필수 소프트웨어
- Git
- Node.js (v18 이상)
- 옵시디언 (또는 다른 마크다운 에디터)
필수 인프라 (택 1)
옵션 A: Self-hosted (권장)
- TrueNAS Scale + Gitea App
- Linux VPS + Docker
옵션 B: GitHub (대안)
- GitHub 계정 (Private Repository 가능)
1단계: Git Server 구축
A. TrueNAS + Gitea 방식 (Self-hosted)
1.1 Gitea 설치
# TrueNAS Scale Web UI에서
Apps → Available Applications → 검색: "gitea"설정 값:
Service Port: 3000 (HTTP)
SSH Port: 2222
Database: SQLite (간단) 또는 PostgreSQL (권장)설치 후 http://YOUR_NAS_IP:3000에서 접속하여 초기 설정 완료
1.2 Gitea Act Runner 설치
# TrueNAS Apps에서
Apps → Available Applications → 검색: "gitea-act-runner"연동 설정:
- Gitea 관리자 페이지 → Site Administration → Actions → Runners
- “Create new Runner” 클릭
- Registration Token 복사
- Act Runner 설치 시 Token 입력
검증:
# Gitea Actions 탭에서 Runner 상태 확인
Settings → Actions → Runners1.3 첫 저장소 생성
- Gitea에서 새 저장소 생성:
quartz-blog - Private/Public 선택
- Initialize with README 체크 해제
B. GitHub 방식 (대안)
GitHub를 사용할 경우:
- GitHub에서 Private Repository 생성
- GitHub Actions는 기본 제공되므로 별도 설정 불필요
- 아래 단계에서 Gitea → GitHub로 치환하여 진행
2단계: Quartz 설정
2.1 Quartz 저장소 가져오기
Gitea 마이그레이션 기능 사용:
Gitea Web UI → 오른쪽 상단 "+" → "New Migration"
Clone Address: https://github.com/jackyzha0/quartz.git
Owner: [Your Username]
Repository Name: quartz-blog
Visibility: Private
또는 로컬에서 Clone:
# Quartz 공식 저장소 clone
git clone https://github.com/jackyzha0/quartz.git quartz-blog
cd quartz-blog
# Remote를 내 Gitea로 변경
git remote remove origin
git remote add origin http://YOUR_GITEA_IP:3000/username/quartz-blog.git
git push -u origin main2.2 Content Submodule 추가
Content 저장소 준비:
# 옵시디언 Vault를 Git 저장소로 만들기
cd ~/Documents/ObsidianVault # 실제 경로로 변경
git init
git add .
git commit -m "Initial commit"
# Gitea에 푸시
git remote add origin http://YOUR_GITEA_IP:3000/username/obsidian-vault.git
git push -u origin mainQuartz에 Submodule로 추가:
cd ~/quartz-blog
git submodule add http://YOUR_GITEA_IP:3000/username/obsidian-vault.git content
git commit -m "Add content submodule"
git push디렉토리 구조:
quartz-blog/
├── content/ # Submodule (옵시디언 Vault)
│ ├── 4_Project Notes/
│ │ └── joshua_blog/ # 블로그로 공개할 노트들
│ │ ├── index.md
│ │ ├── 개발/
│ │ └── toy/
│ ├── Assets/ # 이미지 등
│ └── (기타 private 노트들)
├── quartz/ # Quartz 엔진
├── quartz.config.ts # 설정 파일
└── package.json
2.3 Quartz 설정 파일 수정
quartz.config.ts:
import { QuartzConfig } from "./quartz/cfg"
import * as Plugin from "./quartz/plugins"
const config: QuartzConfig = {
configuration: {
pageTitle: "내 블로그 제목",
enableSPA: true,
enablePopovers: true,
analytics: {
provider: "google", // 선택사항
tagId: "G-XXXXXXXXXX",
},
locale: "ko-KR",
baseUrl: "your-site.pages.dev", // 나중에 Cloudflare 주소로 변경
ignorePatterns: ["Private", "Templates", ".obsidian"],
defaultDateType: "created",
theme: {
fontOrigin: "googleFonts",
cdnCaching: true,
typography: {
header: "Noto Sans KR",
body: "Noto Sans KR",
code: "D2Coding",
},
colors: {
lightMode: {
light: "#faf8f8",
lightgray: "#e5e5e5",
gray: "#b8b8b8",
darkgray: "#4e4e4e",
dark: "#2b2b2b",
secondary: "#284b63",
tertiary: "#84a59d",
highlight: "rgba(143, 159, 169, 0.15)",
},
darkMode: {
light: "#161618",
lightgray: "#393639",
gray: "#646464",
darkgray: "#d4d4d4",
dark: "#ebebec",
secondary: "#7b97aa",
tertiary: "#84a59d",
highlight: "rgba(143, 159, 169, 0.15)",
},
},
},
},
plugins: {
transformers: [
Plugin.FrontMatter(),
Plugin.CreatedModifiedDate({
priority: ["frontmatter", "filesystem"],
}),
Plugin.Latex({ renderEngine: "katex" }),
Plugin.SyntaxHighlighting({
theme: {
light: "github-light",
dark: "github-dark",
},
keepBackground: false,
}),
Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
Plugin.GitHubFlavoredMarkdown(),
Plugin.TableOfContents(),
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
Plugin.Description(),
],
filters: [Plugin.RemoveDrafts()],
emitters: [
Plugin.AliasRedirects(),
Plugin.ComponentResources(),
Plugin.ContentPage(),
Plugin.FolderPage(),
Plugin.TagPage(),
Plugin.ContentIndex({
enableSiteMap: true,
enableRSS: true,
}),
Plugin.Assets(),
Plugin.Static(),
Plugin.NotFoundPage(),
],
},
}
export default config2.4 로컬에서 테스트
# 의존성 설치
npm install
# 특정 디렉토리만 빌드
npx quartz build --directory "content/4_Project Notes/joshua_blog"
# 로컬 서버 실행
npx quartz serve
# 브라우저에서 http://localhost:8080 확인주의사항:
- 이미지 경로: 옵시디언의
[[이미지.png]]형식은로 자동 변환됨 - Wikilinks:
[[노트 제목]]형식 지원 - Assets 폴더는 Symlink로 연결 (빌드 시 자동 처리)
3단계: Cloudflare Pages 연동
3.1 Cloudflare 계정 생성
- https://dash.cloudflare.com 접속
- 무료 계정 생성
3.2 Pages 프로젝트 생성
Workers & Pages → Create application → Pages → Connect to Git
중요: Git 연결하지 말고 “Direct Upload” 선택!
- Gitea/Self-hosted Git은 직접 연결 불가
- Wrangler CLI로 업로드할 예정
프로젝트 이름 설정:
Project name: my-quartz-blog
생성 완료 후:
URL: https://my-quartz-blog.pages.dev
3.3 API Token 생성
Cloudflare Dashboard → My Profile → API Tokens → Create Token
권한 설정:
Token name: Quartz Deploy Token
Permissions:
- Account / Cloudflare Pages / Edit
Zone Resources:
- All zones from an account / [Your Account]
생성 후 Token 복사 (다시 볼 수 없음!)
3.4 Account ID 확인
Cloudflare Dashboard → Workers & Pages → 우측 사이드바
Account ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
4단계: 자동 배포 설정
4.1 Gitea Secrets 설정
Gitea: quartz-blog 저장소 → Settings → Secrets → Actions
추가할 Secrets:
1. ACTIONS_TOKEN
- Value: Gitea Personal Access Token
- 생성: Gitea → Settings → Applications → Generate Token
- Scopes: repo (all)
2. CLOUDFLARE_API_TOKEN
- Value: (3.3에서 생성한 Token)
3. CLOUDFLARE_ACCOUNT_ID
- Value: (3.4에서 확인한 ID)
4.2 GitHub Actions Workflow 작성
.gitea/workflows/deploy.yml 생성:
name: Deploy to Cloudflare Pages
on:
workflow_dispatch: # 수동 실행 버튼
schedule:
- cron: '0 17 * * *' # 매일 UTC 17:00 (한국시간 새벽 2시)
push:
branches:
- main # main 브랜치 푸시 시 자동 실행
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# 1. 저장소 체크아웃
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive # Submodule도 함께 clone
fetch-depth: 0 # 전체 히스토리 (날짜 정보 필요)
token: ${{ secrets.ACTIONS_TOKEN }}
# 2. Content Submodule 최신화
- name: Update content submodule
run: |
git submodule update --remote --merge content
git config user.name "Gitea Actions"
git config user.email "actions@gitea.local"
# 변경사항이 있으면 커밋
if [[ -n $(git status -s) ]]; then
git add content
git commit -m "chore: update content submodule to latest"
git push https://${{ secrets.ACTIONS_TOKEN }}@YOUR_GITEA_DOMAIN/username/quartz-blog.git HEAD:main
fi
# 3. Node.js 설정
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
# 4. 의존성 설치
- name: Install dependencies
run: npm install
# 5. Assets Symlink 생성
# (옵시디언 Assets 폴더를 블로그 폴더에서 참조 가능하게)
- name: Create Assets symlink
run: |
ln -s "$(pwd)/content/Assets" "content/4_Project Notes/joshua_blog/Assets"
# 6. Quartz 빌드
- name: Build Quartz
run: |
npx quartz build --directory "content/4_Project Notes/joshua_blog"
# 7. Symlink 제거 (배포 전 정리)
- name: Remove Assets symlink
run: |
rm "content/4_Project Notes/joshua_blog/Assets"
# 8. Cloudflare Pages 배포
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy public --project-name=my-quartz-blog주의사항:
YOUR_GITEA_DOMAIN: 실제 Gitea 주소로 변경username/quartz-blog: 실제 저장소 경로로 변경my-quartz-blog: Cloudflare Pages 프로젝트 이름과 일치시키기
4.3 Workflow 파일 푸시
git add .gitea/workflows/deploy.yml
git commit -m "Add deployment workflow"
git push테스트 및 배포
첫 배포 테스트
Gitea: quartz-blog 저장소 → Actions 탭
→ "Deploy to Cloudflare Pages" 선택
→ "Run workflow" 버튼 클릭
실행 로그 확인:
- Checkout ✓
- Update submodule ✓
- Setup Node.js ✓
- Install dependencies ✓
- Create symlink ✓
- Build Quartz ✓
- Remove symlink ✓
- Deploy to Cloudflare ✓
배포 확인
https://my-quartz-blog.pages.dev
접속하여 블로그 정상 작동 확인
자동 배포 확인
# 옵시디언 Vault에서 새 노트 작성
cd ~/Documents/ObsidianVault
git add .
git commit -m "Add new post"
git push
# 다음날 새벽 2시에 자동으로 블로그 업데이트됨
# 또는 Gitea Actions에서 수동 실행트러블슈팅
문제 1: Submodule이 업데이트 안 됨
증상:
git submodule update --remote
Already up to date.
# 하지만 실제로는 새 커밋이 있음해결:
# Submodule 초기화 다시 하기
git submodule deinit -f content
git rm -rf content
git submodule add http://YOUR_GITEA_IP:3000/username/obsidian-vault.git content
git submodule update --init --recursive문제 2: 빌드 시 이미지가 안 보임
증상:
404 Error: image.png not found
원인:
- Assets 폴더 경로 문제
- Symlink가 제대로 생성 안 됨
해결:
# 로컬에서 테스트
cd quartz-blog
ln -s "$(pwd)/content/Assets" "content/4_Project Notes/joshua_blog/Assets"
npx quartz build --directory "content/4_Project Notes/joshua_blog"
# public 폴더에서 이미지 확인
ls -la public/Assets문제 3: Cloudflare 배포 실패
증상:
Error: Authentication error
해결:
- Cloudflare API Token 재생성
- Gitea Secrets에서
CLOUDFLARE_API_TOKEN업데이트 - Workflow 다시 실행
증상:
Error: Project not found
해결:
deploy.yml의--project-name확인- Cloudflare Pages에서 프로젝트 이름 다시 확인
문제 4: 한글 URL이 깨짐
증상:
/개발/toy/파일명 → /%EA%B0%9C%EB%B0%9C/...
해결:
// quartz.config.ts
configuration: {
...
slugifyFn: (str: string) => {
// 한글 URL 유지
return str
.toLowerCase()
.replace(/\s+/g, '-')
}
}문제 5: Gitea Actions Runner가 작동 안 함
증상:
Workflow waiting for runner...
해결:
# TrueNAS에서 Gitea Act Runner 상태 확인
Apps → gitea-act-runner → Logs
# Runner 재시작
Apps → gitea-act-runner → Stop → Start
# Gitea에서 Runner 등록 상태 확인
Site Administration → Actions → Runners추가 설정 (선택사항)
Custom Domain 연결
Cloudflare Pages → my-quartz-blog → Custom domains
→ Add a custom domain: blog.example.com
DNS 설정:
Type: CNAME
Name: blog
Content: my-quartz-blog.pages.dev
quartz.config.ts 업데이트:
baseUrl: "blog.example.com",Google Analytics 추가
// quartz.config.ts
analytics: {
provider: "google",
tagId: "G-XXXXXXXXXX",
}댓글 시스템 (Giscus)
// quartz.config.ts
plugins: {
emitters: [
Plugin.ContentPage({
enableGiscus: true,
giscus: {
repo: "username/repo",
repoId: "R_...",
category: "General",
categoryId: "DIC_...",
}
}),
]
}체크리스트
설정 완료 확인
- Gitea 서버 실행 중
- Gitea Act Runner 등록됨
- Quartz 저장소 생성
- Content submodule 추가
- Cloudflare Pages 프로젝트 생성
- API Token 발급
- Secrets 설정 완료
- Workflow 파일 작성
- 첫 배포 성공
- 도메인 접속 확인
- 자동 배포 테스트
일상 워크플로우
- 옵시디언에서 노트 작성
- Git commit & push
- (선택) Gitea Actions에서 수동 실행
- (자동) 매일 새벽 2시 자동 배포
- 블로그 확인
참고 자료
마치며
이 설정을 통해 다음과 같은 이점을 얻을 수 있습니다:
- 완전한 소유권: 모든 데이터가 내 서버에 있음
- 자동화: 옵시디언에서 작성하면 자동으로 배포
- 무료 호스팅: Cloudflare Pages 무료 플랜 활용
- 빠른 속도: Cloudflare CDN으로 전세계 어디서나 빠름
- 확장성: 필요 시 커스터마이징 가능