Safe layout server component
API reference for the safe App Router layout factory — segments, children, optional parallel slots, and authorization.
createSafeLayoutServerComponent wraps a default-exported layout Server Component. It validates dynamic segments, passes children (and optional parallel route slots), supports authorization, and aligns with Next.js error boundaries. Import from @sugardarius/anzen/server-components.
Layouts do not receive searchParams in this factory (same as the page vs layout split in the underlying API).
Import
importfrom '@sugardarius/anzen/server-components'Signature
function createSafeLayoutServerComponent<
AC = undefined,
TSegments = undefined,
TSlots extends readonly string| undefined = undefined,
>(
options: CreateSafeLayoutServerComponentOptions<AC, TSegments, TSlots>,
layout:
ctx: SafeLayoutServerComponentContext<AC, TSegments, TSlots>
=> Promise<React.ReactElement>
):props: LayoutProvidedProps<TSlots>) => Promise<React.ReactElement>LayoutProvidedProps includes params, children, and when experimental_slots is set, named slot props matching your slot names.
Options
When creating a safe layout server component you can use a bunch of options for helping you achieve different tasks 👇🏻
id
id?: string
Used for logging in development or when the debug option is enabled. You can also use it to add extra logging or monitoring.
By default the id is set to [unknown:layout:server:component].
export default createSafeLayoutServerComponent
'races/[id]/layout',
asyncid=>
returndiv>Layout {id}</div>
onError
onError?: (err: unknown) => Awaitable<never>
Callback triggered when the layout server component throws an unhandled error.
By default it rethrows the error as a service hatch to Next.js do its job and use error boundaries. The error is logged into the console.
Use it if you want to manage unexpected errors properly to log, trace or define behaviors like using notFound or redirect.
importfrom 'next/navigation'
importfrom '@sugardarius/anzen/server-components'
export default createSafeLayoutServerComponent
onError: asyncerr: unknown): Promise<never> =>
ifinstanceof NotFoundError
notFound
throw
async=>
returndiv>Hello</div>
debug
debug?: boolean
Use this options to enable debug mode. It will add logs in the layout server component to help you debug the request.
By default it's set to false for production builds.
In development builds, it will be true if NODE_ENV is not set to production.
importfrom '@sugardarius/anzen/server-components'
export default createSafeLayoutServerComponenttrueasync=>
returndiv>Hello</div>
})Layout server components support the same options as page server components except searchParams and onSearchParamsValidationError. Additionally, layout server components receive children in their context.
authorize
authorize?: LayoutAuthFunction<AC, TSegments>
Function to use to authorize the layout server component. By default it always authorize the server component.
Return never (throws an error, notFound, forbidden, unauthorized, or redirect) when the request to the server component is not authorized.
The authorize function receives validated attributes (segments) when they are defined, allowing you to use validated data for authorization logic.
Parameters:
id: string- Server component IDsegments?:- Validated route dynamic segments (ifsegmentsoption is defined)
importfrom 'zod'
importfrom '@sugardarius/anzen/server-components'
importfrom '~/lib/auth'
importfrom 'next/navigation'
export default createSafeLayoutServerComponent
string
authorize: asyncsegments=>
const session = awaitgetSession
if!session) {
unauthorized
const hasAccess = await checkAccountAccess
if!hasAccess) {
notFound
return
asyncauth, segments, children=>
return
div>
header>Account: {segments.accountId}</header>
div>
You can configure layout server component options' validation using a validation library dynamic route segments 👇🏻
authorize
authorize?: LayoutAuthFunction<AC, TSegments>
Function to use to authorize the layout server component. By default it always authorize the layout server component.
Return never (throws an error, notFound, forbidden, unauthorized, or redirect) when the request to the server component is not authorized.
The authorize function receives validated attribute (segments) when they are defined, allowing you to use validated data for authorization logic.
Parameters:
id: string- Server component IDsegments?:- Validated route dynamic segments (ifsegmentsoption is defined)
Basic authorization
importfrom '@sugardarius/anzen/server-components'
importfrom '~/lib/auth'
importfrom 'next/navigation'
export default createSafeLayoutServerComponent
authorize: async=>
const session = awaitgetSession
if!session) {
unauthorized
return
asyncauth=>
returndiv>Hello {auth.user.name}!</div>
Authorization with validated segments
importfrom 'zod'
importfrom '@sugardarius/anzen/server-components'
importfrom '~/lib/auth'
importfrom 'next/navigation'
export default createSafeLayoutServerComponent
string
string
authorize: asyncsegments=>
// segments are already validated at this point
const session = awaitgetSession
if!session) {
throw new Error'Unauthorized'
// Check if user has access to this account
const hasAccess = await checkAccountAccess
if!hasAccess) {
notFound
return
asyncauth, segments=>
return
div>
div>
segments
segments?: TSegments
Dynamic route segments used for the layout server component path. By design it will handle automatically if the segments are a Promise or not.
Please note the expected input is a StandardSchemaDictionary.
importfrom 'zod'
importfrom '@sugardarius/anzen/server-components'
export default createSafeLayoutServerComponent
string
stringoptional
asyncsegments=>
return
div>
div>
onSegmentsValidationError
onSegmentsValidationError?: OnValidationError
Callback triggered when dynamic segments validations returned issues. By default it throws a ValidationError and issues are logged into the console.
importfrom 'zod'
importfrom '@sugardarius/anzen/server-components'
importfrom 'next/navigation'
export default createSafeLayoutServerComponent
string
stringoptional
onSegmentsValidationError: asyncissues) =>
error'Invalid segments', issues)
notFound
asyncsegments=>
return
div>
div>
experimental_slots 🧪
experimental_slots?: string[]
Slots used in the layout when using Next.js parallel routes (experimental).
When defined, the slots are provided in the context as experimental_slots with the slot names as keys and React.ReactNode as values. The factory validates that all expected slots are provided and throws a MissingLayoutSlotsError if any slots are missing.
importfrom '@sugardarius/anzen/server-components'
export default createSafeLayoutServerComponent
'analytics', 'team'as const,
// ^^^^^👆🏻
// Required for proper type inference
asyncchildren, experimental_slots=>
return
div>
aside>
??
div>Analytics slot: No matching route</div>
aside>
main>{children}</main>
footer>
??div>Team slot: No matching route</div>}
footer>
div>
Using children in layout server components
The children prop is automatically provided in the context for layout server components.
importfrom '@sugardarius/anzen/server-components'
export default createSafeLayoutServerComponentasyncchildren=>
return
div>
header>Header</header>
main>{children}</main>
footer>Footer</footer>
div>
})Handler context
SafeLayoutServerComponentContext is the context object that is passed to the handler.
| Field | When |
|---|---|
id | Always. |
children | Always — page content; may be a fragment when no child page exists. |
auth | When authorize returned a value. |
segments | When segments was configured. |
experimental_slots | When experimental_slots was configured. Empty object when no matching parallel routes. |
Error handling
By design the factory will catch any error thrown in the layout server component and rethrow it by default to let Next.js handle it with error boundaries.
You can customize the error handling if you want to fine tune error management.
importfrom '@sugardarius/anzen/server-components'
importfrom '~/lib/errors'
importfrom '~/lib/db'
importfrom 'next/navigation'
export default createSafeLayoutServerComponent
onError: asyncerr: unknown): Promise<never> =>
// Take it as an example not as a real use case 😅
ifinstanceof NotFoundError
notFound
else ifinstanceof DbUnknownError
error'Database error', err)
throw
throw
asyncchildren=>
constdata, err] = awaitfindUnique'liveblocks'
if
throw new DbUnknownError500
if=== null
throw new NotFoundError
return
main>
header>{data.name}</header>
main>
Synchronous Validation
Validation must be synchronous. The Standard Schema contract discourages async validation. If a schema resolves validation asynchronously, behavior is undefined and may throw at runtime.
Fair use note
Please note that if you're not using any of the proposed options createSafeLayoutServerComponent it means you're surely don't need it.
// Calling 👇🏻
export default createSafeLayoutServerComponentasyncchildren=>
returndiv>{children}</div>
})
// is equal to declare the layout server component this way 👇🏻
export default async function Layout({
children,
}:
children: React.ReactNode
}) {
returndiv>{children}</div>
}See also
Last updated on