Guide
The React patterns from the previous page all work
in Next.js. This page covers the Next-specific decisions: when to render on
the server, how next/image interacts with the signature CDN, and the
edge-runtime gotchas.
Server vs client component
eSigKit signatures are public (no auth needed to fetch the rendered HTML). That means you can fetch them in a Server Component and stream the result down to the browser without round-tripping through the client:
// app/profile/[userId]/page.tsx — server component (default)
async function getSignatureHtml(userId: string): Promise<string> {
const res = await fetch(
`https://api.esigkit.com/v1/users/${userId}/signature`,
{ next: { revalidate: 3600 } } // cache for 1 hour
);
if (!res.ok) throw new Error(`signature fetch ${res.status}`);
return res.text();
}
export default async function ProfilePage({
params,
}: {
params: Promise<{ userId: string }>;
}) {
const { userId } = await params;
const html = await getSignatureHtml(userId);
return (
<article>
<h1>Profile</h1>
<div dangerouslySetInnerHTML={{ __html: html }} />
</article>
);
}
This:
- Avoids a client-side fetch waterfall (the HTML is already in the initial document).
- Uses Next’s data cache —
revalidate: 3600means once per hour peruserId, not once per request. - Is SSR-friendly (the signature is in the HTML for crawlers).
Use a client component when …
- You’re polling for
signature/status(live status indicator while a render is in flight). - The signature is inside an interactive widget (drag-and-drop, etc.).
- You’re embedding inside a
'use client'boundary already.
In those cases, drop in the useSignature hook from the React guide.
next/image vs raw <img> for thumbnails
If you’re rendering the eSigKit signature as a preview thumbnail (e.g.,
inside a settings page), use next/image for the user’s photo (a real
PNG), but not for the signature itself — the signature is HTML, not an
image.
import Image from 'next/image';
// ✅ The user's avatar (a PNG):
<Image
src={user.photoUrl}
width={64}
height={64}
alt={user.name}
/>
// ✅ The signature (HTML, inlined):
<div dangerouslySetInnerHTML={{ __html: signatureHtml }} />
If you really need an image preview of the signature (e.g., for a card grid), the simplest path is to screenshot it server-side via Puppeteer + upload the PNG. eSigKit doesn’t yet provide a server-side screenshot endpoint — contact support if it’s a priority for your use case.
next/image with the user-photo CDN
Avatar URLs live on cdn.esigkit.com. Add the CDN to
images.remotePatterns in next.config.js:
/** @type {import('next').NextConfig} */
module.exports = {
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'cdn.esigkit.com', pathname: '/**' },
],
},
};
Without this entry, <Image> rejects the URL at build time with
Invalid src prop … hostname is not configured.
Edge runtime gotchas
If you’re targeting runtime: 'edge' for a route handler that serves
signatures, two things to know:
fetchin the edge runtime doesn’t auto-follow redirects across origins by default in some adapters. Passredirect: 'follow'explicitly. The redirect endpoint always 301s to acdn.esigkit.comURL.- Don’t use
Bufferin edge handlers — it’s a Node.js global. If you’re doing anything binary (e.g., proxying the open-tracking pixel), useUint8ArrayandResponse.body(aReadableStream).
Server actions with @esigkit/node
If you’re building a dashboard inside a Next app that calls the eSigKit API, use a server action with the official Node SDK so the API key never reaches the browser:
npm install @esigkit/node
// app/lib/esigkit.ts — module-scoped, reused across server actions
import { Esigkit } from '@esigkit/node';
export const esk = new Esigkit({ apiKey: process.env.ESIGKIT_API_KEY });
// app/actions.ts
'use server';
import { esk } from './lib/esigkit';
import { EsigkitNotFoundError } from '@esigkit/node/errors';
export async function getSignature(email: string) {
try {
return await esk.signatures.fetch({ email });
} catch (e) {
if (e instanceof EsigkitNotFoundError) return null;
throw e;
}
}
// app/page.tsx
import { getSignature } from './actions';
export default async function Page() {
const sig = await getSignature('alice@example.com');
if (!sig || sig.status !== 'live') return <p>Signature not available.</p>;
return <div dangerouslySetInnerHTML={{ __html: sig.html }} />;
}
API keys live exclusively on the server; the browser never sees them.
The SDK’s browser guard throws if
you accidentally import it from a 'use client' boundary.
Why the SDK over raw fetch
- Typed responses + typed errors — no
as anycasts, no missing fields when the spec adds optional metadata. - In-process cache — 60-second TTL on
signatures.fetch(). A page that renders the same signature multiple times (preview + footer + thumbnail) round-trips the API once. - Optimistic concurrency —
EsigkitConflictError.serverVersionis on the instance; you can refetch + merge without parsing JSON error bodies. - AbortSignal — propagates Next.js request cancellation through to
the underlying
fetch. Long-running renders can be cancelled when the user navigates away.
See the Node SDK guide for the full surface.
Streaming + suspense
For long-running list pages (e.g., a per-org “all team signatures”
overview), stream each card with its own <Suspense> boundary so slow
fetches don’t block the rest of the page:
import { Suspense } from 'react';
async function SignatureCard({ userId }: { userId: string }) {
const html = await getSignatureHtml(userId);
return (
<div className="card">
<div dangerouslySetInnerHTML={{ __html: html }} />
</div>
);
}
export default function TeamPage({ users }: { users: { userId: string }[] }) {
return (
<div className="grid">
{users.map((u) => (
<Suspense key={u.userId} fallback={<div className="card">Loading…</div>}>
<SignatureCard userId={u.userId} />
</Suspense>
))}
</div>
);
}
Each card streams in independently. Great for dashboards where rendering 40 signatures sequentially would otherwise mean a 40× the slowest fetch total wait.
Common pitfalls
- Don’t use the API in
getStaticProps/generateStaticParamsif you’ve got many users. Build times explode. Use ISR withrevalidate: 3600(or longer) — fetch on demand, cache. - Don’t ship API keys to the client. Use server actions or route handlers. The eSigKit API key has full org admin scope; leaking it is the same blast radius as leaking your dashboard password.
- Don’t
force-dynamican entire layout to fetch one signature. Scope the dynamic boundary tight (the page, or even a single component inside it) so the rest of the layout stays static.