Skip to main content

Integrate with Next.js

Generate PDF documents from your Next.js application using an API route for the server-side logic and a client component for the UI.

Prerequisites

  • A Next.js 13+ project (App Router)
  • A Rynko account with an API key
  • A published template (note the template ID)

Step 1: Install the SDK

npm install @rynko/sdk

Add your API key to .env.local:

.env.local
RYNKO_API_KEY=fm_abc123xyz456...
warning

The RYNKO_API_KEY variable intentionally omits the NEXT_PUBLIC_ prefix so it stays on the server and is never exposed to the browser.


Step 2: Create an API Route

Create a server-side route that handles document generation. The SDK runs only on the server, keeping your API key secure.

app/api/generate-invoice/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Rynko, RynkoError } from '@rynko/sdk';

const rynko = new Rynko({
apiKey: process.env.RYNKO_API_KEY!,
});

export async function POST(request: NextRequest) {
const { customerName, invoiceNumber, amount } = await request.json();

try {
const job = await rynko.documents.generatePdf({
templateId: 'YOUR_TEMPLATE_ID',
variables: { customerName, invoiceNumber, amount },
});

const completed = await rynko.documents.waitForCompletion(job.jobId, {
timeout: 30000,
});

if (completed.status === 'failed') {
return NextResponse.json(
{ error: completed.errorMessage },
{ status: 500 },
);
}

return NextResponse.json({ downloadUrl: completed.downloadUrl });
} catch (error) {
if (error instanceof RynkoError) {
return NextResponse.json(
{ error: error.message },
{ status: error.statusCode },
);
}
return NextResponse.json(
{ error: 'Document generation failed' },
{ status: 500 },
);
}
}
tip

Replace YOUR_TEMPLATE_ID with the ID of your template from the Rynko dashboard.


Step 3: Build the Client Component

Create a form component that calls your API route:

app/invoices/invoice-form.tsx
'use client';

import { useState } from 'react';

export function InvoiceForm() {
const [customerName, setCustomerName] = useState('');
const [amount, setAmount] = useState('');
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError(null);
setDownloadUrl(null);

try {
const res = await fetch('/api/generate-invoice', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
customerName,
invoiceNumber: `INV-${Date.now()}`,
amount: parseFloat(amount),
}),
});

const data = await res.json();
if (!res.ok) throw new Error(data.error || 'Generation failed');

setDownloadUrl(data.downloadUrl);
} catch (err) {
setError(err instanceof Error ? err.message : 'Something went wrong');
} finally {
setLoading(false);
}
}

return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="customerName">Customer Name</label>
<input
id="customerName"
value={customerName}
onChange={(e) => setCustomerName(e.target.value)}
required
/>
</div>
<div>
<label htmlFor="amount">Amount</label>
<input
id="amount"
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
required
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Generating...' : 'Generate Invoice PDF'}
</button>

{error && <p style={{ color: 'red' }}>{error}</p>}
{downloadUrl && (
<p>
<a href={downloadUrl} target="_blank" rel="noopener noreferrer">
Download Invoice PDF
</a>
</p>
)}
</form>
);
}

Step 4: Add the Page

Create a page that renders the form:

app/invoices/page.tsx
import { InvoiceForm } from './invoice-form';

export default function InvoicesPage() {
return (
<main>
<h1>Invoice Generator</h1>
<InvoiceForm />
</main>
);
}

Run npm run dev and visit http://localhost:3000/invoices to test.


Next Steps