Skip to content

Should not warn metadataBase missing if only absolute urls are present #61898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 15, 2024
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
87 changes: 62 additions & 25 deletions packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import {
resolveAbsoluteUrlWithPathname,
} from './resolve-url'
import { resolveTitle } from './resolve-title'
import { isFullStringUrl } from '../../url'
import { warnOnce } from '../../../build/output/log'

type FlattenArray<T> = T extends (infer U)[] ? U : T
type ResolvedMetadataBase = ResolvedMetadata['metadataBase']

const OgTypeFields = {
article: ['authors', 'tags'],
Expand All @@ -34,41 +39,58 @@ const OgTypeFields = {
],
} as const

function resolveAndValidateImage(
item: FlattenArray<OpenGraph['images'] | Twitter['images']>,
metadataBase: NonNullable<ResolvedMetadataBase>,
isMetadataBaseMissing: boolean
) {
if (!item) return undefined
const isItemUrl = isStringOrURL(item)
const inputUrl = isItemUrl ? item : item.url
if (!inputUrl) return undefined

validateResolvedImageUrl(inputUrl, metadataBase, isMetadataBaseMissing)

return isItemUrl
? {
url: resolveUrl(inputUrl, metadataBase),
}
: {
...item,
// Update image descriptor url
url: resolveUrl(inputUrl, metadataBase),
}
}

export function resolveImages(
images: Twitter['images'],
metadataBase: ResolvedMetadata['metadataBase']
metadataBase: ResolvedMetadataBase
): NonNullable<ResolvedMetadata['twitter']>['images']
export function resolveImages(
images: OpenGraph['images'],
metadataBase: ResolvedMetadata['metadataBase']
metadataBase: ResolvedMetadataBase
): NonNullable<ResolvedMetadata['openGraph']>['images']
export function resolveImages(
images: OpenGraph['images'] | Twitter['images'],
metadataBase: ResolvedMetadata['metadataBase']
metadataBase: ResolvedMetadataBase
):
| NonNullable<ResolvedMetadata['twitter']>['images']
| NonNullable<ResolvedMetadata['openGraph']>['images'] {
const resolvedImages = resolveAsArrayOrUndefined(images)
if (!resolvedImages) return resolvedImages

const { isMetadataBaseMissing, fallbackMetadataBase } =
getSocialImageFallbackMetadataBase(metadataBase)
const nonNullableImages = []
for (const item of resolvedImages) {
if (!item) continue
const isItemUrl = isStringOrURL(item)
const inputUrl = isItemUrl ? item : item.url
if (!inputUrl) continue

nonNullableImages.push(
isItemUrl
? {
url: resolveUrl(item, metadataBase),
}
: {
...item,
// Update image descriptor url
url: resolveUrl(item.url, metadataBase),
}
const resolvedItem = resolveAndValidateImage(
item,
fallbackMetadataBase,
isMetadataBaseMissing
)
if (!resolvedItem) continue

nonNullableImages.push(resolvedItem)
}

return nonNullableImages
Expand All @@ -94,9 +116,26 @@ function getFieldsByOgType(ogType: OpenGraphType | undefined) {
}
}

function validateResolvedImageUrl(
inputUrl: string | URL,
fallbackMetadataBase: NonNullable<ResolvedMetadataBase>,
isMetadataBaseMissing: boolean
): void {
// Only warn on the image url that needs to be resolved with metadataBase
if (
typeof inputUrl === 'string' &&
!isFullStringUrl(inputUrl) &&
isMetadataBaseMissing
) {
warnOnce(
`metadataBase property in metadata export is not set for resolving social open graph or twitter images, using "${fallbackMetadataBase.origin}". See https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadatabase`
)
}
}

export const resolveOpenGraph: FieldResolverExtraArgs<
'openGraph',
[ResolvedMetadata['metadataBase'], MetadataContext, string | null]
[ResolvedMetadataBase, MetadataContext, string | null]
> = (openGraph, metadataBase, metadataContext, titleTemplate) => {
if (!openGraph) return null

Expand All @@ -114,9 +153,7 @@ export const resolveOpenGraph: FieldResolverExtraArgs<
}
}
}

const imageMetadataBase = getSocialImageFallbackMetadataBase(metadataBase)
target.images = resolveImages(og.images, imageMetadataBase)
target.images = resolveImages(og.images, metadataBase)
}

