Generate a PDF in Next.js

The simplest way to generate a PDF in Next.js is to call the Docweave API from a Route Handler or Server Action with the built-in fetch — no headless browser to run, bundle, or patch yourself. POST your HTML, a URL, or a template + JSON to one endpoint and get back a rendered PDF.

Generate a PDF from a Route Handler

Using only fetch and node:fs/promises, POST your HTML to /api/v1/pdf from a Route Handler and write the response to disk:

// app/api/generate-pdf/route.ts
import { NextResponse } from "next/server";
import { writeFile } from "node:fs/promises";

export async function POST(request: Request) {
  const { html, filename } = (await request.json()) as {
    html: string;
    filename: string;
  };

  const response = await fetch("https://docweave.dev/api/v1/pdf", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.DOCWEAVE_API_KEY}`,
      "Content-Type": "application/json",
      Accept: "application/pdf",
    },
    body: JSON.stringify({
      source: { type: "html", html },
      options: { format: "A4" },
      idempotencyKey: filename,
    }),
  });

  if (!response.ok) {
    return NextResponse.json(
      { error: `Docweave API error: ${response.status}` },
      { status: 502 },
    );
  }

  // Vercel functions only allow writes under /tmp — fine for a
  // short-lived file you then stream, upload, or email.
  const pdfBuffer = Buffer.from(await response.arrayBuffer());
  const outputPath = `/tmp/${filename}.pdf`;
  await writeFile(outputPath, pdfBuffer);

  return NextResponse.json({ ok: true, path: outputPath });
}

…or from a Server Action

If the PDF comes from a form submission, skip the separate endpoint and call Docweave directly from a Server Action:

// app/actions/generate-pdf.ts
"use server";

import { writeFile } from "node:fs/promises";

export async function generateReportPdf(formData: FormData) {
  const html = String(formData.get("html"));

  const response = await fetch("https://docweave.dev/api/v1/pdf", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.DOCWEAVE_API_KEY}`,
      "Content-Type": "application/json",
      Accept: "application/pdf",
    },
    body: JSON.stringify({
      source: { type: "html", html },
      options: { format: "A4" },
      idempotencyKey: `report-${Date.now()}`,
    }),
  });

  if (!response.ok) {
    throw new Error(`Docweave API error: ${response.status}`);
  }

  const pdfBuffer = Buffer.from(await response.arrayBuffer());
  await writeFile("/tmp/report.pdf", pdfBuffer);
}

…or from an AI agent

The same workflow works from an AI agent with no fetch code at all. Run npx @docweave/mcp and your agent gets a generate_pdf tool that wraps the same render orchestration as the REST endpoint:

// From an AI agent over MCP — no fetch/HTTP boilerplate:
// npx @docweave/mcp
generate_pdf({
  source: {
    type: "html",
    html: "<h1>Hello from Next.js</h1><p>Generated with Docweave.</p>",
  },
  outputPath: "/tmp/output.pdf"
})

FAQ

What's the simplest way to generate a PDF in Next.js?

Call the Docweave API from a Route Handler or Server Action: POST your HTML (or a URL, or a template + JSON) to /api/v1/pdf with an Authorization: Bearer header using the built-in fetch, then write the returned bytes to disk with node:fs/promises. There's no headless browser to run inside your Next.js app.

Should I generate the PDF in a Route Handler or a Server Action?

Both work the same way under the hood — one fetch call to Docweave. Use a Route Handler when you want a URL other clients (a form, a webhook, a mobile app) can POST to; use a Server Action when the PDF is generated directly from a Next.js form submission and you don't need a separate endpoint.

Do I need to bundle Playwright or Puppeteer with my Next.js app?

No. Chromium runs on Docweave's infrastructure, not inside your Next.js function, so there's no browser binary to bundle, no serverless function size limit to fight, and no Playwright version to keep patched. Your app only needs fetch.

How do I avoid double-generating a PDF when a Server Action or Route Handler retries?

Pass an idempotencyKey in the request body (an order ID, invoice number, or timestamp-based key). If the same key arrives again — say, after a client retry or a redeployed function replaying a request — Docweave returns the original result instead of rendering and billing a second time.

Ready to generate PDFs from your Next.js app?

Get an API key