Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions packages/next/src/utilities/meta.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { describe, expect, it } from 'vitest'

import { generateMetadata } from './meta.js'

describe('generateMetadata', () => {
it('should handle a string title with titleSuffix', async () => {
const result = await generateMetadata({
serverURL: 'http://localhost:3000',
title: 'Dashboard',
titleSuffix: '- My CMS',
})

expect(result.title).toBe('Dashboard - My CMS')
})

it('should apply titleSuffix to default and template fields of a TemplateString title object', async () => {
const result = await generateMetadata({
serverURL: 'http://localhost:3000',
title: { default: 'Dashboard', template: '%s | Dashboard' },
titleSuffix: '- My CMS',
})

expect(typeof result.title).toBe('object')
expect((result.title as { default: string; template: string }).default).toBe(
'Dashboard - My CMS',
)
expect((result.title as { default: string; template: string }).template).toBe(
'%s | Dashboard - My CMS',
)
})

it('should use the TemplateString default for ogTitle when title is a TemplateString object', async () => {
const result = await generateMetadata({
serverURL: 'http://localhost:3000',
title: { default: 'My CMS', template: '%s | My CMS' },
titleSuffix: '- Payload',
})

// OG title must be a plain string — extract from TemplateString.default and append titleSuffix
expect(result.openGraph?.title).toBe('My CMS - Payload')
})

it('should use the TemplateString absolute for ogTitle when title has absolute property', async () => {
const result = await generateMetadata({
serverURL: 'http://localhost:3000',
title: { absolute: 'My CMS Absolute' },
titleSuffix: '- Payload',
})

expect(result.openGraph?.title).toBe('My CMS Absolute - Payload')
})

it('should apply titleSuffix to the absolute field of a TemplateString title object', async () => {
const result = await generateMetadata({
serverURL: 'http://localhost:3000',
title: { absolute: 'My CMS Absolute' },
titleSuffix: '- Payload',
})

expect(typeof result.title).toBe('object')
expect((result.title as { absolute: string }).absolute).toBe('My CMS Absolute - Payload')
})

it('should use openGraph.title string over incomingMetadata.title for ogTitle', async () => {
const result = await generateMetadata({
serverURL: 'http://localhost:3000',
title: 'My CMS',
titleSuffix: '- Payload',
openGraph: { title: 'Custom OG Title' },
})

expect(result.openGraph?.title).toBe('Custom OG Title')
})

it('should return undefined for metaTitle when no title and no titleSuffix are set', async () => {
const result = await generateMetadata({
serverURL: 'http://localhost:3000',
})

expect(result.title).toBeUndefined()
})

it('should return just the title when no titleSuffix is set', async () => {
const result = await generateMetadata({
serverURL: 'http://localhost:3000',
title: 'My CMS',
})

expect(result.title).toBe('My CMS')
})
})
49 changes: 45 additions & 4 deletions packages/next/src/utilities/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,44 @@ import type { MetaConfig } from 'payload'
import { payloadFaviconDark, payloadFaviconLight, staticOGImage } from '@payloadcms/ui/assets'
import * as qs from 'qs-esm'

const appendTitleSuffix = (
title: Metadata['title'],
suffix: string | undefined,
): Metadata['title'] => {
if (!suffix || !title) {
Comment thread
PatrikKozak marked this conversation as resolved.
return title ?? undefined
}
if (typeof title === 'string') {
return `${title} ${suffix}`
}

if ('default' in title) {
return { default: `${title.default} ${suffix}`, template: `${title.template} ${suffix}` }
}

if ('template' in title) {
return {
absolute: `${title.absolute} ${suffix}`,
template: title.template !== null ? `${title.template} ${suffix}` : null,
}
}

return { absolute: `${title.absolute} ${suffix}` }
}

const getTitleString = (title: Metadata['title']): string | undefined => {
if (!title) {
return undefined
}
if (typeof title === 'string') {
return title
}
if ('absolute' in title) {
return title.absolute
}
return title.default
}

const defaultOpenGraph: Metadata['openGraph'] = {
description:
'Payload is a headless CMS and application framework built with TypeScript, Node.js, and React.',
Expand Down Expand Up @@ -43,11 +81,14 @@ export const generateMetadata = async (
},
] satisfies Array<Icon>)

const metaTitle: Metadata['title'] = [incomingMetadata.title, titleSuffix]
.filter(Boolean)
.join(' ')
const metaTitle: Metadata['title'] = appendTitleSuffix(incomingMetadata.title, titleSuffix)

const titleStringForOg: string | undefined =
typeof incomingMetadata.openGraph?.title === 'string'
? incomingMetadata.openGraph.title
: getTitleString(incomingMetadata.title)

const ogTitle = `${typeof incomingMetadata.openGraph?.title === 'string' ? incomingMetadata.openGraph.title : incomingMetadata.title} ${titleSuffix}`
const ogTitle = [titleStringForOg, titleSuffix].filter(Boolean).join(' ')

const mergedOpenGraph: Metadata['openGraph'] = {
...(defaultOpenGraph || {}),
Expand Down
Loading