사이트 제작기 시리즈 · 1부 — 왜 정적 홈페이지인가 / 2부 — 콘텐츠 3구역 / 3부 (이 글) — 자동화 파이프라인 / 4부 — 커스터마이징과 관리자 패널
2부 마지막에서 “AI가 매일 자동으로 글을 올린다"고 했습니다. 이번 편은 그 무인 자동화 라인을 통째로 열어 봅니다. 사람이 잠든 밤사이 이 사이트에서는 세 개의 톱니바퀴가 맞물려 돌아갑니다.
① 밤 11시경 — AI가 오늘의 IT뉴스를 생성해 파일로 저장
② 밤 11시 30분 — 로컬 스케줄러가 변경분을 자동으로 GitHub에 push
③ 아침 6시 — 예약된 글을 공개하기 위해 사이트를 자동 재빌드
④ 접속하는 순간 — 그날의 QT가 팝업 모달로 방문자를 맞이
하나씩 뜯어보겠습니다.
1. 톱니바퀴 ① — AI가 IT뉴스를 쓴다
매일의 IT뉴스(content/blog/daily-it-news/)는 AI가 씁니다. 매일 밤, AI 도구가 그날의 AI·IT 주요 소식을 정리해 마크다운 파일 한 장을 만들어 이 폴더에 떨어뜨립니다.
이때 글이 항상 같은 형식으로 나오도록 **틀(템플릿)**이 준비되어 있습니다. Hugo에서는 이런 새 글 템플릿을 archetypes/ 폴더에 둡니다. 실제 IT뉴스 전용 템플릿(archetypes/daily-it-news.md)은 이렇게 생겼습니다.
---
title: "{{ .Date.Format "2006년 1월 2일" }} IT뉴스"
date: {{ .Date }}
author: "Gihyun Park"
summary: ""
tags: ["IT뉴스"]
comments: false
categories: ["매일의 IT뉴스", "blog"]
---
## 오늘의 주요 뉴스
1.
2.
3.
{{ .Date.Format "2006년 1월 2일" }} 부분이 재미있는데, Hugo(Go 언어)에서는 날짜 형식을 “2006년 1월 2일"이라는 기준 날짜로 표기합니다. 이 자리에 실제 오늘 날짜가 자동으로 채워져 “2026년 7월 7일 IT뉴스” 같은 제목이 만들어집니다.
이 틀 덕분에 AI는 매일 “내용"만 채우면 되고, 제목·날짜·카테고리·태그 같은 뼈대는 항상 일관되게 유지됩니다.
2. 톱니바퀴 ② — 밤 11시 30분, 스케줄러가 자동으로 올린다
AI가 파일을 만들어 두어도, 누군가 GitHub에 올려(push) 주지 않으면 사이트에는 반영되지 않습니다. 이 “올리는” 일을 사람 대신 해 주는 것이 macOS의 launchd 스케줄러와 자동 푸시 스크립트입니다.
자동 푸시 스크립트 (scripts/auto_push.sh)
핵심 로직만 추리면 이렇습니다.
# 1. 원격 저장소와 먼저 동기화 (충돌 방지)
git pull --rebase origin main
# 2. 바뀐 파일이 있는지 확인
STATUS_OUT=$(git status --porcelain)
if [ -z "$STATUS_OUT" ]; then
echo "변경된 문서가 없습니다. 종료."
exit 0 # 바뀐 게 없으면 아무것도 안 하고 끝냄
fi
# 3. 바뀐 게 있으면 날짜를 붙여 커밋하고 push
git add -A
git commit -m "chore: 일일 자동 생성 컨텐츠 추가 [$(date '+%Y-%m-%d')]"
git push origin main
똑똑한 점은 **“바뀐 게 있을 때만 움직인다”**는 것입니다. git status --porcelain으로 변경 사항을 검사해, 새 글이 없으면 조용히 종료합니다. 덕분에 의미 없는 빈 커밋이 쌓이지 않습니다. 또 맨 처음에 git pull --rebase를 먼저 해서, 다른 곳(예: 관리자 패널)에서 생긴 변경과 충돌하는 것도 예방합니다.
이 스크립트를 매일 부르는 스케줄러
이 스크립트를 사람이 매일 밤 실행할 수는 없으니, macOS의 백그라운드 스케줄러인 launchd에 등록해 뒀습니다.
- 등록 파일:
~/Library/LaunchAgents/com.jiwumission.autopush.plist - 실행 시각: 매일 밤 11시 30분(23:30)
- 하는 일:
auto_push.sh를 무인으로 호출
작동 여부는 scripts/auto_push.log에 기록됩니다. 스케줄러를 다시 등록하거나 해제하려면 다음 명령을 씁니다.
# 서비스 해제
launchctl unload ~/Library/LaunchAgents/com.jiwumission.autopush.plist
# 서비스 등록
launchctl load ~/Library/LaunchAgents/com.jiwumission.autopush.plist
정리하면, 밤 11시경 AI가 글을 쓰고 → 11시 30분에 스케줄러가 자동으로 올린다는 흐름입니다. 사람은 아무것도 누르지 않습니다.
3. 톱니바퀴 ③ — 아침 6시, 예약된 글이 스스로 공개된다
여기서 정적 홈페이지 특유의 문제가 하나 생깁니다. 정적 사이트는 “빌드하는 그 순간"의 상태로 굳어집니다. 그래서 미래 날짜로 예약해 둔 글은, 그 날짜가 되어도 사이트를 다시 빌드하지 않으면 계속 숨어 있습니다.
이 문제를 풀기 위해 GitHub Actions에 매일 아침 6시 자동 재빌드 워크플로우(.github/workflows/scheduled-publish.yml)를 두었습니다.
on:
schedule:
- cron: "0 21 * * *" # UTC 21:00 = 한국시간 06:00
jobs:
trigger-rebuild:
runs-on: ubuntu-latest
steps:
- name: Create scheduled rebuild commit
run: |
DATE=$(TZ="Asia/Seoul" date "+%Y-%m-%d %H:%M KST")
git commit --allow-empty -m "chore: scheduled daily publish [$DATE]"
git push origin main
원리는 단순하고 영리합니다. 내용이 텅 빈 커밋(--allow-empty)을 하나 만들어 push합니다. 그러면 Cloudflare Pages가 “저장소에 변화가 생겼다!“고 감지해 사이트를 새로 빌드하고, 그 시점에 날짜가 도래한 예약 글들이 자연스럽게 공개됩니다.
💡
cron: "0 21 * * *"이 왜 아침 6시일까요? GitHub Actions는 세계 표준시(UTC)로 돕니다. 한국은 UTC보다 9시간 빠르므로, UTC 21시는 한국 시간으로 다음 날 오전 6시입니다.
4. 톱니바퀴 ④ — 접속하면 오늘의 QT가 팝업으로
이제 이 사이트에서 가장 눈에 띄는 기능, 오늘의 QT 팝업 모달입니다. 방문자가 첫 페이지에 들어오면 그날 날짜에 해당하는 성경 묵상이 부드럽게 떠오릅니다. 2부에서 봤듯 QT는 367개나 미리 쌓여 있는데, 그중 정확히 오늘 것만 골라 보여주는 겁니다. 어떻게 할까요?
4-1. 각 QT를 “작은 데이터 API"로 만든다
핵심 아이디어는 이렇습니다. QT 글 하나하나를, 보통의 HTML 페이지뿐 아니라 JavaScript가 읽기 좋은 데이터 파일(JSON)로도 함께 출력하는 것입니다. 이를 위해 Hugo의 커스텀 출력 형식을 정의한 템플릿(layouts/databank/daily-bible/single.dailybiblejson.txt)이 있습니다.
{
"title": {{ .Title | jsonify }},
"date": {{ .Date.Format "2006-01-02" | jsonify }},
"passage": {{ .Params.passage | jsonify }},
"status": {{ .Params.qt_status | jsonify }},
"url": {{ .RelPermalink | jsonify }},
"content": {{ .Content | jsonify }}
}
이 템플릿 덕분에 예를 들어 2026년 7월 7일 QT는 다음 주소에서 JSON 데이터로 접근할 수 있게 됩니다.
/databank/daily-bible/2026-07-07/index.txt
즉, 서버 없이도 각 날짜의 QT가 하나의 “미니 API 엔드포인트"가 되는 셈입니다. 이것이 정적 사이트에서 데이터를 다루는 전형적인 기법입니다.
4-2. 홈 화면 JavaScript가 오늘 것을 불러온다
이제 첫 페이지(layouts/home.html)에 심어 둔 JavaScript가 이 데이터를 이용합니다. 핵심 흐름만 옮기면 이렇습니다.
const todayStr = getTodayString(); // 예: "2026-07-07"
// ① 오늘 하루 안 보기를 눌렀다면 그냥 종료
if (localStorage.getItem("daily_bible_hide_today") === todayStr) return;
// ② 오늘 날짜의 QT 데이터를 가져온다
const jsonUrl = `/databank/daily-bible/${todayStr}/index.txt`;
fetch(jsonUrl)
.then(response => {
if (!response.ok) throw new Error("오늘 QT 없음"); // 파일 없으면 조용히 스킵
return response.json();
})
.then(todayData => {
// ③ 내용이 있고, 아직 초안(draft) 상태가 아닐 때만 모달 표시
if (todayData.content && todayData.status !== "draft") {
showDailyBibleModal(todayData.title, todayData.content);
}
})
.catch(() => { /* 오늘 QT가 없으면 아무 일도 일어나지 않음 */ });
동작을 말로 풀면:
- 오늘 날짜를 계산한다 (
2026-07-07형태). - localStorage 확인 — 이 방문자가 “오늘 하루 보지 않기"를 눌렀다면 즉시 종료.
- 오늘 날짜의 JSON을 fetch — 파일이 없으면(그날 QT가 준비 안 됐으면) 에러가 나고, 그 에러를 조용히 삼켜서 아무 팝업도 뜨지 않는다.
- 내용이 있고 아직
draft상태가 아니면 → 모달을 띄운다.
4-3. “오늘 하루 보지 않기"의 정체
모달을 닫을 때 “오늘 하루 보지 않기” 체크박스를 켜면, 브라우저의 localStorage에 오늘 날짜를 저장합니다.
function closeModal() {
if (hideCheckbox.checked) {
localStorage.setItem("daily_bible_hide_today", todayStr); // 오늘 날짜 기록
}
// ... 부드럽게 사라지는 애니메이션 ...
}
그러면 위 ②단계에서 “저장된 날짜 == 오늘"이 되어 다시 뜨지 않습니다. 그리고 날짜가 바뀌면 저장된 값(어제 날짜)과 오늘이 달라지므로 다음 날 다시 정상적으로 등장합니다. 서버도, 로그인도, 데이터베이스도 없이 브라우저 저장소 한 줄로 해결한 겁니다.
🔎 참고로 QT 목록 페이지(
/databank/daily-bible/)에도 비슷한 원리로 “오늘의 묵상 보기” 버튼이 있습니다. 오늘 날짜 페이지가 실제로 존재할 때만 버튼이 나타나도록,fetch(... , { method: "HEAD" })로 파일 존재 여부를 먼저 확인합니다.
5. 네 톱니바퀴를 한 장으로
[밤 11시경] AI가 IT뉴스 .md 생성 ─┐
│
[밤 11:30] launchd → auto_push.sh │→ git push → Cloudflare 재빌드·배포
(변경 있을 때만 동작) ─┘
[아침 06:00] GitHub Actions가 빈 커밋 push → 예약 글 공개용 재빌드
[방문 순간] home.html JS가 오늘 날짜 QT(JSON) fetch
→ 있으면 팝업 모달 / 없거나 "하루 안 보기"면 스킵
이 네 가지가 맞물려서, 관리자가 아무 버튼도 누르지 않는 날에도 사이트는 매일 새로운 IT뉴스를 발행하고, 방문자에게 그날의 말씀을 건넵니다.
마치며
3부의 요점:
- IT뉴스는 AI 생성(정해진 archetype 틀) → 밤 11:30 launchd 자동 push → Cloudflare 배포로 이어진다.
- 아침 6시 빈 커밋은 예약 글을 공개하기 위한 정적 사이트 특유의 재빌드 트릭이다.
- 오늘의 QT 팝업은 각 QT를 JSON 엔드포인트로 출력하고, 홈 화면 JS가 오늘 것만 fetch해 모달로 띄우며,
localStorage로 “하루 안 보기"를 처리한다.
마지막 4부에서는 눈에 보이는 껍데기 — 카드 레이아웃, 반응형 디자인, 다크 모드, 그리고 코딩 없이 브라우저에서 설정을 바꾸는 Git 기반 관리자 패널 — 을 다루고, 제작 과정에서 실제로 부딪혔던 대표적인 문제와 해결법도 함께 정리하겠습니다.