SEO
In this guide, we will present one of the ways you can work with SEO fields in Contember. You can take it as a continuation of the quickstart tutorial.
1. Schema definition
First, we have to define the schema. Here we have chosen four fields we want to edit.
title
(for<title>
tag)description
(for<meta name="description" />
)ogTitle
(for<meta property="og:title" />
)ogDescription
(<meta property="og:description " />
)
For this, we define an entity in our schema with fields. We are making only the title
field required (using the .notNull()
call). We save it in a new file.
import { SchemaDefinition as def } from '@contember/schema-definition'
export class Seo {
title = def.stringColumn().notNull()
description = def.stringColumn()
ogTitle = def.stringColumn()
ogDescription = def.stringColumn()
}
Let's say we have just one type of page represented by an existing Article
entity.
In our headless CMS starter kit, we have two entities representing a "page" on the front-end of your site, which need SEO attributes. These are Page
(a page that presents static information - such as "Contact" or "About us") and Article
(a blog-like article). You may have even more.
To add SEO fields to all of these pages you can create a single Seo
entity and then create relations for all of your page-like entities. Just repeat the steps below for each of these entities.
To connect it to the Article
entity we add the relation to the entity:
import { SchemaDefinition as def } from '@contember/schema-definition'
import { Seo } from './Seo'
export class Article {
// some specific fields…
seo = def.oneHasOne(Seo, 'article').notNull().removeOrphan()
}
We specify that the field is called Seo
and one Article can have just one Seo and vice versa (thus oneHasOne
relation). The notNull()
call marks it as non-nullable, so Article
must always have a Seo
entity connected. To delete a Seo
when we delete an Article
we add the removeOrphan
call.
Note, that if you have already created some articles you can't mark this field as notNull
, because the migration would fail. Just remove the notNull
call in that case.
To specify the other side of the relation we add a field to the Seo
entity we created earlier.
import { SchemaDefinition as def } from '@contember/schema-definition'
import { Article } from './Article'
export class Seo {
title = def.stringColumn().notNull()
description = def.stringColumn()
ogTitle = def.stringColumn()
ogDescription = def.stringColumn()
article = def.oneHasOneInverse(Article, 'seo')
}
To create migration for this we run npm run contember migration:diff . add-seo
command and then choose Yes and execute immediately
to create the migration file and execute it on our local machine.
2. Add fields to the administration
Now, we have these fields in our database and API, but we need to add them to our administration. To easily reuse them in different parts of our administration we can create a component that will encapsulate all the fields. We create a new file for it:
import {
Component,
TextAreaField,
TextField,
} from '@contember/admin'
export const Seo = Component(
() => (
<>
<TextField field="seo.title" label="Title" />
<TextAreaField field="seo.description" label="Description" />
<TextField field="seo.ogTitle" label="Open Graph title (for facebook)" />
<TextAreaField field="seo.ogDescription" label="Open Graph description (for facebook)" />
</>
),
)
Then we can use use the newly created component in existing administration pages like this:
import * as React from 'react'
import { EditPage, RichTextField, TextField } from '@contember/admin'
import { Seo } from '../components/Seo'
export default () => (
<EditPage entity="Article(id = $id)" rendererProps={{ title: 'Edit Article' }}>
<TextField field="title" label="Title" />
<RichTextField field="content" label="Content" />
<Seo />
</CreatePage>
)
Congratulations - you have just created your first custom component and used it!
Optional: Using the article's title as a SEO title
Most of the time your article or page has a title that you use as a title in SEO fields. This can be achieved using the DerivedFieldLink
component.
First, let's modify our Seo
component to take the name of the field we should copy the title from.
import {
Component,
TextAreaField,
TextField,
} from '@contember/admin'
interface SeoProps {
titleField?: string
}
export const Seo = Component<SeoProps>(
({ titleField }) => (
<>
<TextField field="seo.title" label="Title" />
<TextAreaField field="seo.description" label="Description" />
<TextField field="seo.ogTitle" label="Open Graph title (for facebook)" />
<TextAreaField field="seo.ogDescription" label="Open Graph description (for facebook)" />
</>
),
)
And then, when the prop is passed, we use the DerivedFieldLink
component. Thus when the source field (title of the article) is edited, the change is mirrored in the derived fields (seo.title
and seo.ogTitle
).
import {
Component,
TextAreaField,
TextField,
} from '@contember/admin'
interface SeoProps {
titleField?: string
}
export const Seo = Component<SeoProps>(
({ titleField }) => (
<>
<TextField field="seo.title" label="Title" />
<TextAreaField field="seo.description" label="Description" />
<TextField field="seo.ogTitle" label="Open Graph title (for facebook)" />
<TextAreaField field="seo.ogDescription" label="Open Graph description (for facebook)" />
{titleField && (
<>
<DerivedFieldLink sourceField={titleField} derivedField="seo.title" />
<DerivedFieldLink sourceField={titleField} derivedField="seo.ogTitle" />
</>
)}
</>
),
)