Use Cases (업데이트: 2026. 6. 2.)

Claude Code로 PDF 생성 구현하기: Playwright 송장·리포트·인쇄 CSS

Claude Code로 PDF 생성 구현. Playwright, 인쇄 CSS, 한글 폰트, 송장, 리포트, 스크린샷 검증까지 설명합니다.

Claude Code로 PDF 생성 구현하기: Playwright 송장·리포트·인쇄 CSS

웹앱에 “PDF 다운로드” 버튼을 붙이는 일은 쉬워 보입니다. 하지만 실제 업무 문서에서는 송장 금액, 세금, 페이지 여백, 표 정렬, 한글 폰트, 배경색, 여러 페이지 분리까지 모두 맞아야 합니다. 월간 리포트는 표와 차트, 설명문이 함께 들어가고, 수료증은 이름과 발급일, 인증 ID가 인쇄했을 때도 신뢰감 있게 보여야 합니다.

이 글은 초보자에게 가장 실용적인 방식인 HTML-to-PDF에 집중합니다. 문서는 HTML로 만들고, 종이 크기와 여백은 print CSS로 제어하고, Playwright나 Puppeteer의 Chromium으로 렌더링한 뒤 PDF로 저장합니다. Claude Code에 단순히 “PDF 만들어줘”라고 요청하면 좌표를 직접 찍는 jsPDF 예제나 전체 문서를 canvas 이미지로 만드는 코드가 나올 수 있습니다. 그런 방식은 빠르게 보이지만 검색 가능한 텍스트, 파일 크기, 접근성, 회귀 테스트에서 손해가 큽니다.

공식 문서는 반드시 원문을 확인하세요. Playwright의 page.pdf, Puppeteer의 PDF generationPDFOptions, MDN의 @pageprinting CSS, 그리고 Claude Code overview가 기준입니다. 데이터 생성은 스프레드시트 자동화, 브라우저 검증은 Playwright 테스트, 전체 품질 기준은 테스트 전략과 함께 보면 좋습니다.

HTML에서 PDF를 만드는 이유

PDF 생성에는 세 가지 길이 있습니다. 첫째, jsPDF처럼 PDF 좌표에 직접 텍스트와 선을 그리는 방식입니다. 단순 라벨에는 좋지만 복잡한 송장에서는 x, y 좌표 조정이 계속 늘어납니다. 둘째, 화면이나 canvas를 이미지로 만들어 PDF에 넣는 방식입니다. 처음 보기에는 같아 보여도 텍스트 검색, 복사, 확대, 접근성, 파일 용량에서 불리합니다. 셋째, HTML과 CSS를 브라우저의 인쇄 엔진으로 렌더링하는 방식입니다.

대부분의 웹팀에는 세 번째 방식이 가장 유지보수하기 쉽습니다. 표는 table로 남고, 제목은 제목 요소로 남으며, @page로 A4 여백을 제어할 수 있습니다. 또한 PDF로 내보내기 전에 브라우저에서 인쇄용 HTML을 열어 스크린샷 비교를 할 수 있습니다. Claude Code도 일반 HTML, CSS, Node 스크립트, Playwright 테스트를 다루기 때문에 결과를 리뷰하기 쉽습니다.

flowchart TD
  A["업무 데이터"] --> B["HTML 템플릿"]
  B --> C["print CSS와 @page"]
  C --> D["Chromium 렌더링"]
  D --> E["PDF 파일"]
  C --> F["스크린샷 비교"]
  F --> G["리뷰 증거"]

실제 유스케이스

첫 번째는 송장입니다. 판매자, 구매자, 품목, 수량, 단가, 세금, 합계, 결제 기한이 필요합니다. 위험한 버그는 색상이 아니라 금액 반올림 오류, 세금 누락, 합계 영역 잘림, 한글 폰트 대체로 인한 줄바꿈 변화입니다.

