Skip to main content

Components

import { Copy } from 'copy-ink'

Copy is the single-field component and the namespace for the rest. All five are async Server Components.

Shared props

Every field-reading component (<Copy>, <Copy.Rich>, <Copy.Image>) takes:

PropTypeDescription
fieldstringDot path into the content file. Required
scopestringAbsolute scope. Overrides the route and any enclosing item
localestringOverrides the route's locale

scope is always absolute and always escapes an enclosing collection context. See resolution order.

Both props also decide how the page renders: without them the component resolves the current route, which reads the request headers and makes the page dynamic. See scope and rendering.

<Copy>

<Copy field="heroTitle" />
<Copy field="hero.subtitle" scope="about" locale="de" />

Renders a plain string, and in production nothing else — no element, no class, no style. Numbers and booleans stringify; anything else renders as empty.

<Copy.Rich>

<Copy.Rich field="body" />

Renders Markdown to React elements against a strict allowlist. No HTML string is produced. See Rich text.

Shared props only.

<Copy.Image>

<Copy.Image field="cover" className="rounded" sizes="(max-width: 768px) 100vw, 50vw" priority />

Wraps next/image, taking src, alt, width and height from the typed node. In addition to the shared props:

PropType
classNamestring
sizesstring
priorityboolean
qualitynumber
loading'lazy' | 'eager'

A field that is not an image node renders nothing in production, and a labelled placeholder in editor mode. A remote source from an undeclared host renders as a plain <img unoptimized>.

<Copy.Item>

<Copy.Item collection="posts" item={slug}>
<h1><Copy field="title" /></h1>
</Copy.Item>
PropTypeDescription
collectionstringCollection name from the config. Required
itemstringItem slug. Required
localestringOverrides the route's locale
childrenReactNodeScoped to the item. Required

Falls back to the default locale's item when the current locale has none. Renders nothing when the item does not exist, unless missingField is 'error', which throws UNKNOWN_ITEM.

<Copy.List>

<Copy.List collection="posts" limit={10} orderBy="date" direction="desc">
{(item, index) => <article key={item.slug}></article>}
</Copy.List>
PropTypeDescription
collectionstringCollection name. Required
children(item, index) => ReactNodeRender function. Required
limitnumberMaximum items, applied after ordering
orderBystringField to sort by. Defaults to the schema's orderBy
direction'asc' | 'desc'Sort direction
filter(item) => booleanApplied after ordering
emptyReactNodeRendered when nothing is visible
localestringOverrides the route's locale

Drafts are hidden outside editor mode. Each row is scoped to its own item.

CollectionItem

The value passed to the render function, and returned by getItem / getList:

interface CollectionItem {
collection: string
slug: string
locale: string
/** Repo-relative path of the item's YAML file. */
path: string
data: ContentData
draft: boolean
/** Manual position written by the editor's reorder control. */
order: number | null
}

<CopyInkScripts />

import { CopyInkScripts } from 'copy-ink/server'

<body>
{children}
<CopyInkScripts />
</body>

Renders nothing at all without a session. With one, renders the loader that dynamically imports the editor. Takes no props. Render it once, in the root layout, inside <body>.

<CopyProvider>

import { CopyProvider } from 'copy-ink/server'

<CopyProvider scope="about">{children}</CopyProvider>
PropTypeDescription
scopestringAbsolute. Defaults to the current route
localestringDefaults to the route's locale
childrenReactNodeRequired

Publishes one scope's content to client components so useCopy works. Put it in the page, not the root layout, when it should follow navigation.

AdminPage

app/admin/page.tsx
export { AdminPage as default } from 'copy-ink/admin'

The sign-in page, mounted at adminPath. Signed out it offers a sign-in button; signed in it names the user and links back to the site.

It reads ?next= from searchParams and returns there after sign-in, so a "edit this page" link can send the client straight back where they were.