const resolved = {
Expand Down Expand Up @@ -146,7 +183,7 @@ const TwitterBasicInfoKeys = [

export const resolveTwitter: FieldResolverExtraArgs<
'twitter',
[ResolvedMetadata['metadataBase'], string | null]
[ResolvedMetadataBase, string | null]
> = (twitter, metadataBase, titleTemplate) => {
if (!twitter) return null
let card = 'card' in twitter ? twitter.card : undefined
Expand All @@ -157,8 +194,8 @@ export const resolveTwitter: FieldResolverExtraArgs<
for (const infoKey of TwitterBasicInfoKeys) {
resolved[infoKey] = twitter[infoKey] || null
}
const imageMetadataBase = getSocialImageFallbackMetadataBase(metadataBase)
resolved.images = resolveImages(twitter.images, imageMetadataBase)

resolved.images = resolveImages(twitter.images, metadataBase)

card = card || (resolved.images?.length ? 'summary_large_image' : 'summary')
resolved.card = card
Expand Down
24 changes: 10 additions & 14 deletions packages/next/src/lib/metadata/resolvers/resolve-url.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import path from '../../../shared/lib/isomorphic/path'
import * as Log from '../../../build/output/log'
import type { MetadataContext } from '../types/resolvers'

function isStringOrURL(icon: any): icon is string | URL {
Expand All @@ -12,34 +11,31 @@ function createLocalMetadataBase() {

// For deployment url for metadata routes, prefer to use the deployment url if possible
// as these routes are unique to the deployments url.
export function getSocialImageFallbackMetadataBase(
metadataBase: URL | null
): URL | null {
export function getSocialImageFallbackMetadataBase(metadataBase: URL | null): {
fallbackMetadataBase: URL
isMetadataBaseMissing: boolean
} {
const isMetadataBaseMissing = !metadataBase
const defaultMetadataBase = createLocalMetadataBase()
const deploymentUrl =
process.env.VERCEL_URL && new URL(`https://${process.env.VERCEL_URL}`)

let fallbackMetadata
let fallbackMetadataBase
if (process.env.NODE_ENV === 'development') {
fallbackMetadata = defaultMetadataBase
fallbackMetadataBase = defaultMetadataBase
} else {
fallbackMetadata =
fallbackMetadataBase =
process.env.NODE_ENV === 'production' &&
deploymentUrl &&
process.env.VERCEL_ENV === 'preview'
? deploymentUrl
: metadataBase || deploymentUrl || defaultMetadataBase
}

if (isMetadataBaseMissing) {
Log.warnOnce('')
Log.warnOnce(
`metadata.metadataBase is not set for resolving social open graph or twitter images, using "${fallbackMetadata.origin}". See https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadatabase`
)
return {
fallbackMetadataBase,
isMetadataBaseMissing,
}

return fallbackMetadata
}

function resolveUrl(url: null | undefined, metadataBase: URL | null): null
Expand Down
8 changes: 7 additions & 1 deletion packages/next/src/lib/url.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
const DUMMY_ORIGIN = 'http://n'

function getUrlWithoutHost(url: string) {
return new URL(url, 'http://n')
return new URL(url, DUMMY_ORIGIN)
}

export function getPathname(url: string) {
return getUrlWithoutHost(url).pathname
}

export function isFullStringUrl(url: string) {
return /https?:\/\//.test(url)
}
9 changes: 0 additions & 9 deletions test/e2e/app-dir/metadata-missing-metadata-base/app/page.js

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function Page() {
return 'absolute url og page'
}

export const metadata = {
openGraph: {
images: 'https://repository-images.githubusercontent.com/123',
},
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export default function Layout({ children }) {
return (
<html>
<head></head>
<body>{children}</body>
</html>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return 'page'
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
import { createNextDescribe } from 'e2e-utils'
import { fetchViaHTTP } from 'next-test-utils'

const METADATA_BASE_WARN_STRING =
'metadataBase property in metadata export is not set for resolving social open graph or twitter images,'

createNextDescribe(
'app dir - metadata missing metadataBase',
{
files: __dirname,
skipDeployment: true,
},
({ next, isNextStart }) => {
({ next, isNextDev }) => {
// If it's start mode, we get the whole logs since they're from build process.
// If it's dev mode, we get the logs after request
function getCliOutput(logStartPosition: number) {
return isNextStart
? next.cliOutput
: next.cliOutput.slice(logStartPosition)
return isNextDev ? next.cliOutput.slice(logStartPosition) : next.cliOutput
}

if (isNextDev) {
it('should not warn metadataBase is missing if there is only absolute url', async () => {
const logStartPosition = next.cliOutput.length
await fetchViaHTTP(next.url, '/absolute-url-og')
const output = getCliOutput(logStartPosition)
expect(output).not.toInclude(METADATA_BASE_WARN_STRING)
})
}

it('should fallback to localhost if metadataBase is missing for absolute urls resolving', async () => {
const logStartPosition = next.cliOutput.length
await fetchViaHTTP(next.url, '/')
//
await fetchViaHTTP(next.url, '/og-image-convention')
const output = getCliOutput(logStartPosition)
expect(output).toInclude(
'metadata.metadataBase is not set for resolving social open graph or twitter images,'
)
expect(output).toInclude(METADATA_BASE_WARN_STRING)
expect(output).toMatch(/using "http:\/\/localhost:\d+/)
expect(output).toInclude(
'. See https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadatabase'
Expand Down