Skip to content

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: 3600 means once per hour per userId, 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:

  • fetch in the edge runtime doesn’t auto-follow redirects across origins by default in some adapters. Pass redirect: 'follow' explicitly. The redirect endpoint always 301s to a cdn.esigkit.com URL.
  • Don’t use Buffer in edge handlers — it’s a Node.js global. If you’re doing anything binary (e.g., proxying the open-tracking pixel), use Uint8Array and Response.body (a ReadableStream).

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 any casts, 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 concurrencyEsigkitConflictError.serverVersion is 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 / generateStaticParams if you’ve got many users. Build times explode. Use ISR with revalidate: 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-dynamic an 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.