import { NextRequest, NextResponse } from 'next/server';
import puppeteer from 'puppeteer';
/**
* PDF 생성 API
* POST /api/pdf/generate
*
* Body: {
* html: string,
* styles: string,
* title?: string,
* orientation?: 'portrait' | 'landscape',
* documentNumber?: string,
* createdDate?: string,
* showHeaderFooter?: boolean
* }
* Response: PDF blob
*/
export async function POST(request: NextRequest) {
try {
const {
html,
styles = '',
title = '문서',
orientation = 'portrait',
documentNumber = '',
createdDate = '',
showHeaderFooter = true,
} = await request.json();
if (!html) {
return NextResponse.json(
{ error: 'HTML content is required' },
{ status: 400 }
);
}
// Puppeteer 브라우저 실행 (Docker Alpine에서는 시스템 Chromium 사용)
const browser = await puppeteer.launch({
headless: true,
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--disable-software-rasterizer',
],
});
const page = await browser.newPage();
// 전체 HTML 문서 구성 (인라인 스타일 포함)
const fullHtml = `
${title}
${html}
`;
// 뷰포트 설정 (문서 전체가 보이도록 넓게)
await page.setViewport({
width: 1200,
height: 1600,
deviceScaleFactor: 2,
});
// 외부 리소스 요청 차단 (이미지는 이미 base64 인라인)
await page.setRequestInterception(true);
page.on('request', (req) => {
const resourceType = req.resourceType();
// 이미지/폰트/스타일시트 등 외부 리소스 차단 → 타임아웃 방지
if (['image', 'font', 'stylesheet', 'media'].includes(resourceType)) {
req.abort();
} else {
req.continue();
}
});
// HTML 설정 (domcontentloaded: 외부 리소스 대기 안 함)
await page.setContent(fullHtml, {
waitUntil: 'domcontentloaded',
});
// 헤더 템플릿 (문서번호, 생성일)
const headerTemplate = showHeaderFooter
? `
${documentNumber ? `문서번호: ${documentNumber}` : ''}
${createdDate ? `생성일: ${createdDate}` : ''}
`
: '';
// 푸터 템플릿 (라인 + 페이지번호)
const footerTemplate = showHeaderFooter
? `
`
: '';
// PDF 생성 (자동 스케일로 A4에 맞춤)
const pdfBuffer = await page.pdf({
format: 'A4',
landscape: orientation === 'landscape',
printBackground: true,
preferCSSPageSize: false,
scale: 0.75, // 문서를 75%로 축소하여 A4에 맞춤
displayHeaderFooter: showHeaderFooter,
headerTemplate: headerTemplate,
footerTemplate: footerTemplate,
margin: {
top: showHeaderFooter ? '20mm' : '10mm',
right: '10mm',
bottom: showHeaderFooter ? '20mm' : '10mm',
left: '10mm',
},
});
// 브라우저 종료
await browser.close();
// PDF 응답
return new NextResponse(Buffer.from(pdfBuffer), {
status: 200,
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${encodeURIComponent(title)}.pdf"`,
},
});
} catch (error) {
console.error('PDF 생성 오류:', error);
return NextResponse.json(
{ error: 'PDF 생성 중 오류가 발생했습니다.' },
{ status: 500 }
);
}
}