Bug: `<Layout>` always throws "Invalid input: expected nonoptional, received undefined → at children"
Describe the bug
<Layout> from nextra-theme-docs throws a Zod validation error on every single render, regardless of how it's used:
Error: ✖ Invalid input: expected nonoptional, received undefined
→ at childrenThis happens because the compiled Layout component destructures children out of its props via object rest spread before validating the remaining props against LayoutPropsSchema — but LayoutPropsSchema.children is a required (non-optional) field. JS rest destructuring always removes the destructured key from the rest object entirely (not just sets it to undefined), and Zod treats "key missing" differently from "key present but undefined" for required fields — so validation always fails.
To Reproduce
Steps to reproduce the behavior:
- Set up a minimal Next.js App Router + Nextra project following the "Get Started" guide.
- Use
<Layout>normally inapp/layout.tsx:<Layout banner={banner} navbar={navbar} pageMap={pageMap} footer={footer} > {children} </Layout> - Run
next dev. - Visit any page (e.g.
/). - See error in terminal/browser overlay.
This reproduces 100% of the time, independent of page content, bundler (webpack/Turbopack), or Next.js version (confirmed on Next.js 15.5.22 and 16.2.12).
Root cause
In nextra-theme-docs/dist/layout.js:
const Layout = (t0) => {
...
({ children, ...themeConfig } = t0);
...
const { data, error } = LayoutPropsSchema.safeParse(themeConfig);
if (error) {
throw z.prettifyError(error);
}
...{ children, ...themeConfig } removes the children key from themeConfig entirely. In nextra-theme-docs/dist/schemas.js:
const LayoutPropsSchema = z.strictObject({
banner: reactNode.optional().meta({...}),
children: reactNode, // <-- required, no `.optional()`
...children has no .optional(). Minimal Zod v4 repro confirming the "missing key" vs "key present but undefined" distinction:
import { z } from 'zod'
const reactNode = z.custom(data => data == null || typeof data === 'string')
const schema = z.strictObject({ children: reactNode })
schema.safeParse({}).error?.issues
// → [{ code: 'invalid_type', expected: 'nonoptional', path: ['children'], message: 'Invalid input: expected nonoptional, received undefined' }]
schema.safeParse({ children: undefined }).error
// → undefined (passes!)Even though reactNode's own custom check explicitly allows null/undefined, that check never runs, because Zod rejects the missing key before reaching the custom validator.
Confirmed this affects both [email protected] and 4.6.1 (identical destructuring pattern in the compiled output of both).
Suggested fix
Either of these fixes the issue:
- Add
.optional()to thechildrenfield inLayoutPropsSchema:const LayoutPropsSchema = z.strictObject({ banner: reactNode.optional().meta({...}), - children: reactNode, + children: reactNode.optional(), - Or don't strip
childrenfrom the object passed to.safeParse()— validate the full props object (includingchildren) instead of the rest-destructuredthemeConfig.
I verified locally that fix #1 resolves the issue and <Layout> renders correctly afterward.
Expected behavior
<Layout> should render normally without throwing a validation error when used with standard props as shown in the official docs.
Screenshots
Desktop (please complete the following information):
- OS: macOS
- Package versions:
next: 16.2.12nextra: 4.6.1nextra-theme-docs: 4.6.1 (also reproduced on 4.5.1)zod: 4.4.3react/react-dom: 19.2.8- Node.js: 24.18.0
Smartphone:
N/A (server-side build/runtime error, not device-specific)
Additional context
Workaround: patching node_modules/nextra-theme-docs/dist/schemas.js locally to change children: reactNode → children: reactNode.optional() (e.g. via patch-package) unblocks local development.
Source: shuding/nextra