You shipped a real app this weekend. v0 scaffolded the UI, you wired up the data, it looks great. Then your first user asks for the one thing the AI didn't build: "Can I download this as a PDF?"

So you reach for the obvious options, and they all disappoint:

  • window.print() — gives you the browser's print dialog, page margins you don't control, and a result that looks like a 2009 web page, not a document.
  • A client-side library (jspdf, html2canvas) — rasterizes your DOM into a blurry image, breaks on page boundaries, ignores half your CSS, and balloons your bundle.
  • Puppeteer — the right rendering engine, but it doesn't deploy on serverless, and now you've lost the afternoon.

The clean answer is to render the PDF on the server, with a real browser engine, via one API call. Here's the whole thing.

1. Get a key

Sign up at leafwright.app, create a project, copy your lw_live_… key into .env:

LEAFWRIGHT_API_KEY=lw_live_xxxxxxxx

2. Add one route

Your app already renders the thing on screen as HTML. Send that HTML to the API and hand back a download link:

// app/api/export/route.ts
export async function POST(req: Request) {
  const { html } = await req.json();

  const res = await fetch("https://leafwright.co/api/v1/pdfs", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LEAFWRIGHT_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      source: { type: "html", html },
      delivery: { return: "url" }
    })
  });

  const { download_url } = await res.json();
  return Response.json({ download_url });
}

3. Wire up the button

async function downloadPdf(html: string) {
  const res = await fetch("/api/export", {
    method: "POST",
    body: JSON.stringify({ html })
  });
  const { download_url } = await res.json();
  window.open(download_url, "_blank");
}

That's it. Server-side Chromium renders your HTML exactly as a browser would — real fonts, real CSS, proper page breaks — and returns a signed URL.

Don't want to write print CSS?

Screen CSS and print CSS aren't the same, and getting page breaks, headers, and margins right is fiddly. So skip it: describe the document and let AI draft a clean, branded template for you, then render it with your data:

// Render a published template with JSON instead of raw HTML
await fetch("https://leafwright.co/api/v1/templates/tmpl_receipt/render", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LEAFWRIGHT_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ data: { order_id: "1024", total: "$59.00" } })
});

Lock your fonts, colors, and footer into a brand kit once, and every export — whether a user clicks the button or your backend generates 10,000 receipts overnight — comes out on-brand.

Ship it

PDF export is the feature that makes a weekend project feel like a product. It shouldn't take more than the five minutes above.

Start free — 100 PDFs/month, no credit card