@@ -22,6 +22,7 @@ export async function GET(req: Request) {
2222 const { searchParams } = new URL ( req . url ) ;
2323 const url = searchParams . get ( 'url' ) ;
2424 let page = null ;
25+ const MAX_RETRIES = 3 ;
2526
2627 if ( ! url ) {
2728 return NextResponse . json (
@@ -30,93 +31,130 @@ export async function GET(req: Request) {
3031 ) ;
3132 }
3233
33- try {
34- // Get browser instance
35- const browser = await getBrowser ( ) ;
36-
37- // Create a new page
38- page = await browser . newPage ( ) ;
39-
40- // Set viewport to a reasonable size
41- await page . setViewport ( {
42- width : 1280 ,
43- height : 720 ,
44- } ) ;
45-
46- // Navigate to URL with increased timeout and more reliable wait condition
47- await page . goto ( url , {
48- waitUntil : 'domcontentloaded' , // Less strict than networkidle0
49- timeout : 60000 , // Increased timeout to 60 seconds
50- } ) ;
51-
52- // 等待额外的时间让页面完全渲染
53- await page . waitForTimeout ( 3000 ) ;
54-
55- // 尝试等待页面上的内容加载,如果失败也继续处理
34+ for ( let attempt = 0 ; attempt < MAX_RETRIES ; attempt ++ ) {
5635 try {
57- // 等待页面上可能存在的主要内容元素
58- await Promise . race ( [
59- page . waitForSelector ( 'main' , { timeout : 2000 } ) ,
60- page . waitForSelector ( '#root' , { timeout : 2000 } ) ,
61- page . waitForSelector ( '.app' , { timeout : 2000 } ) ,
62- page . waitForSelector ( 'h1' , { timeout : 2000 } ) ,
63- ] ) ;
64- } catch ( waitError ) {
65- // 忽略等待选择器的错误,继续截图
66- logger . info ( 'Unable to find common page elements, continuing with screenshot' ) ;
67- }
36+ logger . info ( `Screenshot attempt ${ attempt + 1 } for ${ url } ` ) ;
37+
38+ // Get browser instance
39+ const browser = await getBrowser ( ) ;
40+
41+ // Create a new page
42+ page = await browser . newPage ( ) ;
43+
44+ // Set viewport to a reasonable size
45+ await page . setViewport ( {
46+ width : 1280 ,
47+ height : 720 ,
48+ } ) ;
49+
50+ // Navigate to URL with increased timeout and more reliable wait condition
51+ await page . goto ( url , {
52+ waitUntil : 'networkidle2' , // 更改为等待网络空闲状态,确保页面完全加载
53+ timeout : 90000 , // 增加超时时间到90秒
54+ } ) ;
55+
56+ // 等待额外的时间让页面完全渲染
57+ await page . waitForTimeout ( 8000 ) ; // 增加等待时间到8秒
58+
59+ // 尝试等待页面上的内容加载,如果失败也继续处理
60+ try {
61+ // 等待页面上可能存在的主要内容元素
62+ await Promise . race ( [
63+ page . waitForSelector ( 'main' , { timeout : 5000 } ) ,
64+ page . waitForSelector ( '#root' , { timeout : 5000 } ) ,
65+ page . waitForSelector ( '.app' , { timeout : 5000 } ) ,
66+ page . waitForSelector ( 'h1' , { timeout : 5000 } ) ,
67+ page . waitForSelector ( 'div' , { timeout : 5000 } ) , // 添加更通用的选择器
68+ ] ) ;
69+ } catch ( waitError ) {
70+ // 忽略等待选择器的错误,继续截图
71+ logger . info ( 'Unable to find common page elements, continuing with screenshot' ) ;
72+ }
6873
69- // Take screenshot
70- const screenshot = await page . screenshot ( {
71- type : 'png' ,
72- fullPage : false ,
73- } ) ;
74+ // 检查页面是否有内容
75+ const contentCheck = await page . evaluate ( ( ) => {
76+ return {
77+ bodyContent : document . body . innerText . length ,
78+ elements : document . querySelectorAll ( '*' ) . length
79+ } ;
80+ } ) ;
81+
82+ logger . info ( `Page content: ${ contentCheck . bodyContent } chars, ${ contentCheck . elements } elements` ) ;
83+
84+ if ( contentCheck . elements < 5 ) {
85+ logger . warn ( 'Page seems empty, possibly not fully loaded' ) ;
86+ if ( attempt < MAX_RETRIES - 1 ) {
87+ await page . close ( ) ;
88+ continue ; // 重试
89+ }
90+ }
7491
75- // Always close the page when done
76- if ( page ) {
77- await page . close ( ) ;
78- }
92+ // Take screenshot with full page height
93+ const screenshot = await page . screenshot ( {
94+ type : 'png' ,
95+ fullPage : false ,
96+ } ) ;
7997
80- // Return the screenshot as a PNG image
81- return new Response ( screenshot , {
82- headers : {
83- 'Content-Type' : 'image/png' ,
84- 'Cache-Control' : 's-maxage=3600' ,
85- } ,
86- } ) ;
87- } catch ( error : any ) {
88- logger . error ( 'Screenshot error:' , error ) ;
89-
90- // Ensure page is closed even if an error occurs
91- if ( page ) {
92- try {
98+ // Always close the page when done
99+ if ( page ) {
93100 await page . close ( ) ;
94- } catch ( closeError ) {
95- logger . error ( 'Error closing page:' , closeError ) ;
96101 }
97- }
98102
99- // If browser seems to be in a bad state, recreate it
100- if (
101- error . message . includes ( 'Target closed' ) ||
102- error . message . includes ( 'Protocol error' ) ||
103- error . message . includes ( 'Target.createTarget' )
104- ) {
105- try {
106- if ( browserInstance ) {
107- await browserInstance . close ( ) ;
108- browserInstance = null ;
103+ // Return the screenshot as a PNG image
104+ return new Response ( screenshot , {
105+ headers : {
106+ 'Content-Type' : 'image/png' ,
107+ 'Cache-Control' : 's-maxage=3600' ,
108+ } ,
109+ } ) ;
110+ } catch ( error : any ) {
111+ logger . error ( `Screenshot error on attempt ${ attempt + 1 } :` , error ) ;
112+
113+ // Ensure page is closed even if an error occurs
114+ if ( page ) {
115+ try {
116+ await page . close ( ) ;
117+ } catch ( closeError ) {
118+ logger . error ( 'Error closing page:' , closeError ) ;
109119 }
110- } catch ( closeBrowserError ) {
111- logger . error ( 'Error closing browser:' , closeBrowserError ) ;
112120 }
113- }
114121
115- return NextResponse . json (
116- { error : error . message || 'Failed to capture screenshot' } ,
117- { status : 500 }
118- ) ;
122+ // If browser seems to be in a bad state, recreate it
123+ if (
124+ error . message . includes ( 'Target closed' ) ||
125+ error . message . includes ( 'Protocol error' ) ||
126+ error . message . includes ( 'Target.createTarget' )
127+ ) {
128+ try {
129+ if ( browserInstance ) {
130+ await browserInstance . close ( ) ;
131+ browserInstance = null ;
132+ }
133+ } catch ( closeBrowserError ) {
134+ logger . error ( 'Error closing browser:' , closeBrowserError ) ;
135+ }
136+ }
137+
138+ // 如果这不是最后一次尝试,则继续
139+ if ( attempt < MAX_RETRIES - 1 ) {
140+ // 等待一会儿再重试
141+ await new Promise ( resolve => setTimeout ( resolve , 3000 ) ) ;
142+ continue ;
143+ }
144+
145+ // 最后一次尝试失败
146+ return NextResponse . json (
147+ { error : error . message || 'Failed to capture screenshot after multiple attempts' } ,
148+ { status : 500 }
149+ ) ;
150+ }
119151 }
152+
153+ // 如果重试都失败
154+ return NextResponse . json (
155+ { error : 'Failed to capture screenshot after exhausting all retries' } ,
156+ { status : 500 }
157+ ) ;
120158}
121159
122160// Handle process termination to close browser
0 commit comments