왜 직접 만들었나

나만의 블로그를 가지고 싶었다!

처음엔 군대에서 Chirpy 테마를 그대로 사용해 만들다 Bootstrap 설정이 제대로 안돼서 일주일을 고생하다 유기했다. 그러다 다시 멈춰버린 시계를 작동시키기로 하고 개발을 해봤는데 시험기간엔 공부빼고 다 재밌는듯 역시 포폴 제출도 할겸.. 테마가 기능도 많고 완성도도 높았지만, 내가 원하는 디자인과 구조로 바꾸려 할수록 복잡한 구조가 발목을 잡기도 했고, 카피라이트를 붙이는게 싫었다. 그리하여 결국 테마를 통째로 걷어내고 처음부터 직접 만들기로 했다.


기술 스택

역할 도구
정적 사이트 생성 Jekyll 4.3
호스팅 / 배포 GitHub Pages + GitHub Actions
스타일링 순수 SCSS (Bootstrap 없음)
글 작성 Notion
발행 자동화 Node.js + Notion API

구조 잡기

레이아웃

Jekyll의 레이아웃 시스템을 활용해 9개 레이아웃을 직접 작성했다.

1
2
3
4
5
6
7
8
9
10
_layouts/
├── default.html   # 기본 HTML 뼈대
├── home.html      # 포스트 그리드 + 히어로 검색
├── post.html      # 본문 + 사이드 TOC
├── page.html      # 일반 페이지
├── archives.html  # 연도별 아카이브
├── tags.html      # 태그 목록
├── tag.html       # 태그별 포스트
├── categories.html
└── category.html

다크모드

JavaScript 없이 CSS 변수만으로 구현했다. localStorage로 설정을 저장하고, html[data-mode] 속성으로 테마를 전환한다.

1
2
3
4
5
6
7
8
9
10
11
:root {
  --bg: #f8f7f4;
  --text-h: #1c1c1c;
  /* ... */
}

html[data-mode='dark'] {
  --bg: #141414;
  --text-h: #f0efec;
  /* ... */
}

페이지 로드 시 깜빡임 방지를 위해 <head> 안에 인라인 스크립트로 data-mode를 미리 적용한다.

