html을 기반으로 pdf다운로드 기능을 구현해보기
이름: puppteer
이유: 타 라이브러리보다 css에 대한 안정성이 좋았고 next.js내부 api로 호출하는 방식에 대해 호환성이 좋았다
puppeteer
: HTML을 PDF로 변환하는 데 사용.NextRequest
, NextResponse
: Next.js 14 App Router의 API 핸들러를 정의하기 위한 객체.fs
, path
: 파일 시스템과 경로를 조작하기 위한 Node.js 기본 모듈.loggerSetting
: 커스텀 로깅 설정 모듈.logo.svg
와 signature_example.svg
파일을 Base64로 인코딩하여 data:image/svg+xml;base64
형식으로 변환.POST
요청을 처리하기 위해 작성됨.reservationDate
를 포함한 데이터를 JSON 형식으로 받음.reservationDate
를 기준으로 1일 후 날짜를 계산함.headless
모드로 브라우저를 실행.-no-sandbox
, -disable-setuid-sandbox
플래그는 보안 설정과 관련.await page.setContent(htmlContent)
를 사용해 페이지에 설정.page.pdf
메서드를 호출해 PDF를 생성.NextResponse
로 반환:Content-Type: application/pdf
로 PDF임을 명시.Content-Disposition: attachment
로 파일 다운로드를 유도.import puppeteer from "puppeteer";
import { NextRequest, NextResponse } from "next/server";
import fs from "fs";
import path from "path";
import loggerSetting from "@/util/logging";
const logoPath = path.join(process.cwd(), "public/layout/logo.svg");
const signPath = path.join(
process.cwd(),
"public/dashboard/consulting/estimateSheet/signature_example.svg"
);
const logoBase64 = fs.readFileSync(logoPath).toString("base64");
const signBase64 = fs.readFileSync(signPath).toString("base64");
const logoSrc = `data:image/svg+xml;base64,${logoBase64}`;
const signSrc = `data:image/svg+xml;base64,${signBase64}`;
export async function POST(req: NextRequest): Promise<NextResponse> {
const { logger } = loggerSetting();
let today = new Date();
today.setHours(today.getHours() + 9);
const logDate = today.toISOString();
try {
const body = await req.json();
const reservationDate = new Date(body.reservationDate);
const transMiliSecond = new Date(reservationDate).setDate(
new Date(reservationDate).getDate() + 1
);
const transDate = new Date(transMiliSecond);
const browser = await puppeteer.launch({
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();
const htmlContent = `
<html lang="ko">
// ...html내용
</html>
`;
// 폰트가 깨지지 않게 폰트 직접지정
await page.addStyleTag({
url: "https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap",
});
// PDF로 변환할 HTML 내용 설정
await page.setContent(htmlContent);
const pdfBuffer = await page.pdf({
format: "A4",
printBackground: true,
});
await browser.close();
// PDF 반환
return new NextResponse(pdfBuffer, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": "attachment; filename=document.pdf",
},
});
} catch (error) {
const logDate = new Date().toISOString();
const errMessage = error instanceof Error ? error.message : "Unknown error";
const errStack =
error instanceof Error ? error.stack : "No stack available";
const errString = `dateTime=${logDate} msg=${errMessage}, stack=${errStack}, url='/api/pdfDownload', method=POST`;
console.error("PDF Generation Error:", error);
logger.error({ errString });
return new NextResponse("Failed to generate PDF", { status: 500 });
}
}
const onClickEstimateSheet = async (data) => {
try {
const requestBody = {
...data
};
// 호출영역
const response = await fetch("/api/pdfDownload", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody), // JSON 형태로 전송
});
if (!response.ok) {
throw new Error("Failed to fetch PDF");
}
const blob = await response.blob();
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = "document.pdf";
link.click();
URL.revokeObjectURL(link.href); // 메모리 정리
} catch (error) {
console.error("PDF Download Error:", error);
}
};
해당 라이브러리는 브라우저 의존성에 따른 환경별 추가 라이브러리가 필요하다
위 코드는 localhost에서는 정상적인 동작을 하지만 실제 IP나 도메인에선 기능이 동작하지 않는다.
따라서 아래의 코드로 ssh에 접속하여 필요한 의존성 라이브러리를 설치를 해주어야한다.
ssh ubuntu@${TARGET_IP} "
sudo apt-get update &&
sudo apt-get install -y \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libgbm1 \
libnspr4 \
libnss3 \
libxcomposite1 \
libxdamage1 \
libxrandr2 \
libasound2 \
libx11-xcb1 \
libxtst6 \
libxshmfence1 \
libegl1 \
libjpeg-dev \
libxkbcommon0 \
gconf-service \
libappindicator1 \
libappindicator3-1 \
fonts-liberation \
lsb-release \
xdg-utils
"
젠킨스를 통한 설정도가능한데 이 방법은 젠킨스 빌드시 항시 동작하기에 배포 속도에 영향을 끼친다
stages {
stage('Git Clone Project') {
steps {
cleanWs()
git credentialsId: "${REPOSITORY_CREDENTIAL_ID}", branch: "${TARGET_BRANCH}", url: "${REPOSITORY_URL}"
}
}
stage('Install Puppeteer Dependencies') {
steps {
sshagent (credentials: ['key-jenkins']) {
sh '''
echo "🔄 Installing Puppeteer dependencies on ${TARGET_IP}"
ssh ubuntu@${TARGET_IP} "
sudo apt-get update &&
sudo apt-get install -y \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libgbm1 \
libnspr4 \
libnss3 \
libxcomposite1 \
libxdamage1 \
libxrandr2 \
libasound2 \
libx11-xcb1 \
libxtst6 \
libxshmfence1 \
libegl1 \
libjpeg-dev \
libxkbcommon0 \
gconf-service \
libappindicator1 \
libappindicator3-1 \
fonts-liberation \
lsb-release \
xdg-utils &&
echo '✅ Puppeteer dependencies installed successfully!'
"
'''
}
}
}