Next.js App Router Integration

The @copypatch/next package integrates CopyPatch v2 directly with the Next.js App Router (React Server Components). It mounts same-origin route handlers and reads published snapshots directly from storage during server rendering.

1. Initialize the Shared Backend Singleton

Create a server-side module that instantiates your persistence adapter and the CopyPatch backend instance:

lib/copypatch.ts
// lib/copypatch.ts
import { createCopyPatchBackend } from '@copypatch/backend';
import { createSQLitePersistence } from '@copypatch/storage-sqlite';

const persistence = createSQLitePersistence('./data/copypatch.sqlite');
await persistence.migrate();

export const copypatch = createCopyPatchBackend({
  persistence,
  passphraseHash: process.env.COPYPATCH_PASSPHRASE_HASH!,
});

2. Mount the Same-Origin Route Handler

Create a catch-all route handler at app/%5F%5Fcopypatch/api/v2/[...path]/route.ts:

app/%5F%5Fcopypatch/api/v2/[...path]/route.ts
// app/%5F%5Fcopypatch/api/v2/[...path]/route.ts
import { createCopyPatchRouteHandlers } from '@copypatch/next/server';
import { copypatch } from '@/lib/copypatch';

export const { GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS } =
  createCopyPatchRouteHandlers(copypatch, {
    // Optional: resolve host-authenticated context or trusted IP for rate limiting
    resolveContext: async (request) => ({
      clientAddress: request.headers.get('x-forwarded-for')?.split(',')[0].trim(),
    }),
  });
Why %5F%5Fcopypatch? Next.js treats underscore-prefixed folders (e.g. _copypatch) as private folders and excludes them from routing. By percent-encoding the underscores as %5F%5Fcopypatch in the filesystem, Next.js properly serves the canonical public URL: /__copypatch/api/v2/....

3. Pre-Render with Server Component Snapshots

In your Server Components or root page, call readPublishedSnapshot(). This reads directly from your persistence layer with zero HTTP overhead or internal fetch latency, and passes the hydrated state to NextCopyPatchProvider:

app/[locale]/page.tsx
// app/[locale]/page.tsx
import { NextCopyPatchProvider, EditableText } from '@copypatch/next';
import { readPublishedSnapshot } from '@copypatch/next/server';
import { copypatch } from '@/lib/copypatch';

export default async function Page({
  params,
}: {
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;

  // Direct storage read inside Server Component (Zero HTTP overhead)
  const initialSnapshot = await readPublishedSnapshot(copypatch, locale, {
    fallback: { 'home.hero.title': 'Welcome to our platform' },
  });

  return (
    <NextCopyPatchProvider locale={locale} initialSnapshot={initialSnapshot}>
      <main className="container">
        <EditableText contentKey="home.hero.title" as="h1">
          Welcome to our platform
        </EditableText>
      </main>
    </NextCopyPatchProvider>
  );
}

Benefits of the Next.js Integration

  • Zero Layout Shifts: Content is pre-rendered into the initial HTML response during SSR/RSC.
  • Zero Self-Fetch: The server component accesses storage directly via memory or database pool without calling http://localhost....
  • No Proxy / Rewrite Config: No custom rewrite rules in next.config.js are needed because the route lives inside your application tree.

Static Export Notice

Important: CopyPatch requires a server runtime (Node.js or serverless/edge). If your Next.js project is configured with output: 'export', you cannot host the authenticated editing API or dynamic database persistence.