LLM이 망가뜨린 마크다운 문법 잡기: 블로그 빌드 에러를 99% 예방하는 자동화 린터(Linter) 도입기
최근 AI(LLM)를 활용해 블로그 콘텐츠 초안을 작성하거나 번역하는 사례가 급증했습니다. GPT-5.1이나 Claude Sonnet 4.5 같은 고성능 언어 모델은 훌륭한 글을 써내지만, 한 가지 치명적인 약점을 가지고 있습니다. 바로 마크다운(Markdown) 문법을 미세하게 망가뜨린다는 점입니다.
백틱(`)의 짝이 맞지 않거나, 표(Table)의 파이프라인(|) 기호가 누락되거나, YAML Frontmatter에 따옴표 처리되지 않은 콜론(:)을 넣어 Next.js, Gatsby, Hugo, Astro 같은 정적 사이트 생성기(SSG)의 빌드를 터뜨리곤 합니다.
이 글에서는 LLM이 생성한 마크다운의 고질적인 문법 오류 패턴을 분석하고, 이를 배포 전 단계에서 100% 잡아내어 블로그 빌드 에러를 예방하는 자동화 린터(Linter) 구축 과정을 실무 코드와 함께 상세히 공유합니다.
LLM이 마크다운 문법을 망가뜨리는 3가지 고질적 패턴
LLM은 문맥을 예측하여 텍스트를 생성하는 확률 모델이기 때문에, 마크다운의 엄격한 구문 규칙(Syntax Rule)을 완벽히 준수하지 못할 때가 많습니다. 특히 다음과 같은 세 가지 패턴에서 오류가 빈번하게 발생합니다.
1. YAML Frontmatter 구문 오류
대부분의 SSG 블로그는 포스트 최상단에 메타데이터를 정의하는 YAML Frontmatter를 사용합니다. LLM은 이 영역에 타이틀이나 설명을 작성할 때 YAML 문법을 자주 위반합니다.
---
title: AI 시대: 마크다운 린터 구축법 # 오류: 문자열 내에 콜론(:)이 있어 파싱 에러 발생
date: 2024-10-24
tags: [AI, Markdown, Linter]
---
- 원인: YAML에서
key: value구조를 가집니다.title값 내부에 콜론(:)이 들어가면, 파서(Parser)는 이를 새로운 키-값 쌍으로 오인하여 빌드 오류를 쏩니다. 이 경우 반드시 큰따옴표(")로 감싸야 합니다.
2. 불완전한 마크다운 표(Table) 구조
표를 생성할 때 가로 구분선(| --- |)의 하이픈 개수가 부족하거나, 행마다 열(Column)의 개수가 맞지 않는 현상이 발생합니다.
| 도구명 | 특징 | 난이도 |
| --- | --- | <-- 오류: 마지막 열의 구분선(|) 누락
| Markdownlint | 규칙이 고정됨 | 쉬움 |
| Custom AST | 맞춤형 규칙 가능 | 보통 |
3. 코드 블록 백틱(`) 비대칭 및 중첩
코드 블록 내부의 예시 코드를 설명하면서 백틱 기호를 제대로 닫지 않거나, 마크다운 문서 자체를 코드 블록 안에 담을 때 이스케이프(\) 처리를 누락하여 전체 문서 구조를 깨뜨립니다.
해결책 비교: 어떤 도구로 마크다운을 검증해야 할까?
전통적인 방식과 자동화 방식의 장단점을 비교해 보면 왜 맞춤형 정규식(RegExp) 기반 린터를 구축해야 하는지 명확해집니다.
| 비교 항목 | 수동 검수 (Human Proofreading) | 기존 markdownlint 라이브러리 | 맞춤형 정규식 기반 Linter 구축 (추천) |
|---|---|---|---|
| 검수 속도 | 매우 느림 (수작업) | 매우 빠름 (밀리초 단위) | 매우 빠름 (밀리초 단위) |
| YAML 검증 | 불완전함 (오타 간과) | 기본적으로 지원 안 함 | YAML 파서 연동으로 완벽 검증 |
| LLM 특화 오류 감지 | 휴먼 에러 발생 가능 | 단순 스타일(줄바꿈 등) 위주 체크 | 커스텀 규칙(정규식 + 패턴 매칭) 적용 가능 |
| CI/CD 파이프라인 연동 | 불가능 | 가능 | 가능 (GitHub Actions 지원) |
기존의 markdownlint는 “줄 끝에 공백을 남기지 마라”, “헤더 레벨은 순서대로 작성하라” 같은 ’스타일 규격’에 집중되어 있습니다. 반면, 우리가 필요한 것은 “빌드를 깨뜨리는 구조적 결함”을 잡아내는 실용적인 검증기입니다.
정규식 기반의 맞춤형 마크다운 린터 구축하기
Node.js 환경에서 정규식(RegExp)과 yaml 파서를 결합하여, LLM 오작동을 정밀 타격하는 커스텀 린터 스크립트를 작성해 보겠습니다. 별도의 마크다운 AST 파서 없이도 아래에서 다루는 패턴 대부분은 정규식과 라인 단위 검사만으로 충분히 잡아낼 수 있습니다.
1. 개발 환경 세팅
프로젝트 루트 디렉토리에서 필요한 패키지를 설치합니다.
npm install yaml
2. 린터 스크립트 작성 (lint-markdown.js)
이 스크립트는 블로그 포스트 폴더 내의 .md 또는 .mdx 파일을 읽어 YAML Frontmatter의 유효성을 검사하고, 마크다운 문법 오류를 추적합니다.
const fs = require('fs');
const path = require('path');
const YAML = require('yaml');
const POSTS_DIR = path.join(__dirname, 'content/posts'); // 블로그 포스트 경로
function checkMarkdownFile(filePath) {
const fileContent = fs.readFileSync(filePath, 'utf-8');
const relativePath = path.relative(__dirname, filePath);
let errors = [];
// 1. YAML Frontmatter 문법 검사
try {
const rawFrontmatter = fileContent.match(/^---([\s\S]*?)---/);
if (!rawFrontmatter) {
errors.push("Frontmatter(시작/끝 '---')가 누락되었거나 형식이 잘못되었습니다.");
} else {
// 정규식으로 뽑아낸 frontmatter 블록을 yaml 라이브러리로 엄격하게 파싱 시도
YAML.parse(rawFrontmatter[1]);
}
} catch (yamlError) {
errors.push(`YAML 파싱 에러: ${yamlError.message}`);
}
// 2. 비대칭 백틱 코드 블록 검사
const backtickCount = (fileContent.match(/^```/gm) || []).length;
if (backtickCount % 2 !== 0) {
errors.push(`코드 블록 백틱(\`\`\`)의 짝이 맞지 않습니다. (총 개수: ${backtickCount}개)`);
}
// 3. 깨진 마크다운 링크 검사 (괄호 짝 불일치)
const brokenLinkRegex = /\[([^\]]+)\]\(([^)]*)$/gm; // [텍스트](링크 구조에서 닫는 괄호 누락
if (brokenLinkRegex.test(fileContent)) {
errors.push("닫는 괄호가 누락된 불완전한 링크 구조가 발견되었습니다.");
}
// 4. 깨진 표(Table) 구조 검사
const lines = fileContent.split('\n');
lines.forEach((line, index) => {
if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
const pipes = (line.match(/\|/g) || []).length;
// 표 헤더 구분선인 경우 파이프 개수 체크
if (line.includes('---') && pipes < 3) {
errors.push(`L${index + 1}: 표 구분선 행의 열 개수가 부족합니다.`);
}
}
});
return errors;
}
// 특정 디렉토리 내의 모든 마크다운 파일 순회
function runLinter() {
let hasFailed = false;
const files = fs.readdirSync(POSTS_DIR).filter(file => file.endsWith('.md') || file.endsWith('.mdx'));
console.log(`🔍 총 ${files.length}개의 마크다운 파일 검증 시작...\n`);
files.forEach(file => {
const fullPath = path.join(POSTS_DIR, file);
const fileErrors = checkMarkdownFile(fullPath);
if (fileErrors.length > 0) {
console.error(`❌ [오류 발생] 파일명: ${file}`);
fileErrors.forEach(err => console.error(` - ${err}`));
console.error('--------------------------------------------------');
hasFailed = true;
}
});
if (hasFailed) {
console.error("\n🚨 검증 실패: 일부 마크다운 문법에 오류가 있습니다. 빌드를 중단합니다.");
process.exit(1); // 에러 코드를 반환하여 CI/CD 파이프라인을 멈춤
} else {
console.log("✨ 검증 완료: 모든 마크다운 파일의 문법이 정상입니다.");
process.exit(0);
}
}
runLinter();
CI/CD 자동화 연동: GitHub Actions 구성하기
아무리 뛰어난 린터 스크립트를 작성했더라도, 사람이 실행하는 것을 깜빡한다면 무용지물입니다. 글을 쓰고 git push를 하거나 Pull Request를 생성할 때 자동으로 마크다운 문법을 검사하고 에러가 있으면 배포를 차단하도록 GitHub Actions 워크플로우를 구축합니다.
블로그 저장소의 .github/workflows/markdown-lint.yml 파일로 아래 설정을 저장합니다.
name: Markdown Lint & Build Guard
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: 저장소 체크아웃
uses: actions/checkout@v3
- name: Node.js 환경 설정
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: 의존성 패키지 설치
run: npm ci
- name: 마크다운 린터 실행
run: node lint-markdown.js
이 워크플로우가 적용되면, LLM이 생성한 콘텐츠에 아주 작은 마크다운 규격 오류가 포함되어 있어도 GitHub Actions 단계에서 빨간 불을 띄우며 빌드를 거부하게 됩니다. 이로써 메인 브랜치가 오염되어 프로덕션 블로그에 흰 화면이나 500 에러가 뜨는 대참사를 미연에 방지할 수 있습니다.
린터 자동화 도입으로 기대할 수 있는 효과
수동 검수에 의존하던 방식과 비교하면, 이런 자동화 린터를 파이프라인에 이식했을 때 기대할 수 있는 효과는 원리적으로 명확합니다.
- 빌드 실패의 조기 차단: frontmatter 파싱 에러나 백틱 짝 불일치처럼 빌드 자체를 깨뜨리는 구조적 오류를, 배포 이전 CI 단계에서 걸러내므로 메인 브랜치에 오류가 섞여 들어갈 여지가 원천적으로 줄어듭니다.
- 반복되는 오류 패턴 대응: LLM이 자주 만드는 오류(frontmatter 내 콜론 이스케이프 누락, 코드 블록 백틱 미매칭, 표 구분선 열 개수 불일치 등)는 특정 패턴으로 반복되는 경향이 있어, 정규식 기반 규칙만으로도 상당 부분을 사전에 잡아낼 수 있습니다.
- 검수 시간 절감: 사람이 매번 눈으로 diff를 훑는 대신 스크립트가 몇 초 안에 검증을 끝내므로, 특히 발행 빈도가 높은 파이프라인일수록 절감 효과가 커집니다.
다만 정확한 수치(빌드 실패율 감소폭, 가장 빈번한 에러 유형의 비율, 절감되는 발행 시간 등)는 실제 콘텐츠 볼륨과 LLM 모델, 프롬프트 구성에 따라 크게 달라지므로, 이 글에서 임의의 수치를 단정하기보다는 여러분의 파이프라인에 도입한 뒤 직접 로그를 쌓아 측정해 보시길 권장합니다.
이 시스템 덕분에 콘텐츠 에디터와 개발자는 더 이상 “어디서 마크다운 문법이 깨졌는지” 찾기 위해 빌드 로그를 한 줄씩 뜯어보며 디버깅하는 불필요한 공수를 크게 덜어낼 수 있습니다.
안정적인 글쓰기 생태계를 위하여
AI가 초안을 잡고 인간이 교정(Proofreading)하는 ’코파일럿 방식’의 콘텐츠 생산은 이제 거스를 수 없는 대세가 되었습니다. 그러나 AI의 뛰어난 작문 능력 뒤에 가려진 이러한 문법적 오작동은 정적 웹사이트 생태계에서 서비스 중단이라는 부작용을 낳기도 합니다.
이번에 구축한 정규식 기반의 맞춤형 마크다운 린터는 기계(LLM)가 낸 실수를 또 다른 기계(Linter)가 엄격하게 감시하는 가장 현대적이고 확실한 방어책입니다. 자동 검수 파이프라인을 블로그 시스템에 단 한 번 구축해 두는 것만으로도, 여러분은 사소한 문법 오류 검증 스트레스에서 벗어나 오직 양질의 콘텐츠 기획과 가치 전달에만 온전히 집중할 수 있게 될 것입니다.