Document
Config.document creates a Type for pages: it adds a title, a path and a metadata field to the fields you define. Use it for anything that gets its own url; use Config.type for entries that don't, such as settings or blocks.
import {Config, Field} from 'alinea'
export const Page = Config.document('Page', {
fields: {
intro: Field.text('Intro', {multiline: true}),
body: Field.richText('Body')
}
})Included fields
title: a required text field, shown in the sidebar and returned asQuery.title.path: a required path field that slugifies the title as you type. It's the last segment of the entry's url.metadata: SEO and sharing data, on a separate "Metadata" tab. It holdstitle,description(at most 160 characters),openGraph(image,title,description) and the entry's urlaliases.
The dashboard shows title, path and your own fields on a "Document" tab, and the metadata on a "Metadata" tab.
The metadata field also keeps an audit trail: createdAt and createdBy are set when the entry is created, updatedAt and updatedBy on every save. Timestamps are Unix timestamps in seconds, the users are the editor's name and email. When an entry is published on a new url, its previous url is added to aliases. These values are written to the content files, so keep in mind that editor emails end up in your repository.
Renamed and moved pages
When an entry is published on a new url, because its path changed or it moved to another parent, its previous url is added to metadata.aliases. Links between entries keep working, since they point to the entry rather than its url. For visitors who arrive on an old url, look up the entry by its alias and redirect to where it lives now, for example in the catch-all route that renders your pages:
import {cms} from '@/cms'
import {Query} from 'alinea'
import {notFound, permanentRedirect} from 'next/navigation'
// No page was found at `url`, check if one used to live there
const moved = await cms.first({
alias: url,
select: Query.url
})
if (moved) permanentRedirect(moved)
notFound()The dashboard shows the other side: the References tab in the side panel of an entry lists every entry that links to it, so editors can see what is affected before they rename, move or delete a page.
Options
Config.document takes the same options as Config.type: contains, orderChildrenBy, entryUrl, icon and so on. To change a built-in field, define it in fields under the same name. A home page, for example, often gets a read-only path (see a fixed path for the home page):
import {Config, Field} from 'alinea'
export const Home = Config.document('Home', {
fields: {
path: Field.path('Path', {readOnly: true, width: 0.5})
}
})Using the metadata
Select the metadata field in the query for a page and map it to your framework's head tags. With the Next.js App Router:
import {cms} from '@/cms'
import {Page} from '@/schema'
import {Query} from 'alinea'
import type {Metadata} from 'next'
interface PageProps {
params: Promise<{slug?: Array<string>}>
}
export async function generateMetadata({params}: PageProps): Promise<Metadata> {
const {slug = []} = await params
const page = await cms.first({
type: Page,
url: `/${slug.join('/')}`,
select: {title: Query.title, metadata: Page.metadata}
})
if (!page) return {}
const {metadata} = page
return {
title: metadata.title || page.title,
description: metadata.description,
openGraph: {
title: metadata.openGraph.title || metadata.title || page.title,
description: metadata.openGraph.description || metadata.description,
images: metadata.openGraph.image?.src
}
}
}Image urls are relative to your site (/admin/file/...), so set metadataBase in your root layout to get absolute Open Graph urls.
Query.createdAt and Query.updatedAt read the audit timestamps directly, for example to sort by the most recently created entries: orderBy: {desc: Query.createdAt}.