DocsGo deeper
layout.tsx
The layout file of client mode (useCSR = True) for React, Solid and Preact. As in the Next.js App Router, the root
layout.tsx defines the whole document: <!DOCTYPE html> is added for you, and the layout writes
<html>, <head> and <body> itself.
Root layout
import type { ReactNode } from "react";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en" className="dark" suppressHydrationWarning>
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My app</title>
</head>
<body>
<header>My app</header>
<main>{children}</main>
<footer>Open source under the MIT license.</footer>
</body>
</html>
);
}nx new with React and useCSR = True creates this file. The page is passed as children; the
layout also receives the route's props as params.
Solid
The same file in Solid uses class and props.children. Do not destructure children: Solid reads
it lazily.
import type { JSX } from "solid-js";
export default function RootLayout(props: { children: JSX.Element }) {
return (
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<title>My app</title>
</head>
<body>
<main>{props.children}</main>
</body>
</html>
);
}Solid has to be compiled for the server as well as the browser, so nx new sets
solid({ ssr: true }) in vite.config.ts when useCSR = True. Keep it if you edit the file.
Nested layouts
A layout.tsx in a subdirectory wraps the pages below it and renders inside its parent. It writes UI
only, never <html>:
import type { ReactNode } from "react";
export default function DashboardLayout({ children }: { children: ReactNode }) {
return (
<section>
<aside>Sidebar</aside>
{children}
</section>
);
}dashboard/index.tsx renders as <RootLayout><DashboardLayout><Index /></DashboardLayout></RootLayout>.
How the document is produced
| Moment | What happens |
|---|---|
nx dev |
each request asks the Vite dev server (/__nexy__/render) to render the layouts and the page in Node. Dynamic routes receive their real params. |
nx build |
every static route is rendered to a complete HTML document. A dynamic route ([slug].tsx) is rendered as a shell: the layouts around an empty page. |
nx start |
Python serves the pre-rendered file and adds the client assets and a small JSON description of the page. No Node process runs. |
| Browser | The framework hydrates the whole document. A shell is hydrated first, then the page appears. |
Without a pre-rendered file (for example the dev server is not running) Nexy sends an empty document and the browser renders everything.
Navigation
A click on a Link swaps the page and keeps the layouts mounted, including <html> and <body>:
their DOM, state and scroll position stay. In React a different chain of layouts rebuilds only what
differs. In Solid only the page changes in place: a navigation to a page with a different chain of
layouts loads that page normally.
Rules
layout.tsxandlayout.nexycannot coexist (modes).- Only the root layout writes
<html>,<head>and<body>. - The root layout of a static route is rendered once, at build time: it cannot depend on the request (cookies, headers). Put request-dependent UI in a client component that reads them in the browser.
- A theme script that changes
<html>before hydration needssuppressHydrationWarningon<html>, as in the example. - Import components from
src/components/, not fromsrc/routes/.
Preact
The root layout.tsx of a Preact project is the same file as the Solid one with class and
children (nx new creates it). Preact cannot hydrate a whole document: its hydrate builds a second
<html> element and leaves the handlers unattached. So the browser renders the page body and swaps it
in for the one the server sent; <html> and <head> stay as sent. See
how the browser takes over.
Solid needs the Node renderer
Solid hydrates the server document and cannot build it in the browser alone. When Nexy has no rendered document to send (the Vite dev server is not running and nothing was built), the browser logs an error instead of rendering. React, Preact, Vue and Svelte render in the browser in that case.