Node 정적 블로그 만들기, 워드프레스 대신 고른 폴더 구조

2026.10.16 · 밥

블로그를 처음 열 때 제일 먼저 떠오르는 건 워드프레스다. 근데 워드프레스는 돌아가는 서버가 있어야 한다. WordPress 요구 사항 페이지는 PHP 8.3 이상, MySQL 8.0 이상이나 MariaDB 10.11 이상, 그리고 HTTPS 지원을 권장한다. PHP 와 데이터베이스가 돌아가야 한다는 얘기고, 서버를 직접 운영한다면 그 관리도 내 몫이 된다.

나는 이 블로그를 Node 정적 블로그로 만들었다. 글은 마크다운 파일로 쓰고, Node 스크립트 하나가 그걸 HTML 로 바꿔서 폴더 하나에 모은다. 그 폴더를 정적 호스팅에 올리면 끝이다. 서버에서 돌아가는 프로그램도 없고, 데이터베이스도 없고. 아래는 실제로 쓰고 있는 구조를 2026년 10월 16일 기준으로 적은 거다.

글과 그림을 빌드해서 site 폴더 하나에 모은다
글과 그림을 빌드해서 site 폴더 하나에 모은다

Node 정적 블로그의 기본 폴더 구조

폴더는 이렇게 나뉜다.

posts/             글 한 편에 마크다운 파일 하나
pages/             소개, 연락, 개인정보처리방침 같은 고정 페이지
images/            그림 원본
src/               style.css, search.js, 파비콘
tools/build.mjs    빌드 스크립트
tools/md.mjs       마크다운 변환기
site.config.json   블로그 이름, 주소, 분류 목록
site/              빌드 결과 (이 폴더만 올린다)

글 파일 맨 위에는 머리 정보를 둔다. 제목, 주소로 쓸 slug, 분류, 날짜, 검색 결과에 나올 설명 같은 거. 그 아래가 본문. 워드프레스라면 관리자 화면에서 칸을 채우는 걸 텍스트 파일 위쪽에 적는다고 보면 된다.

---
title: 글 제목
slug: node-static-blog-build
category: 정적 사이트 운영
date: 2026-10-16
description: 검색 결과에 나오는 설명
---
본문은 여기부터 마크다운으로.

site 폴더는 손으로 건드리지 않는다. 빌드할 때마다 지우고 처음부터 다시 만든다. 그래서 지운 글이 결과에 남을 일이 없지.

빌드 스크립트가 맡는 일

build.mjs 는 지금 470줄이다. 마크다운 변환에 더해 글마다 반복되는 일도 여기서 처리하는데, 맡는 일은 이 정도다.

먼저 글을 읽으면서 머리 정보부터 검사. 제목이나 날짜가 빠졌거나 slug 에 대문자가 들어가면 빌드가 멈춘다. 그다음 마크다운을 HTML 로 바꾸고, ## 소제목이 두 개 이상이면 목차를 붙인다. 글마다 canonical, 메타 설명, 구조화 데이터 같은 head 태그도 여기서 채운다. 첫 화면과 분류별 목록, 사이트맵, RSS, robots.txt, 404 페이지도 빌드가 만든다.

마크다운 변환은 라이브러리를 안 쓰고 md.mjs 라는 123줄짜리 작은 변환기를 따로 뒀다. 소제목, 목록, 표, 코드, 링크, 그림 정도만 처리하면 돼서다. 대신 마크다운 문법을 다 지원하진 않는데, 안 쓰는 문법은 그냥 글자로 나온다.

검색도 서버 없이. 빌드할 때 글 제목과 본문 앞부분을 search.json 하나로 모아 두고, 검색창에 치면 브라우저가 그 파일을 받아서 찾는다. 글이 수천 편이 되면 파일이 무거워질 거라 그때는 다시 생각해야 한다.

그림은 sharp 로 줄이고 정보를 뺀다

외부 패키지는 딱 하나, 그림을 다루는 sharp 0.35.5 다. images 폴더의 원본을 가로 1200px 이하 webp 로 바꾸고, 목록에 쓸 320x320 썸네일도 따로 만든다.

