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

src/routes/layout.tsx
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.

src/routes/layout.tsx
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>:

src/routes/dashboard/layout.tsx
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.

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.tsx and layout.nexy cannot 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 needs suppressHydrationWarning on <html>, as in the example.
  • Import components from src/components/, not from src/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.

Next steps

Edit on GitHub

Enter opens the result, Esc closes.