payloadcms / payload

Payload is the open-source, fullstack Next.js framework, giving you instant backend superpowers. Get a full TypeScript backend and admin panel instantly. Use Payload as a headless CMS or for building powerful applications.
https://payloadcms.com
MIT License
25.77k stars 1.64k forks source link

Build failure persists with `params` as Promise in Next.js App Router, possibly related to locale handling #8711

Closed danielkoller closed 1 month ago

danielkoller commented 1 month ago

Link to reproduction

No response

Environment Info

Payload: 3.0.0-beta.113 Node.js: 22.9.0 Next.js: 15.0.0-canary.173

Describe the Bug

Despite implementing the suggested changes from issue #8463, the build process continues to fail when using Payload CMS with Next.js App Router. The problem seems to be related to how params is being passed to page components and metadata generation functions. I suspect that the handling of the locale parameter might be contributing to this issue.

When trying to run the build, I get this error ->

Type error: Type '{ params: { locale: string; }; }' does not satisfy the constraint 'PageProps'. Types of property 'params' are incompatible. Type '{ locale: string; }' is missing the following properties from type 'Promise<SegmentParams>': then, catch, finally, [Symbol.toStringTag]

Here is my localized [slug]-page handling the pages created via Payload CMS.

import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { PayloadRedirects } from '@/components/PayloadRedirects'
import configPromise from '@payload-config'
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { draftMode } from 'next/headers'
import React, { cache } from 'react'
import type { Page as PageType } from 'src/payload-types.ts'
import { RenderBlocks } from '@/blocks/RenderBlocks'

import { generateMeta } from '@/utilities/generateMeta'

type Args = {
  params: Promise<{
    slug?: string
    locale: string
  }>
}

export default async function Page({ params }: Args) {
  const { slug = 'home', locale } = await params
  const url = `/${locale}/${slug}`
  const { isEnabled: isDraftMode } = await draftMode()

  let page: PageType | null

  page = await queryPageBySlug({
    slug,
    locale,
    isDraftMode,
  })

  if (!page || (locale === 'en' && page._status !== 'published' && !isDraftMode)) {
    return notFound()
  }

  return (
    <article className="container pb-24">
      <PayloadRedirects disableNotFound url={url} />
      <RenderBlocks blocks={page.layout} />
    </article>
  )
}

export async function generateMetadata({ params }: Args): Promise<Metadata> {
  const { slug = 'home', locale } = await params
  const { isEnabled: isDraftMode } = await draftMode()
  const page = await queryPageBySlug({
    slug,
    locale,
    isDraftMode,
  })

  if (!page || (locale === 'en' && page._status !== 'published' && !isDraftMode)) {
    return {}
  }

  return generateMeta({ doc: page })
}

const queryPageBySlug = cache(
  async ({ slug, locale, isDraftMode }: { slug: string; locale: string; isDraftMode: boolean }) => {
    const payload = await getPayloadHMR({ config: configPromise })

    const page = await payload.find({
      collection: 'pages',
      where: {
        slug: {
          equals: slug,
        },
        ...(locale === 'en' && !isDraftMode ? { _status: { equals: 'published' } } : {}),
      },
      limit: 1,
      depth: 2,
      locale: locale as 'en' | 'de',
      draft: isDraftMode,
    })

    return page.docs[0]
  },
)

Reproduction Steps

  1. Set up a Next.js project with App Router and Payload CMS integration.

  2. Implement internationalization (i18n) in your Next.js app with Next-Intl.

  3. Create a dynamic route file for handling pages created via Payload CMS, e.g., app/[locale]/(frontend)[slug]/page.tsx.

  4. In this file, implement the page component and metadata generation as follows:

    import type { Metadata } from 'next'
    import { notFound } from 'next/navigation'
    import { PayloadRedirects } from '@/components/PayloadRedirects'
    import configPromise from '@payload-config'
    import { getPayloadHMR } from '@payloadcms/next/utilities'
    import { draftMode } from 'next/headers'
    import React, { cache } from 'react'
    import type { Page as PageType } from 'src/payload-types.ts'
    import { RenderBlocks } from '@/blocks/RenderBlocks'
    import { generateMeta } from '@/utilities/generateMeta'
    
    type Args = {
     params: Promise<{
       slug?: string
       locale: string
     }>
    }
    
    export default async function Page({ params }: Args) {
     const { slug = 'home', locale } = await params
     // ... rest of the component
    }
    
    export async function generateMetadata({ params }: Args): Promise<Metadata> {
     const { slug = 'home', locale } = await params
     // ... rest of the function
    }
    
    // ... queryPageBySlug function
  5. Ensure that your next.config.js is properly configured for i18n and Payload CMS.

import { withPayload } from '@payloadcms/next/withPayload'
import createNextIntlPlugin from 'next-intl/plugin'
import { withSentryConfig } from '@sentry/nextjs'
import redirects from './redirects.js'

const withNextIntl = createNextIntlPlugin()

const NEXT_PUBLIC_SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3000'

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack(config) {
    config.module.rules.push({
      test: /\.svg$/i,
      use: ['@svgr/webpack'],
    })

    return config
  },
  images: {
    remotePatterns: [
      ...[NEXT_PUBLIC_SERVER_URL].map((item) => {
        const url = new URL(item)
        return {
          hostname: url.hostname,
          protocol: url.protocol.replace(':', ''),
        }
      }),
    ],
  },
  reactStrictMode: true,
  redirects,
}

// Combine withNextIntl, withPayload, and withSentryConfig
const combinedConfig = withSentryConfig(withNextIntl(withPayload(nextConfig)), {
  // Sentry config options
  org: 'organization-name',
  project: 'project-name',
  silent: !process.env.CI,
  widenClientFileUpload: true,
  reactComponentAnnotation: {
    enabled: true,
  },
  tunnelRoute: '/monitoring',
  hideSourceMaps: true,
  disableLogger: true,
  automaticVercelMonitors: true,
})

export default combinedConfig
  1. Run next build to start the build process. It fails with this error ->
Type error: Type '{ params: { locale: string; }; }' does not satisfy the constraint 'PageProps'.
  Types of property 'params' are incompatible.
    Type '{ locale: string; }' is missing the following properties from type 'Promise<SegmentParams>': then, catch, finally, [Symbol.toStringTag]

Adapters and Plugins

No response

jmikrut commented 1 month ago

Hi @danielkoller — this is an issue with your project's TypeScript (and Next.js) and not related to Payload itself. We try and retain our GitHub issues to only be used for bugs / issues with Payload itself, so I will convert this to a Q&A Discussion.