Web Page to PDF 변환기 만드는 방법, 정신이 안 무너지게
Source: Dev.to
웹 페이지를 PDF 로 변환하는 컨버터 만들기 (머리 아프지 않게)
웹 페이지를 PDF 로 변환하는 작업은 생각보다 복잡할 수 있습니다.
하지만 올바른 도구와 접근 방식을 사용한다면, 코드를 깔끔하게 유지하면서도 원하는 결과를 얻을 수 있습니다. 이번 글에서는 Node.js와 Puppeteer를 활용해 간단하면서도 확장 가능한 PDF 변환기를 만드는 과정을 단계별로 살펴보겠습니다.
📚 목차
왜 PDF 변환이 필요한가?
- 오프라인 보기: 사용자는 인터넷 연결이 없어도 문서를 읽을 수 있습니다.
- 프린트 친화성: PDF는 인쇄 시 레이아웃이 깨지지 않는 장점이 있습니다.
- 공유 및 보관: 파일 하나로 여러 사람에게 동일한 형태로 전달할 수 있습니다.
프로젝트 초기 설정
# 프로젝트 폴더 생성 및 초기화
mkdir web-to-pdf && cd web-to-pdf
npm init -y
# 필요한 패키지 설치
npm install express puppeteer dotenv
Tip:
dotenv를 사용하면 환경 변수(예: 포트 번호, 기본 저장 경로 등)를 별도 파일에 보관할 수 있어 보안과 관리가 편리합니다.
Puppeteer 로 페이지 렌더링하기
// server.js
require('dotenv').config();
const express = require('express');
const puppeteer = require('puppeteer');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
// JSON 바디 파싱
app.use(express.json());
// POST /convert 엔드포인트
app.post('/convert', async (req, res) => {
const { url, options } = req.body;
if (!url) {
return res.status(400).json({ error: 'URL is required' });
}
let browser;
try {
browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const page = await browser.newPage();
// 페이지 로드 타임아웃 설정
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
// 기본 옵션 + 사용자가 전달한 옵션 병합
const pdfOptions = {
format: 'A4',
printBackground: true,
...options,
};
const pdfBuffer = await page.pdf(pdfOptions);
// 파일 스트림으로 응답
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${Date.now()}.pdf"`,
'Content-Length': pdfBuffer.length,
});
res.send(pdfBuffer);
} catch (err) {
console.error('Conversion error:', err);
res.status(500).json({ error: 'Failed to convert page to PDF' });
} finally {
if (browser) await browser.close();
}
});
app.listen(PORT, () => {
console.log(`🚀 Server listening on http://localhost:${PORT}`);
});
주요 포인트
| 항목 | 설명 |
|---|---|
waitUntil: 'networkidle2' | 페이지 내 모든 네트워크 요청이 500 ms 동안 발생하지 않을 때까지 대기합니다. |
printBackground: true | 배경 이미지·색상까지 PDF에 포함됩니다. |
--no-sandbox | Docker 등 제한된 환경에서 실행할 때 필요합니다. |
옵션 커스터마이징
클라이언트에서 아래와 같은 옵션을 전달하면 PDF 레이아웃을 세밀하게 제어할 수 있습니다.
{
"url": "https://example.com",
"options": {
"format": "Letter",
"margin": {
"top": "20mm",
"right": "15mm",
"bottom": "20mm",
"left": "15mm"
},
"landscape": false
}
}
format:A4,Letter,Legal등 표준 페이지 크기.margin: 페이지 여백을 지정합니다. 단위는px,mm,in등 CSS와 동일합니다.landscape:true이면 가로 모드,false이면 세로 모드.
에러 핸들링 및 로깅
// utils/logger.js
const fs = require('fs');
const path = require('path');
const logFile = path.join(__dirname, '..', 'logs', 'errors.log');
function logError(err) {
const timestamp = new Date().toISOString();
const message = `[${timestamp}] ${err.stack || err}\n`;
fs.appendFileSync(logFile, message);
}
module.exports = { logError };
위 로거를 server.js에 통합하면, 프로덕션 환경에서 발생한 오류를 파일에 기록해 추후 디버깅이 쉬워집니다.
// server.js (일부 수정)
const { logError } = require('./utils/logger');
// ...
catch (err) {
logError(err);
res.status(500).json({ error: 'Failed to convert page to PDF' });
}
배포 전략
| 환경 | 권장 설정 |
|---|---|
| Docker | node:18-alpine 베이스 이미지에 puppeteer와 chromium을 함께 설치. --no-sandbox 옵션을 반드시 사용. |
| AWS Lambda | chrome-aws-lambda와 puppeteer-core 조합 사용. 메모리 1024 MB 이상 권장. |
| Heroku | heroku-buildpack-apt로 chromium 설치 후 PUPPETEER_EXECUTABLE_PATH 환경 변수 지정. |
Note: 클라우드 환경에서는 메모리 사용량과 타임아웃을 신경 써야 합니다. PDF 생성은 CPU와 I/O를 많이 소모하므로, 적절한 리소스 할당이 필수입니다.
마무리 및 다음 단계
- 테스트 자동화:
jest와supertest를 이용해/convert엔드포인트에 대한 통합 테스트를 작성하세요. - 보안 강화: 외부 URL을 직접 받아서 렌더링할 경우, SSRF(서버 측 요청 위조) 공격에 노출될 수 있습니다. 허용된 도메인 화이트리스트를 적용하거나, URL 검증 로직을 추가하세요.
- 프론트엔드 UI: 간단한 HTML 폼이나 React 컴포넌트를 만들어 사용자가 직접 URL과 옵션을 입력하고 PDF를 다운로드받을 수 있게 하면 서비스 완성도가 높아집니다.
이제 여러분은 Node.js + Puppeteer 기반의 웹‑페이지‑to‑PDF 변환기를 직접 구현하고, 다양한 배포 환경에 맞게 최적화할 수 있는 기반을 갖추었습니다.
필요에 따라 기능을 확장하고, 모니터링 및 로깅을 강화해 안정적인 서비스로 성장시켜 보세요! 🚀
기사(문서)를 PDF로 저장 – 텍스트만 깨끗하게
아무런 불필요한 요소 없이 선택 가능한 깨끗한 텍스트만 포함된 PDF로 기사를 저장하고 싶으신가요?
또는 페이지의 특정 부분만 필요하고, 페이지 구분 없이 한 페이지에 모두 표시되길 원하시나요?
저도 같은 고민을 했기에 직접 해결책을 만들고 이를 브라우저 확장 프로그램으로 구현했습니다.
표준 CTRL + P가 왜 안 될까?
PDF로 저장할 때 화면에 보이는 그대로 정확히 재현되길 원합니다:
- 클릭 가능한 링크
- 전체 문서가 하나의 긴 페이지에 표시되도록 (페이지 구분 없음)
CTRL+P는 이런 요구를 충족하지 못합니다. 종이에 인쇄하기 위해 설계된 기능이라 다른 요구 사항을 처리할 방법을 알지 못합니다.
Available Modes in the Extension
1. Full page

