fix: 修复 Cloudflare Pages 部署时 puppeteer 导致 OOM 的问题

将 puppeteer 从 Nitro 服务端 bundle 中排除(rollupConfig.external),
改为动态 import,Cloudflare 环境调用 PDF 接口时返回 501 提示。
Docker 环境通过运行时单独安装 puppeteer 保持功能正常。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jock
2026-04-15 20:28:16 +08:00
parent 4ccf4e435a
commit e6ebbf39e4
4 changed files with 15 additions and 4 deletions

View File

@@ -50,6 +50,8 @@ WORKDIR /app
# 复制构建输出
COPY --from=build-env /app/.output ./
# puppeteer 被 Rollup external 排除,运行时需要从 node_modules 加载Chromium 已通过 apt 安装,跳过下载)
RUN npm install --no-save --ignore-scripts puppeteer@24
# 创建 KV 存储目录并设置权限(以 root 运行,确保 node 用户可写)
RUN mkdir -p .data/kv && chown -R node:node /app

View File

@@ -36,6 +36,9 @@ export default defineNuxtConfig({
},
nitro: {
minify: process.env.NODE_ENV === 'production',
rollupConfig: {
external: ['puppeteer'],
},
storage: {
kv: {
driver: process.env.NITRO_KV_DRIVER || 'memory',

View File

@@ -1,11 +1,16 @@
import { getBrowser } from '~/server/utils/puppeteer';
export default defineEventHandler(async (event) => {
const html = await readBody<string>(event);
if (!html || typeof html !== 'string') {
throw createError({ statusCode: 400, statusMessage: '请求体必须是 HTML 字符串' });
}
let getBrowser: Awaited<typeof import('~/server/utils/puppeteer')>['getBrowser'];
try {
getBrowser = (await import('~/server/utils/puppeteer')).getBrowser;
} catch {
throw createError({ statusCode: 501, statusMessage: '当前部署环境不支持 PDF 导出,请使用 Docker 部署' });
}
const browser = await getBrowser();
const page = await browser.newPage();
@@ -14,7 +19,6 @@ export default defineEventHandler(async (event) => {
await page.setContent(html, { waitUntil: 'load', timeout: 60_000 });
const contentHeight = await page.evaluate(
// @ts-expect-error runs in browser context
() => Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight),
);

View File

@@ -1,4 +1,4 @@
import puppeteer, { type Browser } from 'puppeteer';
import type { Browser } from 'puppeteer';
let browser: Browser | null = null;
@@ -7,6 +7,8 @@ export async function getBrowser(): Promise<Browser> {
return browser;
}
const puppeteer = await import('puppeteer').then(m => m.default);
const launchArgs = [
'--no-sandbox',
'--disable-setuid-sandbox',