두 번째는 월간 리포트입니다. 광고 성과, SEO, SaaS 사용량, 매출 분석 등은 표, 차트, 스크린샷, 해석 문장이 함께 들어갑니다. 제목만 페이지 맨 아래에 남거나 차트와 설명이 떨어지면 읽기 어렵습니다. 작은 블록에는 break-inside: avoid가 도움이 되지만, 긴 표는 처음부터 여러 페이지를 전제로 설계해야 합니다.

세 번째는 수료증과 인증서입니다. 이름, 과정명, 발급일, 인증 ID, 로고, QR 코드가 한 페이지에 들어갑니다. 전체를 이미지로 만들면 예쁘게 보일 수 있지만, 검색과 보관에는 불리합니다. 핵심 정보는 HTML 텍스트로 두고, 서명이나 장식만 이미지로 사용하는 편이 안정적입니다.

네 번째는 내부 감사 리포트입니다. 배포 이력, 승인 로그, 권한 변경, 장애 요약 같은 정보는 나중에 추적 가능해야 합니다. 생성 시간, 환경, 앱 버전, 데이터 원본 ID를 푸터에 남기면 조사할 때 도움이 됩니다.

Claude Code에 줄 프롬프트

PDF 생성 기능을 구현해 주세요.

방향:
- HTML 템플릿을 Playwright Chromium으로 렌더링하고 PDF로 저장합니다.
- 문서 전체를 canvas 이미지 하나로 만들지 않습니다.
- A4 세로, 14mm 여백, print CSS, 배경색 출력을 사용합니다.
- Noto Sans KR / Malgun Gothic / sans-serif 폰트 스택을 지정합니다.
- 송장, 리포트, 수료증에 재사용 가능한 구조로 만듭니다.

구현:
- scripts/create-invoice-pdf.mjs를 추가합니다.
- 샘플 데이터로 out/invoice-KR-2026-0602.pdf를 생성합니다.
- 금액은 Intl.NumberFormat으로 포맷합니다.
- 사용자 입력은 HTML 이스케이프합니다.
- page.pdf에서 printBackground와 preferCSSPageSize를 사용합니다.

검증:
- 실행 명령을 남깁니다.
- 인쇄용 HTML을 스크린샷 비교할 수 있게 만듭니다.
- 폰트, 페이지 나눔, 배경색, 합계를 확인합니다.

복사해서 실행할 수 있는 코드

npm init -y
npm pkg set type=module
npm i -D playwright
npx playwright install chromium
mkdir scripts out
node scripts/create-invoice-pdf.mjs
import { chromium } from "playwright";
import { mkdir } from "node:fs/promises";
import { dirname, resolve } from "node:path";

const outputPath = resolve("out/invoice-KR-2026-0602.pdf");
const money = new Intl.NumberFormat("ko-KR", { style: "currency", currency: "KRW" });

const invoice = {
  number: "KR-2026-0602",
  buyer: "샘플 주식회사",
  seller: "Masa Design Lab",
  issuedAt: "2026-06-02",
  items: [
    { name: "PDF 템플릿 설계", quantity: 1, unitPrice: 800000 },
    { name: "Playwright 생성 스크립트", quantity: 1, unitPrice: 1200000 },
    { name: "인쇄 CSS와 한글 폰트 검증", quantity: 1, unitPrice: 600000 },
  ],
};

function escapeHtml(value) {
  return String(value).replace(/[&<>"']/g, (char) => ({
    "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;",
  })[char]);
}

