Skip to main content

Reading content in code

Components cannot fill an alt attribute, a title, a metadata string or a JSON response. For those, read content directly.

In Server Components

import { getCopy, getItem, getList, getSlugs, getMetadata } from 'copy-ink/server'

const copy = await getCopy({ scope: 'about', locale: 'de' })

copy.get('hero.subtitle') // string, '' when missing
copy.raw('aboutImg') // the parsed value, including typed nodes
copy.has('hero.subtitle') // boolean
copy.all() // the whole parsed document
copy.scope // 'about'
copy.locale // 'de'

Everything is on the filesystem, so none of this makes a network call.

get coerces: strings pass through, numbers and booleans stringify, anything else reads as ''. Reach for raw when you need the structure.

Scope and dynamic rendering

Without a scope, getCopy resolves the current route, which means reading the request headers — and that makes the page dynamic.

await getCopy() // current route, dynamic
await getCopy({ scope: 'about' }) // explicit, stays statically prerenderable

Pass an explicit scope in anything you want prerendered.

Collections

const post = await getItem('posts', slug)
const posts = await getList('posts', { limit: 5, orderBy: 'date', direction: 'desc' })
const slugs = await getSlugs('posts')

getList takes the same options as <Copy.List> and orders items the same way. Drafts are excluded. getSlugs is the shape generateStaticParams wants:

export async function generateStaticParams() {
return (await getSlugs('posts')).map((slug) => ({ slug }))
}

In Client Components

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

export function Hero() {
const copy = useCopy()
return <img src="/hero.png" alt={copy.get('heroImg.alt')} />
}

useCopy needs a provider in scope:

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

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

Put the provider in the page rather than 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 would keep serving the first route's content.

useCopy is also exported from copy-ink, but importing it from copy-ink/client guarantees a client component pulls in nothing but the hook, without relying on the bundler to tree-shake.

The provider serialises one scope's content into the RSC payload. Keep it to pages that need it; a _global scope full of copy is a page-weight decision.

In route handlers

Same API, no provider needed:

app/og/route.ts
import { getCopy } from 'copy-ink/server'

export async function GET() {
const copy = await getCopy({ scope: '_global' })
return Response.json({ company: copy.get('companyName') })
}

A route handler is its own entry point, so import your setup module there too:

import '@/copy-ink.setup'

Typed reads

copy-ink types generates unions for scopes, fields, collections and locales, and the API accepts them. They are suggestions, not constraints — string stays assignable, so a stale manifest never turns a working project red. See Generated types.