0%
balqalam Logo
balqalam
FeaturesCommunityPricingDocumentation
Login
  • Introduction
  • Pitch
  • Hogwarts
  • Live Demo
  • MVP
  • Roadmap
  • Launch Sprint
  • PRD
  • Get Started
  • Localhost
  • Architecture
  • Structure
  • Pattern
  • Page
  • Layout
  • Content
  • Types
  • Config
  • Actions
  • Queries
  • Authorization
  • Validation
  • Form
  • Table
  • Detail
  • Card
  • Util
  • Hooks
  • List Params
  • Views
  • README.md
  • ISSUE.md
  • Technology Stack
  • Database
  • File
  • CDN Assets
  • Entry Points
  • Dashboard
  • Authentication
  • Credentials
  • OAuth
  • Flow Diagrams
  • Multi-Tenancy
  • Offline
  • Onboarding
  • Onboarding Videos
  • Add Values
  • Admission
  • Application
  • Attendance
  • Compliance
  • Profile
  • Exams
  • Exam Wizard
  • Timetable
  • Classrooms
  • Notifications
  • Conference
  • LMS (Lumos)
Finance
  • Finance
  • Fee Management
  • Invoice
  • Wallet
  • Salary
  • Payroll
  • Timesheet
  • Expenses
  • Budget
  • Receipt
  • Accounts
  • Banking
  • Reports
  • Dashboard
  • Permissions
  • Messages
  • Integration Flow
  • Provision
  • AI Document Processing
  • Document Intelligence
  • Internationalization
  • Translation
  • Translation Guide
  • Icons
  • Docs Factory
  • Inspiration
  • Listings
  • Teachers
  • Students
  • Catalog
  • Library
  • Contributing
  • Code of conduct
  • GitHub Workflow
  • Database Seeds
  • Database Safety
  • Test Accounts
  • Playwright
  • Prettier
  • Block Rebound
Sales & GTM
  • Marketing Brief
  • Sales
  • Go-to-market
  • Marketing
  • Admission — Feature Spotlight
  • Pilot Program
  • Leads
  • Proposal
  • Outreach Templates
  • Case Study
  • Competitors
  • Landing-page teardown
  • Competitor FAQ
  • Business model
  • Shared economy
  • Traction
Fundraising & Ecosystem
  • Get Support
  • Investor Deck
  • Data Room
  • Investors
  • Accelerators
  • Incubators
  • Grants
  • Sponsors
  • Partners
  • Competitions & Hackathons
  • Universities & Training Centers

Layout

PreviousNext

Canonical layout.tsx patterns in Next.js 16 — when to make them async, what providers go where, and what to keep out.

Layouts span many route groups. Layouts are for shared UI and providers — never DB queries, never redundant auth, never inline translations. The deepest chain is 5 levels: root → [lang] → school-dashboard → feature → sub-feature.

The 7 rules

#Rule
1export default [async] function Layout() — never arrow functions
2import type for type-only imports
3Layouts are for shared UI and providers only — no DB queries, no business logic
4Never duplicate parent layout work (auth, school lookup, etc.)
5Use the dictionary for all text — never inline isArabic ? ... : ...
6Keep server layouts under ~60 lines; extract skeletons and error UI
7Use Promise.all for parallel async ops

Param shapes (3 total)

ShapeTypeUsed by
None{ children }Root, passthrough, auth, onboarding, application
Lang only{ lang: string }[lang], saas-dashboard, saas-marketing, docs, catalog
Lang + subdomain{ lang: string; subdomain: string }school-dashboard, school-marketing, feature navs

Async vs sync

Layout typeAsync?
RootSync
LocaleAsync — params, auth, dictionary
DashboardAsync — params, auth, school lookup
Feature navAsync — params, dictionary
Client (onboarding, apply)Sync — uses hooks
PassthroughSync

Provider hierarchy

LayoutProviders
RootNone — html/body only
LocaleDirectionProvider, SessionProvider, NuqsAdapter, ThemeProvider, UserThemeProvider, AnalyticsProvider, ServiceWorkerProvider, Toaster
School DashboardSchoolProvider, SidebarProvider, ModalProvider, PageHeadingProvider
SaaS DashboardSidebarProvider, ModalProvider, PageHeadingProvider
School MarketingNone (props pass school to header)
SaaS MarketingNone
DocsSidebarProvider
AuthNone
OnboardingListingProvider, HostValidationProvider
ApplyApplySessionProvider, ApplyValidationProvider

Layouts cannot pass data to children directly — use providers, or let pages re-fetch (React cache deduplicates).

Template A — Root

Only layout with <html> and <body>. Inline script sets lang/dir before hydration.

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html suppressHydrationWarning>
      <head>
        <script
          dangerouslySetInnerHTML={{
            __html: `(function(){var m=window.location.pathname.match(/^\\/(en|ar)/);var l=m?m[1]:'ar';document.documentElement.lang=l;document.documentElement.dir=l==='ar'?'rtl':'ltr'})()`,
          }}
        />
      </head>
      <body>{children}</body>
    </html>
  )
}

