Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
88 changes: 88 additions & 0 deletions packages/next/src/utilities/meta.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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 pass a TemplateString title object through unchanged, ignoring titleSuffix', async () => {
const result = await generateMetadata({
serverURL: 'http://localhost:3000',
title: { default: 'My CMS', template: '%s | My CMS' },
titleSuffix: '- Payload',
})

// TemplateString should be preserved as-is — the user has taken control of title formatting
expect(typeof result.title).toBe('object')
expect((result.title as { default: string; template: string }).default).toBe('My CMS')
expect((result.title as { default: string; template: string }).template).toBe('%s | 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 pass a TemplateString with absolute through unchanged for metaTitle', 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')
})

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')
})
})
27 changes: 23 additions & 4 deletions packages/next/src/utilities/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ import type { MetaConfig } from 'payload'
import { payloadFaviconDark, payloadFaviconLight, staticOGImage } from '@payloadcms/ui/assets'
import * as qs from 'qs-esm'

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 +56,17 @@ export const generateMetadata = async (
},
] satisfies Array<Icon>)

const metaTitle: Metadata['title'] = [incomingMetadata.title, titleSuffix]
.filter(Boolean)
.join(' ')
const metaTitle: Metadata['title'] =
typeof incomingMetadata.title === 'object' && incomingMetadata.title !== null
? incomingMetadata.title
Comment thread
PatrikKozak marked this conversation as resolved.
Outdated
: [getTitleString(incomingMetadata.title), titleSuffix].filter(Boolean).join(' ') || undefined

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