Next.js Discord

Discord Forum

Getting PDF as blob in server components but cannot send it to client to be downloaded

Answered
Genio posted this in #help-forum
Open in Discord
I always face this error
Blob { size: 4130863, type: 'application/pdf' }
 ⨯ Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
    at stringify (<anonymous>)


my server component
Note, i solved it now by converting it to base64 then back again to file as client component but i was wondering if there was a better approuch. I cannot use fetch in client due to cors policy
"use server"
const getOrderInvoice = async (orderId: string) => {
    const url = new URL(
        `${baseUrl}/service-requests/${orderId}/export`
    );
    const session = await getServerSession(authOptions);
    console.log(url.toString());
    try {
        const response = await fetch(url.toString(), {
            method: 'GET',
            headers: {
                authorization: `Bearer ${session?.accessToken}`
            }
        });
        const data = await response.arrayBuffer();
        const b64 = Buffer.from(data).toString('base64');
        
        if (!response.ok) {
            throw new Error('Cannot get order invoice');
        }
        return b64;
    } catch (error: any) {
        //return { error };
    }
};


client

"use client"
const downloadPDF = (pdf: string) => {
    const linkSource = `data:application/pdf;base64,${pdf}`;
    const downloadLink = document.createElement('a');
    const fileName = 'abc.pdf';
    downloadLink.href = linkSource;
    downloadLink.download = fileName;
    downloadLink.click();
};
----
    const downloadInvoice = async () => {
        //const {blob,error} =
        const base64 = await getOrderInvoice(seeker?.id);
        downloadPDF(base64 as string);
    };
    console.log(seeker);

    return (
                    <Button onClick={downloadInvoice}>
                        Download Invoice
                    </Button>
Answered by Ray
in this case, I would use route handler over server action
// download/[orderId]/route.ts
export const dynamic = "force-dynamic";

export function GET(req: Request, { params }) {
  const url = new URL(`${baseUrl}/service-requests/${params.orderId}/export`);
  return fetch(url.toString(), {
    method: "GET",
    headers: {
      authorization: `Bearer ${session?.accessToken}`,
    },
  });
}

// client.tsx
<a href={`/download/${orderId}`} download>
View full answer

2 Replies

@Genio I always face this error ts Blob { size: 4130863, type: 'application/pdf' } ⨯ Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported. at stringify (<anonymous>) my server component **Note, i solved it now by converting it to base64 then back again to file as client component but i was wondering if there was a better approuch. I cannot use fetch in client due to cors policy** ts "use server" const getOrderInvoice = async (orderId: string) => { const url = new URL( `${baseUrl}/service-requests/${orderId}/export` ); const session = await getServerSession(authOptions); console.log(url.toString()); try { const response = await fetch(url.toString(), { method: 'GET', headers: { authorization: `Bearer ${session?.accessToken}` } }); const data = await response.arrayBuffer(); const b64 = Buffer.from(data).toString('base64'); if (!response.ok) { throw new Error('Cannot get order invoice'); } return b64; } catch (error: any) { //return { error }; } }; client tsx "use client" const downloadPDF = (pdf: string) => { const linkSource = `data:application/pdf;base64,${pdf}`; const downloadLink = document.createElement('a'); const fileName = 'abc.pdf'; downloadLink.href = linkSource; downloadLink.download = fileName; downloadLink.click(); }; ---- const downloadInvoice = async () => { //const {blob,error} = const base64 = await getOrderInvoice(seeker?.id); downloadPDF(base64 as string); }; console.log(seeker); return ( <Button onClick={downloadInvoice}> Download Invoice </Button>
in this case, I would use route handler over server action
// download/[orderId]/route.ts
export const dynamic = "force-dynamic";

export function GET(req: Request, { params }) {
  const url = new URL(`${baseUrl}/service-requests/${params.orderId}/export`);
  return fetch(url.toString(), {
    method: "GET",
    headers: {
      authorization: `Bearer ${session?.accessToken}`,
    },
  });
}

// client.tsx
<a href={`/download/${orderId}`} download>
Answer