그림 처리에서 챙기는 건 두 가지. withoutEnlargement 는 sharp 크기 조절 문서에 따르면 원본이 목표보다 작을 때 키우지 않는 옵션이다. 작은 캡처를 억지로 1200px 로 늘리면 글자가 뭉개지니까 켜 둔다. 그리고 sharp 출력 문서에는 기본으로 메타데이터를 모두 지운다고 적혀 있다. 사진에 찍힌 위치나 촬영 정보가 따로 손 안 대도 빠진다는 얘기. 대신 그 안에 든 회전 정보도 같이 지워져서, 빌드는 저장 전에 인자 없이 rotate() 를 불러 EXIF 방향 정보가 있으면 그대로 방향부터 맞춘다.

본문용 webp 는 KB 단위로 반올림한 용량이 300을 넘으면 빌드가 실패하게 해 뒀다. 썸네일은 따로 재지 않고. 큰 그림이 올라가서 휴대폰에서 글이 늦게 뜨는 걸 미리 막으려고.

빌드 시간과 정적 블로그 올리기

이 PC(Node 22.14.0, Windows 11)에서 배포용 빌드를 세 번 돌려 봤다. 글 30개, 고정 페이지 3개, 그림 65개를 처리해서 site 폴더에 파일 183개가 생겼고, 시간은 4.5초, 4.9초, 5.4초였다. 그림 변환이 대부분일 거라고 보는데, 단계별로 재 보진 않았다.

> node tools/build.mjs
글 30개 · 페이지 3개 · 이미지 65개 → site/
걸린 시간 4.5초

올리는 건 site 폴더를 정적 호스팅에 그대로 넘기면 끝. 이 블로그는 Cloudflare Workers 정적 자산으로 올린다. 캐시 기간 같은 응답 헤더는 빌드가 site 안에 _headers 라는 파일을 만들어서 정한다. Cloudflare 문서를 보면 경로 한 줄, 그 아래 들여쓴 헤더 줄로 쓰고, 별표(*)로 여러 경로를 한 번에 잡을 수 있다. 다만 Worker 코드가 직접 만든 응답에는 이 파일이 안 붙는다고 적혀 있으니, Worker 를 같이 쓴다면 그쪽 헤더는 따로 챙겨야 한다.

캐시는 처음에 길게 잡았다가 1시간으로 줄였다. 배포 직후 잠깐 난 404 에도 캐시가 붙어서 그림이 깨진 채 남았던 적이 있어서다. 그 얘기는 블로그 새로고침이 느릴 때 쓴 글에 자세히 적었다.

워드프레스 대신 정적 블로그가 안 맞는 경우

장점만 있는 건 아니다. 일단 댓글이 기본으로 없으니까, 붙이려면 외부 서비스를 쓰거나 따로 서버 기능을 만들어야 한다. 관리자 화면도 없어서 휴대폰으로 글을 쓰고 바로 올리긴 어렵다. 그리고 뭔가 바꾸고 싶으면? 결국 빌드 스크립트를 열어 코드를 고쳐야 한다. 플러그인을 눌러서 기능을 붙이는 방식에 익숙하다면 이쪽이 더 번거롭다.

반대로 글은 텍스트 파일이라 git 으로 버전을 관리할 수 있다. 호스팅 서버의 운영체제나 PHP, 데이터베이스를 직접 업데이트할 일도 없고. 그래도 Node 와 sharp 같은 빌드 도구는 계속 업데이트해야 한다. 주소 규칙이나 그림 용량 같은 걸 빌드가 막아 주니 실수가 올라가기 전에 걸린다. 주소 규칙을 어떻게 정했는지는 블로그 주소 규칙 글에 따로 정리해 뒀다.

처음 해 본다면 글과 그림을 둘 폴더부터 만들고, 마크다운 하나를 HTML 하나로 바꾸는 스크립트부터 짜 보자. 목록, 사이트맵, 그림 줄이기는 귀찮아질 때 하나씩 붙이면 된다.

다음엔 줄바꿈을 한 번에 정리하다가 PNG 46장을 망가뜨린 날 얘기를 하려고 한다.