내 블로그의 정적 사이트 생성기, Eleventy
Eleventy는 무엇인가
Eleventy는 자바스크립트 기반의 정적 사이트 생성기(Static Site Generator)다. 공식 홈페이지에서는 "simpler static site generator"라고 소개하고 있다. 다른 정적 사이트 생성기에 비해 설정이 간단하고 유연한 것이 특징이다.
정적 사이트 생성기는 주로 블로그, 문서 사이트, 포트폴리오 같은 정적 웹사이트를 만들 때 사용한다. 동적 서버가 필요 없이 HTML, CSS, JavaScript 파일만 생성해주기 때문에 배포가 간단하고 속도가 빠르다. 나도 이 블로그를 만들 때 Eleventy를 선택했다.
Eleventy의 가장 큰 장점은 빌드 속도다. 공식 벤치마크에 따르면 4,000개의 마크다운 파일을 빌드할 때 Eleventy는 1.93초가 걸린 반면, Next.js는 70.65초, Gatsby는 29.05초가 걸렸다. 또한 13가지 이상의 템플릿 언어를 지원해 유연하게 사용할 수 있고, 기본적으로 클라이언트 사이드 JavaScript가 0이라 가볍다. 제로 설정으로 시작할 수 있어 진입 장벽도 낮다.
단점은 정적 사이트에만 특화되어 있다는 점이다. Next.js나 Gatsby처럼 대규모 애플리케이션을 만들기엔 적합하지 않다. 또한 Jekyll이나 Hugo에 비해 커뮤니티가 작은 편이다. 하지만 블로그나 문서 사이트처럼 순수한 정적 사이트를 만든다면 이런 단점은 큰 문제가 되지 않는다.
Eleventy 작동 방식
Eleventy는 입력 디렉토리에 있는 템플릿 파일들을 읽어서 HTML 파일로 변환한 뒤 출력 디렉토리에 저장하는 방식으로 작동한다. 하지만 단순히 파일을 변환하는 것이 아니라, 데이터 캐스케이드(Data Cascade)라는 시스템을 통해 여러 소스의 데이터를 병합한다.[1] 캐스케이드는 폭포처럼 위에서 아래로 흘러내린다는 뜻으로, 우선순위가 낮은 데이터부터 높은 데이터까지 순차적으로 쌓이며 병합되는 과정을 의미한다.
데이터 캐스케이드는 우선순위가 낮은 순서부터 다음과 같다:[1:1]
-
전역 데이터(Global Data) -
_data/디렉토리의 JSON이나 JavaScript 파일 -
디렉토리 데이터(Directory Data) - 특정 디렉토리 내 템플릿들이 공유하는 데이터. 디렉토리명과 같은 이름의 파일로 지정한다.[2] 예:
posts/posts.json{ "layout": "layouts/post.njk", "tags": ["posts"] }{ "layout": "layouts/post.njk", "tags": ["posts"] } -
레이아웃 프론트매터(Layout Frontmatter[3]) - 레이아웃 파일에 정의된 데이터. 레이아웃 파일 상단에 작성한다.[4]
--- title: My Blog author: Suhyeong Lee --- <!doctype html> <html> <head><title>내 블로그의 정적 사이트 생성기, Eleventy</title></head> <body></body> </html>--- title: My Blog author: Suhyeong Lee --- <!doctype html> <html> <head><title>내 블로그의 정적 사이트 생성기, Eleventy</title></head> <body></body> </html> -
템플릿 프론트매터(Template Frontmatter) - 각 템플릿 파일 상단에 정의된 메타데이터 (가장 높은 우선순위)
--- title: What is Eleventy? tags: [blog, eleventy] --- 본문 내용...--- title: What is Eleventy? tags: [blog, eleventy] --- 본문 내용...
같은 키의 데이터가 여러 곳에 정의되어 있으면, 우선순위가 높은 것이 낮은 것을 덮어쓴다.
flowchart TB
A[템플릿 파일<br/>.md, .njk, .liquid] --> B[Front Matter 파싱]
B --> C[데이터 병합]
subgraph Priority["데이터 캐스케이드"]
direction BT
D1[1. 전역 데이터<br/>우선순위: 낮음] --> D2[2. 디렉토리 데이터]
D2 --> D3[3. 레이아웃 프론트매터]
D3 --> D4[4. 템플릿 프론트매터<br/>우선순위: 높음]
end
Priority --> C
C --> G[템플릿 엔진<br/>렌더링]
G --> H[HTML 생성]
H --> I[출력 디렉토리<br/>_site/]
나는 이 블로그에서 Eleventy를 어떻게 사용했나?
이 블로그는 TypeScript로 작성된 Eleventy 설정을 사용한다. 템플릿 언어로는 Nunjucks를 선택했고, 마크다운 파서로는 markdown-it을 커스터마이징해서 사용하고 있다. 가장 큰 특징은 Obsidian에서 사용하는 문법을 그대로 지원한다는 점이다.
Obsidian 문법 지원
Obsidian 문법 지원을 위해 커스텀 markdown-it 플러그인을 만들었다. [[링크]] 형태의 위키링크와 ![[이미지.png]] 형태의 이미지 삽입을 지원한다. 또한 #태그 형태의 해시태그도 자동으로 태그 페이지 링크로 변환된다.
예를 들어 이런 Obsidian 문법이:
[[terminal-and-shell]]을 참고하면 좋다.
![[screenshot.png|스크린샷]]
#eleventy #blog[[terminal-and-shell]]을 참고하면 좋다.
![[screenshot.png|스크린샷]]
#eleventy #blog이렇게 변환된다:
<a href="/posts/terminal-and-shell/">Terminal and Shell</a>을 참고하면 좋다.
<figure>
<img src="/assets/screenshot.png" alt="스크린샷" />
<figcaption>스크린샷</figcaption>
</figure>
<a href="/tags/eleventy/" class="hashtag">#eleventy</a>
<a href="/tags/blog/" class="hashtag">#blog</a><a href="/posts/terminal-and-shell/">Terminal and Shell</a>을 참고하면 좋다.
<figure>
<img src="/assets/screenshot.png" alt="스크린샷" />
<figcaption>스크린샷</figcaption>
</figure>
<a href="/tags/eleventy/" class="hashtag">#eleventy</a>
<a href="/tags/blog/" class="hashtag">#blog</a>이렇게 하면 Obsidian에서 작성한 글을 그대로 블로그에 올릴 수 있어 편리하다.
마크다운 기능 확장
Mermaid 다이어그램 지원은 두 단계로 구현했다. 빌드 시점에 markdown-it의 fence renderer가 HTML 구조만 생성하고, 실제 SVG 렌더링은 CDN에서 불러온 Mermaid 라이브러리가 클라이언트에서 처리한다.
// config/markdown.ts - 빌드 시점: HTML 구조만 생성
md.renderer.rules.fence = function (tokens, idx, options, env, self) {
const token = tokens[idx];
const info = token.info.trim();
if (info === "mermaid") {
return `<div class="mermaid-container">
<pre class="mermaid">\${mermaidCode}</pre>
<!-- 풀스크린, 복사, 줌, 패닝 컨트롤... -->
</div>`;
}
// 일반 코드 블록: 복사 버튼 추가
return `<div class="code-block-container">
<button class="code-copy-btn">...</button>
\${defaultHTML}
</div>`;
};// config/markdown.ts - 빌드 시점: HTML 구조만 생성
md.renderer.rules.fence = function (tokens, idx, options, env, self) {
const token = tokens[idx];
const info = token.info.trim();
if (info === "mermaid") {
return `<div class="mermaid-container">
<pre class="mermaid">\${mermaidCode}</pre>
<!-- 풀스크린, 복사, 줌, 패닝 컨트롤... -->
</div>`;
}
// 일반 코드 블록: 복사 버튼 추가
return `<div class="code-block-container">
<button class="code-copy-btn">...</button>
\${defaultHTML}
</div>`;
};// base.njk - 클라이언트 시점: CDN에서 실제 렌더링
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
mermaid.initialize({
startOnLoad: true,
theme: theme === "dark" ? "dark" : "default",
});
mermaid.run(); // <pre class="mermaid"> 요소를 SVG로 변환// base.njk - 클라이언트 시점: CDN에서 실제 렌더링
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
mermaid.initialize({
startOnLoad: true,
theme: theme === "dark" ? "dark" : "default",
});
mermaid.run(); // <pre class="mermaid"> 요소를 SVG로 변환이 외에도 여러 마크다운 기능을 추가했다:
- 각주 지원: markdown-it-footnote 플러그인 사용
- 앵커 링크: markdown-it-anchor로 각 제목에 자동 링크 추가
- 외부 링크 구분: CSS로 외부 링크에 🌐 아이콘 자동 표시
- 코드 복사: 모든 코드 블록에 복사 버튼 추가
빌드 이벤트 활용
Eleventy의 이벤트 시스템을 활용해 빌드 시점에 필요한 작업을 자동으로 수행한다. eleventy.before 이벤트에서는 블로그 통계(총 포스트 수, 태그별 포스트 수 등)를 자동으로 생성한다. eleventy.after 이벤트에서는 각 포스트의 OG 이미지를 자동으로 생성한다.
// .eleventy.ts
export default function (eleventyConfig: EleventyConfig) {
// 빌드 전: 통계 생성
eleventyConfig.on("eleventy.before", async () => {
await generateStatistics();
});
// 빌드 후: OG 이미지 생성
eleventyConfig.on("eleventy.after", async () => {
if (postsCollection && postsCollection.length > 0) {
await generateAllOgImages(postsCollection);
}
});
}// .eleventy.ts
export default function (eleventyConfig: EleventyConfig) {
// 빌드 전: 통계 생성
eleventyConfig.on("eleventy.before", async () => {
await generateStatistics();
});
// 빌드 후: OG 이미지 생성
eleventyConfig.on("eleventy.after", async () => {
if (postsCollection && postsCollection.length > 0) {
await generateAllOgImages(postsCollection);
}
});
}이렇게 하면 빌드할 때마다 최신 통계와 OG 이미지가 자동으로 업데이트된다.
실제 코드의 함수 호출 구조는 다음과 같다:
flowchart TB
A[.eleventy.ts<br/>메인 설정] --> B[configureMarkdown]
A --> C[registerFilters]
A --> D[registerCollections]
A --> E[이벤트 등록]
E --> E1[eleventy.before<br/>generateStatistics]
E --> E2[eleventy.after<br/>generateAllOgImages]
B --> F[markdown-it 초기화]
F --> G[md.use 플러그인 등록]
G --> G1[markdownItAnchor<br/>앵커 링크]
G --> G2[markdownItFootnote<br/>각주]
G --> G3[wikilinkPlugin<br/>위키링크, 해시태그]
G --> G4[customFenceRenderer<br/>Mermaid, 코드 복사]
G4 --> H[eleventyConfig.setLibrary]
Eleventy는 기본적으로 클라이언트 사이드 JavaScript 없이 작동하지만, 이 블로그에서는 몇 가지 인터랙티브 기능을 위해 어쩔 수 없이 JavaScript를 사용한다. 다크모드 전환, Mermaid 다이어그램 렌더링과 줌/패닝 컨트롤, 코드 블록 복사 버튼, 통계 페이지의 페이지네이션 등이 그것이다. 하지만 핵심 콘텐츠는 모두 정적 HTML로 생성되므로 JavaScript 없이도 읽을 수 있다.
Eleventy는 설정이 간단하면서도 필요한 만큼 커스터마이징할 수 있어 개인 블로그를 만들기에 적합하다. TypeScript로 설정 파일을 작성할 수 있고, 플러그인 시스템을 통해 원하는 기능을 자유롭게 추가할 수 있다. 특히 빌드 속도가 빠르고, 기본적으로 클라이언트 사이드 JavaScript 없이 작동해 가벼운 블로그를 만들 수 있다는 점이 마음에 든다. 필요하다면 인터랙티브 기능을 위해 JavaScript를 선택적으로 추가할 수 있는 유연성도 갖추고 있다.
Markdown이나 정적 사이트 생성기(예: Eleventy, Obsidian)에서 문서의 맨 앞에 YAML 또는 JSON 형태로 넣는 짧은 데이터 덩어리입니다. 주로 제목, 날짜, 태그, 초안 여부 같은 정보를 담아 빌드 도구가 페이지를 렌더링하고 목록을 만들며 동작을 제어할 수 있게 합니다. ↩︎