Skip to main content

Client API

import { useCopy, CopyInkProvider } from 'copy-ink/client'

useCopy is also exported from copy-ink, but that entry carries the Server Components too. Importing from copy-ink/client guarantees a client component pulls in nothing but the hook, without relying on the bundler to tree-shake.

useCopy()

'use client'
import { useCopy } from 'copy-ink/client'

export function Hero() {
const copy = useCopy()

return (
<img
src="/hero.png"
alt={copy.get('heroImg.alt')}
title={copy.get('heroImg.title')}
/>
)
}

Returns a CopyAccessor — the server's CopyReader minus all():

MemberTypeDescription
get(field)stringCoerced; '' when missing
raw(field)unknownThe parsed value
has(field)boolean
scopestring
localestring

Throws when no provider is in scope. That is deliberate: silently returning empty strings would look like missing content rather than missing wiring.

<CopyProvider>

The provider you actually render is the server one, which reads content and hands it down:

app/about/page.tsx
import { CopyProvider } from 'copy-ink/server'
import { Hero } from './Hero'

export default function Page() {
return (
<CopyProvider>
<Hero />
</CopyProvider>
)
}

Put it in the page, not the root layout, when it should follow navigation — App Router does not re-render a layout that did not change, so a layout-level provider keeps serving the first route's content.

Its props are documented in the component reference.

<CopyInkProvider>

import { CopyInkProvider } from 'copy-ink/client'

<CopyInkProvider value={{ scope: 'about', locale: 'en', data }}>
{children}
</CopyInkProvider>

The underlying client-side context provider. Use it when you already have the content — a test, a Storybook story, a client-rendered preview — and want to skip the server round trip. <CopyProvider> is a thin wrapper around it.

TypeShape
CopySnapshot{ scope: string; locale: string; data: ContentData }

What this costs

The provider serialises one scope's whole document into the RSC payload. That is the trade for reading content on the client at all.

Keep it to pages that need it, and prefer passing a single string as a prop when that is all a client component wants:

// Server Component
<Hero alt={copy.get('heroImg.alt')} />