- 페이지에 보이는 그대로 전체를 저장합니다.
- 텍스트는 선택 가능하고 링크는 클릭할 수 있습니다.
- OCR 텍스트 이미지가 아니라 실제 텍스트와 실제 링크가 포함된 진짜 PDF입니다.
2. Export a page element

- 페이지에서 특정 요소를 선택하고 그 요소만 내보냅니다.
- 기사, 코드 블록 또는 기타 요소만 저장하고 싶을 때 유용합니다.
- 저는 Google Forms에 입력한 데이터를 복사해 두기 위해 이 모드를 자주 사용합니다.
3. Article
가장 흥미로운 모드 중 하나입니다. 블로그 기사를 읽기 친화적인 뷰로 저장해 줍니다—페이지에 다른 것이 전혀 없고 기사 자체만 남습니다.
이 모드에서 저는 다음을 보장했습니다:
- 코드 블록이 줄을 올바르게 감쌉니다.
- “ 태그가 확장됩니다.
따라서 전체 기사 내용이 PDF에 포함됩니다.
4. Remove elements

- 페이지의 아무 요소든 클릭해서 제거합니다 (예: 사이드바, 메뉴, 광고).
- 중요한 것을 실수로 제거했을 경우 CTRL + Z를 눌러 되돌릴 수 있습니다.
5. Export chats from ChatGPT, DeepSeek, and Gemini
AI 기업들이 채팅에 대한 PDF 내보내기를 제공하지 않기 때문에, 저는 이 기능을 확장 프로그램에 추가했습니다. 한 번 클릭하면 현재 열려 있는 대화를 저장할 수 있습니다.
Layout options (available in every mode)
- Single‑page PDF – 연속 스크롤, 페이지 구분 없음.
- Multi‑page PDF – 인쇄에 적합.
또한 화면에 맞추거나 A4, A5 등 표준 형식에 맞게 페이지 크기를 조정할 수 있습니다.
정신을 잃지 않는 방법
레이아웃 변형이 너무 많음
웹사이트를 PDF로 저장하는 것은 어렵습니다. 모든 레이아웃 변형을 고려하는 것은 불가능하며, 언제나 뭔가가 깨집니다.
사이트별로 문제를 고치려고 했지만, 금방 바람개비와 싸우는 느낌이었습니다. 이제는 Notion 같은 대형 플랫폼에만 특별히 수정합니다.
Atomic CSS
Tailwind와 같은 Atomic CSS 프레임워크가 대규모로 채택되면서 신뢰할 수 있는 요소 선택이 어려워졌습니다.
- ChatGPT 대화를 내보내려면 많은 작업과 까다로운 셀렉터가 필요했으며, 대화 요소를 잡아내는 것이 힘들었습니다.
- Claude Code는 레이아웃 때문에 포기했습니다.
- Gemini가 가장 쉬웠습니다—클래스 이름이 실제로 사람이 읽을 수 있었습니다.
Lazy‑loading
Lazy‑loading된 이미지는 완전히 별개의 골칫거리입니다. 이미지가 많은 긴 페이지에서는 PDF 생성 전에 모든 이미지를 로드하도록 다음과 같은 간단한 방법을 사용합니다:
for (const img of document.querySelectorAll('img')) {
img.scrollIntoView(); // 로딩 트리거
await new Promise(r => setTimeout(r, 100)); // 약 100 ms 대기
}
더 깔끔한 해결책이 있다면 댓글로 알려 주세요.
페이지 스타일 복원
Export page element 모드는 선택한 요소를 제외한 모든 것을 숨긴 뒤, 페이지를 다시 로드하지 않고 모든 스타일을 복원합니다.
페이지를 다시 로드하는 것은 옵션이 아닙니다—긴 양식 작성을 마치고 “내보내기”를 눌렀는데 페이지가 새로 고쳐진다면 정말 화가 날 겁니다. 저도 그런 경험이 있습니다.
문서 부족
최종 PDF를 렌더링하는 일은 별개의 이야기입니다. 이 작업을 위한 좋은 라이브러리가 없습니다.
- Mozilla의 PDF.js에는 PDFViewer가 포함되어 있어 좋을 것 같지만, 사실상 문서가 거의 없습니다.
- 소스 코드와 GitHub 이슈를 읽어가며 모든 것을 스스로 파악해야 합니다.
- 문서를 요청하는 이슈가 많이 있지만, Mozilla는 제공하고 싶어 하지 않는 듯합니다. 그래도 라이브러리를 제공해 준 점은 고맙습니다.
내부 구조
제가 주로 사용하는 도구는 다음과 같습니다:
- WXT.dev – 브라우저 확장 프로그램을 만들기 위한 프레임워크입니다. 제 의견으로는 현재 최고의 옵션이며, 빠른 HMR, 테스트용 별도 브라우저, 뛰어난 개발 속도를 제공합니다.
- PDFViewer (PDF.js) – PDF를 렌더링하기 위한 도구입니다.
(원본 텍스트에서 목록의 나머지는 잘렸습니다.)
PDF 생성 옵션
웹 페이지에서 PDF를 생성하는 것은 까다로울 수 있습니다. 아래는 가장 신뢰할 수 있는 방법 몇 가지입니다:
- Chrome Debugger – Chrome DevTools Protocol (Page)
이 방법은 Chrome Debugger를 사용해 페이지를 PDF로 변환합니다. 처음에는 이상하게 들릴 수 있지만, 가장 높은 품질의 PDF를 제공합니다.
다음은?
저는 인기 웹사이트와의 통합을 지속적으로 추가하고 있습니다.
예를 들어, 최근에 Reddit 게시물 원클릭 저장 기능을 도입했습니다.
-
확장 프로그램:
Web to PDF – Chrome Web Store -
웹 서비스:
WebToPDF.space – Demo
웹 서비스는 확장 프로그램만큼 많은 기능을 제공하지는 않지만, 모바일 기기에서도 작동합니다.