Quick start
This walks a fresh App Router project from nothing to a client-editable site.
Steps 1–4 get content rendering in next dev; steps 5–7 turn the editor on.
Already installed the package? Start at step 1. Otherwise see Installation first.
npx copy-ink init writes steps 1–5 for you and wires your root layout. Read on
anyway — this is what it wrote, and every choice it made for you is here.
1. Configure
import { defineConfig } from 'copy-ink/config'
export default defineConfig({
contentDir: 'content',
defaultLocale: 'en',
locales: ['en'],
images: {
allowedHosts: ['cdn.example.com'],
},
auth: {
provider: 'github',
allowlist: ['client@example.com'],
},
backend: {
type: 'github',
repo: 'you/your-site',
branch: 'main',
},
collections: {
posts: {
label: 'Blog posts',
path: '_collections/posts',
slugFrom: 'title',
orderBy: { field: 'date', direction: 'desc' },
fields: {
title: { type: 'text', required: true },
date: { type: 'date', required: true },
cover: { type: 'image' },
excerpt: { type: 'text' },
body: { type: 'richtext' },
},
},
},
})
backend: { type: 'local' } writes edits straight to your working tree and
needs no credentials at all. Switch to the GitHub backend once the site is
wired up — see Auth and backends.
2. Register the config once per process
import { setupCopyInk } from 'copy-ink/server'
import config from './copy-ink.config'
export default setupCopyInk(config)
This must run at module scope, from a module your root layout imports — not during render. Keep the returned runtime as the default export: the route handler in step 5 needs it.
3. Wire the root layout
import { CopyInkScripts } from 'copy-ink/server'
import '../copy-ink.setup'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<CopyInkScripts />
</body>
</html>
)
}
<CopyInkScripts /> renders nothing at all for a visitor. Only once a
session exists does it render a small loader that pulls the editor in as its own
lazily-imported chunk.
4. Add the middleware
export { copyInkMiddleware as middleware } from 'copy-ink/middleware'
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}
This stamps the request pathname onto a header so Server Components can resolve
the current route. usePathname() is deliberately not used: it is unavailable
on the server and breaks with route groups, rewrites and locale prefixes.
Already have middleware? Compose it:
import { createCopyInkMiddleware } from 'copy-ink/middleware'
export const middleware = createCopyInkMiddleware({
before: (request) => yourOwnLogic(request),
})
At this point content renders. Add a <Copy> to a page and check it:
import { Copy } from 'copy-ink'
export default function Page() {
return <h1><Copy field="heroTitle" /></h1>
}
5. Mount the route handler and the sign-in page
import { createCopyInkHandler } from 'copy-ink/server'
import copyInk from '@/copy-ink.setup'
export const { GET, POST } = createCopyInkHandler(copyInk)
export const runtime = 'nodejs'
Pass the runtime explicitly. A route handler is its own entry point with its own module graph, so the root layout importing the setup module does not register anything here — and the failure would show up at request time, not at build.
export { AdminPage as default } from 'copy-ink/admin'
6. Wire remote image hosts
import hosts from './.copy-ink/image-hosts.json' with { type: 'json' }
export default {
images: {
remotePatterns: hosts.map((hostname) => ({ protocol: 'https', hostname })),
},
}
Generate that file with npx copy-ink types. next.config is read at process
start, so next dev needs a restart when allowedHosts changes.
Skip this step if every image is a local upload.
7. Check your wiring
npx copy-ink doctor
doctor reads your config, the environment and your Next files, then reports
what is missing: no middleware, a handler mounted without its runtime, a
next.config that never imports image-hosts.json, absent credentials. Fix
what it reports and you are done.
Where to go next
- How it works — what actually happens on a request.
- Content files — the YAML shape and the route mapping.
- Components —
<Copy>, scopes and resolution. - The editor — what your client sees.