Check the current contract and validator results before you build on this.
A reliable Next.js PDF endpoint should run in the Node.js runtime, validate a small application-owned request, translate it into a document operation, and return either PDF bytes or a typed error. Keep credentials and document logic on the server. Set explicit size and time limits, do not cache personalized files, and validate the generated artifact before release when the recipient or workflow requires it.
Choose the runtime before choosing the library
Route Handlers use the standard Request and Response APIs. The Next.js Route Handler docs show handlers in route.ts files that export named HTTP methods. Next.js recommends the Node.js runtime for server rendering. The runtime reference explains that the Edge runtime exposes a smaller API surface.
Document packages commonly need Node.js features, fonts, binary buffers, or native dependencies. Declare the runtime instead of relying on an adapter's default:
export const runtime = "nodejs";
export const maxDuration = 30;
maxDuration is a deployment hint, not a portable timeout guarantee. Enforce an operation timeout as well, and confirm the limits of the platform where the route will run.
Define an application request
Do not accept an arbitrary renderer schema directly from an unauthenticated client. Define the smallest payload your product needs. This invoice example accepts an order identifier and a delivery locale:
import { z } from "zod";
export const PdfRequest = z.object({
orderId: z.string().uuid(),
locale: z.enum(["en-US", "en-GB", "de-DE"]),
});
The server should fetch prices, tax status, customer data, and approved template settings from authoritative systems. A browser-supplied total is display input, not billing authority.
Create the Route Handler
Runstamp's public operation contract uses runstamp.run(operation, input). The checked pdf.render operation page publishes the input and output summary, error and loss boundaries, example files, and artifact hashes. Load the exact SDK transport required by your deployment; the handler below concentrates on the HTTP boundary:
import { local, Runstamp } from "@runstamp/sdk";
import { PdfRequest } from "./request";
export const runtime = "nodejs";
export const maxDuration = 30;
type ArtifactBytes = { bytes: Uint8Array; mediaType: string; hash: string };
const runstamp = new Runstamp({ transport: local() });
export async function POST(request: Request) {
let body: unknown;
try {
body = await request.json();
} catch {
return Response.json({ code: "INVALID_JSON" }, { status: 400 });
}
const parsed = PdfRequest.safeParse(body);
if (!parsed.success) {
return Response.json(
{ code: "INVALID_REQUEST", issues: parsed.error.issues },
{ status: 400 },
);
}
const order = await loadAuthorizedOrder(parsed.data.orderId);
if (!order) {
return Response.json({ code: "ORDER_NOT_FOUND" }, { status: 404 });
}
const input = buildInvoiceDocument(order, parsed.data.locale);
const result = await runstamp.run<ArtifactBytes>("pdf.render", input, {
operation: {
timeoutMs: 20_000,
lossPolicy: "collect",
},
});
if (!result.ok) {
return Response.json(
{ code: result.error.code, remediation: result.error.remediation },
{ status: result.error.retryable ? 503 : 422 },
);
}
return new Response(Buffer.from(result.value.bytes), {
status: 200,
headers: {
"Content-Type": result.value.mediaType,
"Content-Disposition": 'attachment; filename="invoice.pdf"',
"Cache-Control": "private, no-store",
"X-Content-Type-Options": "nosniff",
"X-Artifact-SHA256": result.value.hash,
},
});
}
loadAuthorizedOrder and buildInvoiceDocument are application code. They are deliberately separate from the route so they can be tested without an HTTP server.
Return typed failures
The pdf.render contract declares schema, timeout, cancellation, resource-limit, and loss-policy failures. Do not reduce them all to 500.
| Condition | Suggested response | Retry automatically? |
|---|---|---|
| Invalid browser payload | 400 |
No |
| Order absent or inaccessible | 404 |
No |
| Renderer rejects document schema | 422 |
No; correct the mapping |
| Operation timeout | 503 |
Only with an idempotency policy |
| Resource limit exceeded | 413 or 422 |
No; reduce or split the request |
| Internal or dependency failure | 503 |
When the operation marks it retryable |
The HTTP mapping belongs to your application. Preserve the original operation code in logs and responses so operators can distinguish a bad request from a temporary renderer failure.
Put limits at three layers
A production endpoint needs limits before, during, and after rendering.
- Reject request bodies larger than the product needs.
- Bound rows, pages, images, remote assets, and execution time in the operation options.
- Reject or quarantine results above the delivery channel's file-size limit.
Remote assets deserve separate policy. Resolve approved logos and fonts on the server, or allow only a controlled origin list. Do not let an untrusted URL in JSON turn the renderer into a network proxy. The governance guide covers review, release, and policy controls for document workflows.
Decide whether the response should be synchronous
Return bytes directly when generation is bounded and the caller is waiting for one file. Use a Document Job when the workflow includes several outputs, review, retries, or delivery:
request -> authorize -> reserve job -> render -> validate
-> store -> review -> release -> reconcile delivery
The synchronous endpoint should not imply those later states happened. A 200 proves only that this request returned bytes. The evidence guide explains how artifacts, losses, diagnostics, and receipts describe what actually occurred.
Validate before release
PDF bytes can be structurally valid and still be wrong for the recipient. Separate these checks:
- schema validation before rendering;
pdf.validatefor the supported structural rules;- visual review for clipping, wrapping, and page breaks;
- accessibility testing when the destination requires it;
- business review for names, amounts, dates, and legal text;
- exact-byte approval when a reviewed file must be the released file.
Runstamp's pdf.validate operation page states its supported contract and limits. It does not substitute for a destination application, assistive-technology test, or business authority.
Production checklist
- Use the Node.js runtime unless every dependency is verified for the target Edge runtime.
- Authenticate the caller and authorize access to the source record.
- Parse a narrow request schema.
- Build document input from server-owned data and approved assets.
- Set resource and time limits.
- Preserve typed operation errors and loss records.
- Use
private, no-storefor personalized downloads. - Record an artifact hash without logging document contents.
- Validate and review according to the destination's acceptance requirements.
- Move recurring or multi-output work into a versioned Document Job.
Start with the checked PDF render operation, then use describe("pdf.render") to bind the complete live schema and error handling in your application.
Keep the document workflow in your product.
Pick an operation, run it on a real file, then embed review and release where your customers work.