Layouts and Components
First complete Add Another Page in the full-stack quick start and confirm /admin/dashboard displays Total users: 42. It supplies the page file and HTTP route required here; Project Structure explains file responsibilities but does not provide this complete prerequisite. Continue here to add root and admin layouts and a reusable menu. See Routing and Pages for res.render basics.
Create components/AdminShell.tsx and pages/admin/layout.tsx below. If root pages/layout.tsx already exists, replace it with the root layout example while retaining any required style imports. The root layout affects every page using automatic layouts; the admin layout affects pages in that directory. Keep the original admin/dashboard.tsx page and replace only the route's render call.
Table of Contents
- Automatic Layout Chain
- Explicit Layout Selection
- Layout Data Shape
- Reusable Shells
- Shared Components
- SSR-safe Components
- Verify Layouts
Automatic Layout Chain
Vext layouts are components named layout under the page directory; layout.tsx is the default form used here.
For res.render("admin/dashboard"), the automatic chain runs from the root layout to the admin layout and then the page. Layout IDs are directories relative to the page root: "." for the root and "admin" for the admin layout, not "layout" or "admin/layout".
Use layouts for stable page shells: nav, sidebars, account menus, breadcrumbs, and admin chrome.
Explicit Layout Selection
frontend.render.layout sets the global layout default. An explicit options.layout in the third render argument takes priority: false disables layouts and true restores the automatic chain. This applies to buffered, streaming, and page envelopes.
Use explicit layout selection when two routes in different directories share the same shell, or when an error page should use a minimal shell.
The call above is an option snippet inside a handler; the application provides props. An unregistered layout ID is filtered out. It neither creates a layout nor reports “layout missing,” so inspect the final page to confirm the expected shell appears.
Layout Data Shape
Pass layout data through the third render argument. This replaces the render call in the existing admin/dashboard handler; the handler defines these values locally, while a real application may obtain them from services.
Keep layout data small and serializable. Prefer IDs, labels, URLs, and permission flags over raw ORM records.
Each layout reads the data under its ID through props.data; page props are not merged into the layout automatically. For example, the root layout can be:
Reusable Shells
When several layouts share UI, move the UI to src/frontend/components/**.
Then import it from a layout:
Shared Components
Shared components should be browser-safe. They may receive server-prepared props, but they should not import services or Node-only modules.
A component used by only one page can live in that page file. Place a separate component file under src/frontend/components/**. The page directory scans files with matching extensions as pages; do not treat it as an arbitrary component directory.
SSR-safe Components
The first render runs on the server and then hydrates in the browser. Avoid first-render differences:
- Do not call
Date.now(),Math.random(), or browser-only APIs during initial render. - Read language from
useVextI18n()or server-provided props. - Read request-specific data from props or
layoutData. - Move browser-only work into
useEffect.
This keeps SSR HTML and browser hydration consistent.
Verify Layouts
Run npm run build, then npm start -- --port 3000 after success. Open /admin/dashboard and check for Ada in the root layout, the admin menu link, and the original page content. Set that res.render call to layout: false, rebuild and restart, then confirm the shells disappear but page content remains. Restore the option and stop the verification service. If menu data does not appear, check the layoutData key and layout props.data, then confirm that layout was selected.
For interaction consistency, see Hydration Validation. See Data Flow for server data boundaries.