Skip to main content

Troubleshooting

Start here:

npx copy-ink doctor

It checks config, credentials and Next wiring in one pass, and most of what follows is something it already reports.

Nothing renders

A <Copy> renders as empty.

The field is missing from the content file for that scope. Confirm which scope the route maps to — /about/team is about/team — and run copy-ink check, which reports missing-key with the file and line of the usage.

Set missingField: 'key' temporarily to see the field path on the page instead of a gap.

Every <Copy> renders as empty.

The content directory is not where the config says. doctor reports content-missing.

NOT_SET_UP at request time

Something read content before setupCopyInk() ran. The setup module must be imported by the module graph that is rendering.

Route handlers are their own entry point — the root layout importing your setup module does not reach them:

app/api/copy-ink/[...copyInk]/route.ts
import copyInk from '@/copy-ink.setup'

export const { GET, POST } = createCopyInkHandler(copyInk)

doctor reports this as handler-not-wired.

The wrong page's content appears

The middleware is not stamping the pathname, or its matcher excludes the route.

middleware.ts
export { copyInkMiddleware as middleware } from 'copy-ink/middleware'

export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}

If you already had middleware, compose rather than replace — see the quick start.

doctor reports middleware-missing and middleware-not-wired.

A field inside a collection resolves to the route

function Title() {
return <Copy field="title" /> // ❌
}

<Copy.Item collection="posts" item={slug}>
<Title />
</Copy.Item>

Server Components have no React context, so <Copy.Item> hands its context down by walking the element tree it receives. It cannot reach into what another component returns from its own body.

Give that <Copy> an explicit scope, or move it into the tree you pass to <Copy.Item>. See the context boundary.

The editor never appears

  1. Is <CopyInkScripts /> rendered in the root layout, inside <body>? doctor warns scripts-missing.
  2. Did sign-in actually complete? GET /api/copy-ink/session should return a user.
  3. Is the account on auth.allowlist? An empty allowlist permits nobody.

Sign-in loops or fails

"Sign-in could not be verified." The state cookie did not survive the round trip. Usually a callback URL on a different host than the one that started the flow — check the OAuth app's callback matches the deployed domain exactly.

"…is not on the editor allowlist for this site." The GitHub login or email is not in auth.allowlist. Note that GitHub only returns a public email; use the login when in doubt.

Signed in, then signed out again on the next request. Sessions live in the server process. Several instances, or serverless functions that cold-start, lose them. Supply a shared SessionStore.

Publishing fails

MessageCodeWhat to do
"Someone else changed this page — reload."STALE_BASEThe branch moved. Reload and redo the edit; there is no merging in v1
A forge errorBACKEND_ERRORUsually branch protection on backend.branch, or an App without Contents: write
"Nothing to publish."EMPTYThe changeset is empty — every edit was reverted to its original

For branch protection specifically: the App commits directly, so either exempt it from the rule or point backend.branch at an unprotected branch.

Images

An image renders as a plain <img> rather than through next/image. Its host is not in images.allowedHosts. That is a deliberate fallback, not a bug — declaring the host and restarting next dev fixes it.

Next throws about an unconfigured host. next.config is not importing .copy-ink/image-hosts.json, or was not restarted after allowedHosts changed. doctor warns remote-patterns.

An uploaded image 404s. Images live in public/content/…, mirroring the content tree — not next to the .yml. A relative src resolves against that mirror.

Types

No autocomplete on field. Run copy-ink types, and add .copy-ink to your tsconfig include. doctor warns tsconfig-include.

A field I just added is not in the union. The unions are generated. Re-run copy-ink types. Note that an unknown string still type-checks — the unions are suggestions, so this never blocks you.

check reports keys I use

Dead-key detection is static. A key read only through getCopy with a computed path cannot be seen:

npx copy-ink check --no-dead-keys

Missing keys, naming and file-form errors are still reported.

Still stuck

Open an issue with the output of copy-ink doctor and the version of copy-ink, Next and Node: github.com/Richard-Sen27/copy-ink/issues.