Template B — Locale

Provider hub. Async — needs params, auth, dictionary.

export default async function LocaleLayout({ children, params }: Props) {
  const { lang } = await params
  const session = await auth()
  return (
    <DirectionProvider direction={lang === "ar" ? "rtl" : "ltr"} lang={lang}>
      <SessionProvider session={session}>
        <NuqsAdapter>
          <ThemeProvider>
            <UserThemeProvider>{children}</UserThemeProvider>
          </ThemeProvider>
        </NuqsAdapter>
      </SessionProvider>
    </DirectionProvider>
  )
}
 
export function generateStaticParams() {
  return i18n.locales.map((lang) => ({ lang }))
}

The real layout wraps 8 providers; the example shows the essential 5 for clarity.

Template C — Dashboard

force-dynamic. Auth and school fetched in parallel.

export const dynamic = "force-dynamic"
 
export default async function PlatformLayout({ children, params }: Props) {
  const { subdomain, lang } = await params
  const [result, session] = await Promise.all([
    getSchoolBySubdomain(subdomain),
    auth(),
  ])
  if (!result.success || !result.data) notFound()
 
  return (
    <SchoolProvider school={result.data}>
      <SidebarProvider>
        <ModalProvider>
          <PageHeadingProvider>{children}</PageHeadingProvider>
        </ModalProvider>
      </SidebarProvider>
    </SchoolProvider>
  )
}

Child layouts must NOT re-call auth() or getSchoolBySubdomain().

Template D — Feature nav (~35 layouts)

The most common pattern. Dictionary + PageHeadingSetter + PageNav.

export default async function AttendanceLayout({ children, params }: Props) {
  const { lang } = await params
  const dictionary = await getDictionary(lang as Locale)
  const d = dictionary?.school?.attendance
 
  const pages: PageNavItem[] = [
    { name: d?.manual ?? "Mark", href: `/${lang}/attendance` },
    {
      name: d?.analytics ?? "Analytics",
      href: `/${lang}/attendance/analytics`,
    },
    { name: d?.reports ?? "Reports", href: `/${lang}/attendance/reports` },
    { name: d?.settings ?? "Settings", href: `/${lang}/attendance/settings` },
  ]
 
  return (
    <div className="space-y-6">
      <PageHeadingSetter title={d?.title ?? "Attendance"} />
      <PageNav pages={pages} />
      {children}
    </div>
  )
}

Template E — Passthrough

Sync. <>{children}</> for route grouping. Six layouts use this; consider whether the route group is even needed.

export default function ListingsLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <>{children}</>
}

Anti-patterns found

Anti-patternWhereFix
DB query in layoutcatalog/(catalog)/layout.tsx runs db.catalogQuestion.count()Move to the page that needs the count
Redundant auth()finance/banking/layout.tsxTrust the parent dashboard layout
Inline isArabic translationsschool/layout.tsx, subjects/(browse)/layout.tsxUse dictionary?.section?.key
113-line client layoutonboarding/[id]/layout.tsxExtract <LayoutSkeleton />, <LayoutError />
151-line client layoutapply/[id]/layout.tsxSame
Arrow function export(auth)/layout.tsxexport default function AuthLayout
Unused paramsprofile/layout.tsx declares but never usesDrop the params

Why not LayoutProps<'/route'>

Next.js 16 ships a global LayoutProps<'/route'> helper. Skipped because route literals are very long ('/[lang]/s/[subdomain]/(school-dashboard)/finance/banking'), only 3 param shapes exist, and the helper requires a next typegen step. Inline interface Props is more readable.

Behavioral facts

FactImplication
Layouts don't re-render on soft navigationState persists; only page.tsx and loading.tsx swap
Layouts can't access searchParamsOnly pages receive them
Layouts can't access pathnameUse usePathname() in a client child
error.tsx renders inside its layoutTo catch a layout error, place error.tsx in the parent segment
loading.tsx wraps children, not the layoutSuspense boundary inside the layout
Root layout requires <html> and <body>No other layout should have them
Root layout must not have manual <head> tagsUse metadata / generateMetadata (inline scripts excepted)

Next.js 16 reference

  • params is a Promise in layouts — synchronous access removed.
  • Layouts persist across soft navigation.
  • Use template.tsx if you need remount on navigation.
  • Turbopack is the default bundler.
  • React Compiler is available (reactCompiler: true) but not enabled.

See also

  • Pattern
  • Page
  • Multi-Tenancy
PageContent

On This Page

The 7 rulesParam shapes (3 total)Async vs syncProvider hierarchyTemplate A — RootTemplate B — LocaleTemplate C — DashboardTemplate D — Feature nav (~35 layouts)Template E — PassthroughAnti-patterns foundWhy not LayoutProps<'/route'>Behavioral factsNext.js 16 referenceSee also

Built by Databayt ·

Welcome to balqalam.

A great journey is about to begin.