Client-side config context undefined in CreateFirstUserClient component (Next.js App Router)
Describe the Bug
Client-side config context undefined in CreateFirstUserClient component (Next.js App Router)
Bug Description
When setting up Payload CMS 3.x with Next.js App Router, the admin panel loads correctly server-side but fails client-side with a configuration context error. The CreateFirstUserClient component cannot access the config context, preventing the first user creation form from functioning.
Error Details
TypeError: Cannot destructure property 'config' of '(0 , _payloadcms_ui__WEBPACK_IMPORTED_MODULE_2__.b)(...)' as it is undefined.
at CreateFirstUserClient (webpack-internal:///(app-pages-browser)/./node_modules/@payloadcms/next/dist/views/CreateFirstUser/index.client.js:19:21)
Speculation on why it's happening
The issue seems to stem from the useConfig() hook returning undefined in the CreateFirstUserClient component. This indicates that the Payload configuration context is not being properly initialized or provided to the client-side components when using Next.js App Router.
This is likely due to the way the configuration is being imported and used in the Next.js App Router setup, which differs from the traditional pages-based routing.
- Server loads Payload config (has database access)
- Server renders HTML with that config
- Browser receives HTML (looks perfect!)
- Browser tries to run JavaScript
- useConfig() hook can't access server's config
- Boom! - "Cannot read property 'config' of undefined"
Environment
- Payload CMS Version: 3.40.0
- Next.js Version: 15.3.3
- React Version: 19.0.0
- Node.js Version: 22.15.1
- Package Manager: npm
- Database: MongoDB (via Docker)
- Deployment: Docker Compose (development)
Current Setup
Directory Structure
my-payload-app/
├── payload.config.ts # Root level config
├── next.config.mjs # withPayload wrapper
├── src/
│ └── app/
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page
│ ├── api/
│ │ └── [[...slug]]/
│ │ └── route.ts # API routes
│ └── (payload)/
│ ├── layout.tsx # Admin layout
│ ├── custom.scss # Admin styles
│ └── admin/
│ ├── importMap.js # Generated import map
│ └── [[...segments]]/
│ └── page.tsx # Admin pages
Key Configuration Files
payload.config.ts
import path from 'path'
import { buildConfig } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
export default buildConfig({
admin: {
user: 'users',
importMap: {
baseDir: path.resolve(process.cwd()),
},
},
collections: [
{
slug: 'users',
auth: true,
fields: [],
},
{
slug: 'pages',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'content',
type: 'richText',
editor: lexicalEditor({}),
},
],
},
],
editor: lexicalEditor({}),
secret: process.env.PAYLOAD_SECRET || '',
typescript: {
outputFile: path.resolve(process.cwd(), 'payload-types.ts'),
},
db: mongooseAdapter({
url: process.env.DATABASE_URI || '',
}),
})
next.config.mjs
import { withPayload } from '@payloadcms/next/withPayload'
import path from 'path'
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
reactCompiler: false
},
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
'@payload-config': path.resolve(process.cwd(), 'payload.config.ts'),
}
return config
}
}
export default withPayload(nextConfig)
src/app/api/[[...slug]]/route.ts
import { REST_DELETE, REST_GET, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
import config from '@payload-config'
export const GET = REST_GET(config)
export const POST = REST_POST(config)
export const DELETE = REST_DELETE(config)
export const PATCH = REST_PATCH(config)
src/app/(payload)/admin/[[...segments]]/page.tsx
import type { Metadata } from 'next'
import config from '@payload-config'
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
type Args = {
params: { segments: string[] }
searchParams: { [key: string]: string | string[] }
}
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })
const Page = ({ params, searchParams }: Args) => {
return RootPage({ config, params, searchParams })
}
export default Page
What's Working
✅ Server-side rendering loads perfectly with Payload branding
✅ API routes work correctly (/api/users returns proper JSON response)
✅ MongoDB connection established
✅ Payload configuration valid (generates types successfully)
✅ Admin panel styling and metadata correct
✅ Import map generated successfully
What's Broken
❌ Client-side configuration context is undefined
❌ useConfig() hook returns undefined in CreateFirstUserClient
❌ First user creation form doesn't render due to config error
Browser Behavior
- Navigate to
/admin→ redirects correctly to/admin/create-first-user - Page loads with proper Payload styling and server-rendered content
- Client-side hydration fails with config context error
- Form fields don't render due to JavaScript error
Server Logs
[WARN]: No email adapter provided. Email will be written to console.
⨯ TypeError: Cannot read properties of undefined (reading 'config')
37 | getEntityConfig,
> 38 | } = useConfig()
Steps to Reproduce
- Create new Next.js 15 app with App Router
- Install Payload CMS 3.x with MongoDB adapter
- Configure admin routes using the structure above
- Start development server
- Navigate to
/adminin browser - Observe client-side config context error
Expected Behavior
The CreateFirstUserClient component should have access to the Payload configuration context and render the user creation form successfully.
Attempted Solutions
- ✅ Added proper API routes - fixed API functionality
- ✅ Generated import map - resolved lexical editor imports
- ❌ Tried wrapping with
ConfigProvider- caused server-side import errors - ❌ Attempted to use
RootLayoutfrom Payload views - import not available - ❌ Various layout configuration approaches - context still undefined
Additional Context
This appears to be a Next.js App Router specific issue with client-side configuration context initialization. The server-side integration works perfectly, but the client-side useConfig() hook cannot access the configuration.
Request
Please provide guidance on the correct way to set up Payload 3.x with Next.js App Router so that client-side components can access the configuration context properly.
Speculation on Why it Fails
- Server loads Payload config (has database access)
- Server renders HTML with that config
- Browser receives HTML (looks perfect!)
- Browser tries to run JavaScript
- useConfig() hook can't access server's config
- Boom! - "Cannot read property 'config' of undefined"
Link to the code that reproduces this issue
https://github.com/PowerAppsDarren/my-payload-app
Reproduction Steps
Steps to Reproduce
- Create new Next.js 15 app with App Router
- Install Payload CMS 3.x with MongoDB adapter
- Configure admin routes using the structure above
- Start development server
- Navigate to
/adminin browser - Observe client-side config context error
Which area(s) are affected? (Select all that apply)
area: core
Environment Info
https://github.com/PowerAppsDarren/my-payload-app/blob/main/docs/2025-05-31_development-session.md
PS Microsoft.PowerShell.Core\FileSystem::\\wsl.localhost\Ubuntu-24.04\home\darren\claude-code\my-payload-app> pnpm payload info
pnpm: The term 'pnpm' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.