function renderHtml(data) {
  const subtotal = data.items.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0);
  const tax = Math.round(subtotal * 0.1);
  const rows = data.items.map((item) => `<tr>
    <td>${escapeHtml(item.name)}</td>
    <td class="num">${item.quantity}</td>
    <td class="num">${money.format(item.unitPrice)}</td>
    <td class="num">${money.format(item.quantity * item.unitPrice)}</td>
  </tr>`).join("");

  return `<!doctype html><html lang="ko"><head><meta charset="utf-8">
  <style>
    @page { size: A4; margin: 14mm; }
    body { font-family: "Noto Sans KR", "Malgun Gothic", sans-serif; color: #202124; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
    header { display: flex; justify-content: space-between; border-bottom: 3px solid #1f5eff; padding-bottom: 14px; }
    h1 { margin: 0; font-size: 28px; }
    table { width: 100%; border-collapse: collapse; margin-top: 24px; }
    th { background: #eef3ff; text-align: left; }
    th, td { border-bottom: 1px solid #d7dce5; padding: 10px 8px; }
    .num { text-align: right; white-space: nowrap; }
    .total { margin-left: auto; width: 260px; margin-top: 20px; font-size: 18px; font-weight: 700; }
    .avoid-break { break-inside: avoid; page-break-inside: avoid; }
  </style></head><body>
  <header><h1>송장</h1><div>번호: ${escapeHtml(data.number)}<br>발행일: ${escapeHtml(data.issuedAt)}</div></header>
  <p><strong>청구 대상:</strong> ${escapeHtml(data.buyer)}</p>
  <p><strong>발행자:</strong> ${escapeHtml(data.seller)}</p>
  <table><thead><tr><th>항목</th><th class="num">수량</th><th class="num">단가</th><th class="num">금액</th></tr></thead><tbody>${rows}</tbody></table>
  <div class="total avoid-break">합계: ${money.format(subtotal + tax)}</div>
  </body></html>`;
}

await mkdir(dirname(outputPath), { recursive: true });
const browser = await chromium.launch();
try {
  const page = await browser.newPage();
  await page.setContent(renderHtml(invoice), { waitUntil: "networkidle" });
  await page.evaluate(() => document.fonts.ready);
  await page.emulateMedia({ media: "print" });
  await page.pdf({ path: outputPath, printBackground: true, preferCSSPageSize: true, margin: { top: "0", right: "0", bottom: "0", left: "0" } });
  console.log(`Created ${outputPath}`);
} finally {
  await browser.close();
}

실패 사례와 검증

실패는 보통 네 곳에서 납니다. printBackground를 빼서 표 배경이 사라지는 경우, 서버에 한글 폰트가 없어 줄바꿈이 바뀌는 경우, 외부 로고가 로드되기 전에 PDF를 만드는 경우, 고객명이 HTML로 이스케이프되지 않는 경우입니다. 긴 비고나 긴 품목명이 합계 영역을 다음 페이지로 밀어내는 문제도 자주 발생합니다.

검증은 PDF 파일 생성 여부만으로 끝내면 부족합니다. 인쇄용 URL을 열고 page.emulateMedia({ media: "print" })를 적용한 뒤 Playwright 스크린샷 비교를 남기세요. 별도로 금액 계산 단위 테스트, 긴 품목 테스트, 누락된 이미지 테스트, 2페이지 출력 테스트를 두면 리뷰가 훨씬 쉬워집니다.

수익화 CTA와 직접 확인한 결과

PDF 생성은 송장 템플릿, 리포트 샘플, 수료증 도구, 체크리스트 배포처럼 수익 흐름과 연결하기 좋습니다. 얇은 라이브러리 소개보다 실제 코드, 실패 예, 검증 절차가 있어야 검색 유입도 오래갑니다. 처음에는 무료 Claude Code 자료를 보고, 반복 가능한 템플릿은 ClaudeCodeLab 제품, 팀 도입과 검증 흐름은 교육 및 상담으로 이어가면 자연스럽습니다.

이 글의 흐름은 로컬 Node.js 환경에서 Playwright Chromium으로 송장 PDF를 생성하는 방식으로 확인했습니다. Masa의 실제 작업에서 가장 도움이 된 습관은 PDF만 열어 보는 것이 아니라, PDF 직전의 인쇄 HTML을 테스트 대상으로 둔 것입니다. 폰트 로딩, 배경색 누락, 긴 텍스트 페이지 나눔은 Claude Code가 코드를 작성한 뒤에도 사람이 반드시 확인해야 하는 지점입니다.

#Claude Code #PDF generation #jsPDF #Puppeteer #reports
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.