Layouts
The three segments are three directories, and any of them can have a layout.
Which ones should, comes down to one question: where does <html> go?
Where the root layout goes
Next renders <html> in the root layout, which is the topmost layout in the
tree, and a layout below it cannot reach up to add an attribute. So an app
whose theme is read from <html data-theme> — as the FairGarden design
system does — puts the root layout below the preferences segment, and the
simplest place is below all three:
// app/[locale]/[prefs]/[flags]/layout.tsx
export const generateStaticParams = indicators.generateStaticParams
export default indicators.layout(({ children, locale, prefs, flags }) => (
<html lang={locale} data-theme={prefs.theme}>
<body>{children}</body>
</html>
))
There is then no layout at [locale] or [prefs], and nothing is lost:
generateStaticParams covers all three segments from here, and every page
below has locale, prefs and flags in its params.
An app whose theme lives on an element inside <body> can keep a layout per
level instead:
// app/[locale]/layout.tsx
export const generateStaticParams = indicators.locale.generateStaticParams
export default indicators.locale.layout(({ children, locale }) => (
<html lang={locale}><body>{children}</body></html>
))
// app/[locale]/[prefs]/layout.tsx
export const generateStaticParams = indicators.prefs.generateStaticParams
export default indicators.prefs.layout(({ children, prefs }) => (
<div data-theme={prefs.theme}>{children}</div>
))
// app/[locale]/[prefs]/[flags]/layout.tsx
export const generateStaticParams = indicators.flags.generateStaticParams
export default indicators.flags.layout(({ children, flags }) => (
<>{flags.lang && <LanguageBanner suggests={flags.lang} />}{children}</>
))
Reading without the wrapper
The wrappers are a convenience over read, which any
layout or page can call itself:
export default async function Page({ params }: PageProps<'/[locale]/[prefs]/[flags]/login'>) {
const { locale, prefs, flags } = await indicators.read(params)
// ...
}
read accepts the params promise Next passes, or an already resolved
object. indicators.locale.read, .prefs.read and .flags.read read one
segment each, for a layout that only has that segment in its params.
Not found
A segment the config cannot decode is a 404. That covers an unsupported
locale, an unknown key or value, and a segment that is not in canonical
form — tz~EST.theme~dark, say, when theme~dark.tz~EST is the one the
rewrites produce. Accepting the other spelling would mean two cache entries
for one page; refusing it means a crafted URL cannot make one.
read calls Next's notFound() for these, so the nearest not-found.tsx
renders. Thrown from the root layout, that is the app's global one.
Client components
Nothing has to be provided from a layout. A client component reads the
same params through useParams, and the hooks from
createNavigation decode them:
const { locale, prefs, flags } = useIndicators()
const [theme, setTheme] = usePref('theme')