Spaces:
Runtime error
Runtime error
Commit ·
e7f7858
1
Parent(s): fb404c5
fix: proxy PDFs through /api/pdf-proxy to bypass CORS
Browse filesWorld Bank PDF server blocks cross-origin requests, preventing
Mozilla's hosted PDF.js from loading them. Now PDFs are proxied
through our own API endpoint, and the browser's built-in PDF
viewer renders them via iframe.
- app/api/pdf-proxy/route.js +49 -0
- app/components/PdfViewer.js +4 -4
app/api/pdf-proxy/route.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextResponse } from 'next/server';
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* GET /api/pdf-proxy?url=<encoded_pdf_url>
|
| 5 |
+
* Proxies a PDF from an external URL to bypass CORS restrictions.
|
| 6 |
+
* Returns the PDF with correct Content-Type so PDF.js can render it.
|
| 7 |
+
*/
|
| 8 |
+
export async function GET(request) {
|
| 9 |
+
const { searchParams } = new URL(request.url);
|
| 10 |
+
const pdfUrl = searchParams.get('url');
|
| 11 |
+
|
| 12 |
+
if (!pdfUrl) {
|
| 13 |
+
return NextResponse.json({ error: 'Missing url parameter' }, { status: 400 });
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
try {
|
| 17 |
+
const res = await fetch(pdfUrl, {
|
| 18 |
+
headers: {
|
| 19 |
+
'User-Agent': 'Mozilla/5.0 (compatible; AnnotationTool/1.0)',
|
| 20 |
+
},
|
| 21 |
+
signal: AbortSignal.timeout(30000), // 30s timeout
|
| 22 |
+
});
|
| 23 |
+
|
| 24 |
+
if (!res.ok) {
|
| 25 |
+
return NextResponse.json(
|
| 26 |
+
{ error: `PDF fetch failed: HTTP ${res.status}` },
|
| 27 |
+
{ status: res.status }
|
| 28 |
+
);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
const pdfBuffer = await res.arrayBuffer();
|
| 32 |
+
|
| 33 |
+
return new Response(pdfBuffer, {
|
| 34 |
+
status: 200,
|
| 35 |
+
headers: {
|
| 36 |
+
'Content-Type': 'application/pdf',
|
| 37 |
+
'Content-Length': pdfBuffer.byteLength.toString(),
|
| 38 |
+
'Cache-Control': 'public, max-age=86400', // cache 24h
|
| 39 |
+
'Access-Control-Allow-Origin': '*',
|
| 40 |
+
},
|
| 41 |
+
});
|
| 42 |
+
} catch (error) {
|
| 43 |
+
console.error('PDF proxy error:', error);
|
| 44 |
+
return NextResponse.json(
|
| 45 |
+
{ error: 'Failed to proxy PDF: ' + error.message },
|
| 46 |
+
{ status: 500 }
|
| 47 |
+
);
|
| 48 |
+
}
|
| 49 |
+
}
|
app/components/PdfViewer.js
CHANGED
|
@@ -23,9 +23,9 @@ export default function PdfViewer({ pdfUrl, pageNumber }) {
|
|
| 23 |
// PDF pages in our data are 0-indexed; PDF.js viewer expects 1-indexed pages
|
| 24 |
const viewerPage = (pageNumber ?? 0) + 1;
|
| 25 |
|
| 26 |
-
//
|
| 27 |
-
//
|
| 28 |
-
const
|
| 29 |
|
| 30 |
return (
|
| 31 |
<div className="pdf-container">
|
|
@@ -47,7 +47,7 @@ export default function PdfViewer({ pdfUrl, pageNumber }) {
|
|
| 47 |
)}
|
| 48 |
<iframe
|
| 49 |
key={`pdf-${pdfUrl}-page-${viewerPage}`}
|
| 50 |
-
src={
|
| 51 |
className="pdf-frame"
|
| 52 |
title={`PDF Page ${viewerPage}`}
|
| 53 |
allow="fullscreen"
|
|
|
|
| 23 |
// PDF pages in our data are 0-indexed; PDF.js viewer expects 1-indexed pages
|
| 24 |
const viewerPage = (pageNumber ?? 0) + 1;
|
| 25 |
|
| 26 |
+
// Proxy the PDF through our own API to bypass CORS restrictions.
|
| 27 |
+
// Then use the browser's built-in PDF viewer via <object> tag.
|
| 28 |
+
const proxyUrl = `/api/pdf-proxy?url=${encodeURIComponent(pdfUrl)}#page=${viewerPage}`;
|
| 29 |
|
| 30 |
return (
|
| 31 |
<div className="pdf-container">
|
|
|
|
| 47 |
)}
|
| 48 |
<iframe
|
| 49 |
key={`pdf-${pdfUrl}-page-${viewerPage}`}
|
| 50 |
+
src={proxyUrl}
|
| 51 |
className="pdf-frame"
|
| 52 |
title={`PDF Page ${viewerPage}`}
|
| 53 |
allow="fullscreen"
|