1
2
3
4
5
6
7
<script>
  (function(){
    var m = localStorage.getItem('hd-mode') ||
            (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
    document.documentElement.setAttribute('data-mode', m);
  })();
</script>

검색

Elastic Search 같은 외부 서비스 없이, Jekyll이 빌드 시 search.json을 생성하고 클라이언트에서 필터링하는 방식이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
---
layout: none
---
[
  
  {
    "title": "벨만-포드 알고리즘이란?",
    "url":   "/posts/%EB%B2%A8%EB%A7%8C-%ED%8F%AC%EB%93%9C-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%B4%EB%9E%80/",
    "tags":  "PS",
    "content": "## 핵심 요약\n\n\n음수 간선이 있는 그래프에서 최단경로를 구하거나 **음수 사이클 존재 여부**를 판단할 때 쓴다. 다익스트라가 안 되는 상황에서 꺼내는 카드.\n\n\n---\n\n\n## 개념 설명\n\n\n### 벨만-포드 알고리즘\n\n\n다익스트라는 매 단계에서 현재 가장 가까운 노드를 선택해 탐색한다. 덕분에 빠르지만, 음수 간선이 있으면 이미 \"확정\"된 노드가 나중에 더 짧은 경로로 도달될 수 있어서 틀린 답을 낸다.\n\n\n벨만-포드는 반대로 매 라운드마다 **모든 간선을 전부 확인**하며 dist를 갱신한다. 느리지만(O(VE)) 음수 간선도 처리할 수 있다.\n\n\n**작동 원리**: N개의 정점을 잊는 단순 경로에는 최대 N-1개의 간선이 존재한다. 따라서 모든 간선을 N-1번 확인하면 모든 최단경로가 확정된..."
  },
  
  {
    "title": "Jekyll 블로그 직접 만들고 노션으로 글 쓰기",
    "url":   "/posts/jekyll-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%A7%81%EC%A0%91-%EB%A7%8C%EB%93%A4%EA%B3%A0-%EB%85%B8%EC%85%98%EC%9C%BC%EB%A1%9C-%EA%B8%80-%EC%93%B0%EA%B8%B0/",
    "tags":  "blog github",
    "content": "## 왜 직접 만들었나\n\n\n나만의 블로그를 가지고 싶었다!\n\n\n처음엔 군대에서 Chirpy 테마를 그대로 사용해 만들다 Bootstrap 설정이 제대로 안돼서 일주일을 고생하다 유기했다. 그러다 다시 멈춰버린 시계를 작동시키기로 하고 개발을 해봤는데 ~~시험기간엔 공부빼고 다 재밌는듯 역시 포폴 제출도 할겸..~~ 테마가 기능도 많고 완성도도 높았지만, 내가 원하는 디자인과 구조로 바꾸려 할수록  복잡한 구조가 발목을 잡기도 했고, 카피라이트를 붙이는게 싫었다. 그리하여 결국 테마를 통째로 걷어내고 처음부터 직접 만들기로 했다.\n\n\n---\n\n\n## 기술 스택\n\n\n| 역할        | 도구                            |\n| --------- | -------------------..."
  },
  
  {
    "title": "0-1 BFS (Deque BFS) 란?",
    "url":   "/posts/0-1-bfs-deque-bfs/",
    "tags":  "PS",
    "content": "핵심 요약\n\n간선 가중치가 0 또는 1만 존재할 때, 일반 BFS 대신 deque(양방향 큐) 를 써서 O(V+E)에 최단경로를 구하는 기법이다. 비용 0인 이동은 deque 앞에, 비용 1인 이동은 deque 뒤에 넣는 것이 전부다.\n\n\n\n개념 설명\n\n왜 일반 BFS가 안 되는가?\n\n일반 BFS는 “먼저 방문 = 최단거리” 가 성립하는 이유가, 모든 간선 비용이 동일하기 때문이다. 그런데 어떤 이동은 0초, 어떤 이동은 1초라면 이 전제가 깨진다. 비용 0짜리 이동을 여러 번 해도 총 비용이 안 늘어나므로, 단순 큐로는 “현재 꺼낸 노드가 최소 비용” 임을 보장할 수 없다.\n\n그렇다고 다익스트라를 쓰면 O(E log V)인데, 가중치가 0/1뿐이라면 훨씬 더 단순하고 빠른 방법이 있다.\n\n0-1 BF..."
  },
  
  {
    "title": "1st-git_log",
    "url":   "/posts/1st/",
    "tags":  "github blog",
    "content": "진짜 어지럽네\n\n액션이 안돼\n"
  }
  
]

TOC (목차)

tocbot 라이브러리를 CDN으로 불러와 포스트 페이지에만 적용했다. 헤딩을 자동으로 파싱해서 사이드바에 스티키로 붙어있는다.


노션 연동

노션을 에디터로 쓰고 싶었다. 어디서든 접근할 수 있고, 블록 기반이라 글 구성이 편하기도 하고, markdown 언어는 좀 불편한 감이 있었는데 자동으로 md로 변환되게 스크립트를 작성하니 편하다.

구조

Notion에 Posts 데이터베이스를 만들고 아래 속성을 추가했다.

속성 타입 용도
Title 제목 포스트 제목
Date 날짜 발행일
Tags 멀티셀렉트 태그
Categories 멀티셀렉트 카테고리
Description 텍스트 카드 미리보기 요약
Status 셀렉트 Draft / Ready / Published

발행 스크립트

@notionhq/clientnotion-to-md 패키지를 사용해 Node.js 스크립트를 짰다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Status가 Ready인 페이지 조회
const response = await notion.databases.query({
  database_id: DATABASE_ID,
  filter: { property: 'Status', select: { equals: 'Ready' } },
});

// 본문 마크다운 변환
const mdBlocks = await n2m.pageToMarkdown(page.id);
const body = n2m.toMarkdownString(mdBlocks).parent;

// Jekyll front matter + 본문 저장
writeFileSync(filepath, frontmatter + body);

// 노션 상태 업데이트
await notion.pages.update({
  page_id: page.id,
  properties: { Status: { select: { name: 'Published' } } },
});

워크플로우

  1. 노션에서 글 작성
  2. Status → Ready
  3. node scripts/publish.js 실행
  4. _posts/ 저장 → 노션 Published → git commit + push → GitHub Actions 배포

publish.command 파일을 만들어두면 터미널 없이 더블클릭만으로 실행할 수 있다.


배포

GitHub Actions를 사용해 main 브랜치에 push하면 자동으로 빌드 후 배포된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
jobs:
  build:
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3
          bundler-cache: true
      - run: bundle exec jekyll build
      - uses: actions/upload-pages-artifact@v3

  deploy:
    needs: build
    uses: actions/deploy-pages@v4

별도 서버 없이 GitHub이 무료로 호스팅과 배포를 모두 처리한다.

image.png

Deploy에 성공하게 되면 이런 배포된 사이트의 주소를 알려주는데 클릭하면 비로소 내 블로그가 나의 서버 위에서가 아닌 세상에 공개된다.

다른 것 보다 이런 저런 오류로 Deploy에서 가장 시간을 많이 잡아먹으니 마음의 준비를 하길..


마치며

처음부터 직접 만드는 게 귀찮아 보일 수 있지만, 결과적으로 구조를 완전히 이해하고 원하는 대로 바꿀 수 있다는 게 가장 큰 장점이었다. 특히 노션 연동으로 글 쓰는 과정이 훨씬 편해졌다.

전체 소스는 GitHub에서 볼 수 있다.