Layout files can be placed in any folder inside the ./app directory. They serve as the frame for the routes contained within that folder, and they can nest inside of each other if you place them in sub-directories.
Layouts must render one of the following to show the matched pages that exist in their directory:
This looks something like this at the simplest:
app/_layout.tsx
Layouts can export a loader function just like pages. This is useful for fetching data that’s shared across all routes in that layout:
app/docs/_layout.tsx
Layout loaders run in parallel with page loaders for optimal performance. All matched route loaders (root layout → nested layouts → page) execute simultaneously.
Use useMatches to access the current page’s loader data from a layout:
app/docs/_layout.tsx
See useMatches for more details.
On client-side navigation, layout loaders do not re-run - their data is cached from the initial page load. Only the destination page’s loader runs. This is intentional for performance, as layout data (navigation items, site config) rarely changes.
When layout loaders DO re-run:
window.location.reload()When layout loaders DON’T re-run:
<Link> or useRouter().push()If you need fresh layout data, you can trigger a full page navigation instead of client-side navigation, or store frequently-changing data in the page loader and access it via useMatches().
If you need to access the current route params in a layout, you can use the useActiveParams hook.
The root layout on web controls the entire page, from <html> on down. You can optionally return html, body, and head elements in your app/_layout.tsx and they will “just work” - on web, it will be output, and on native it won’t so as to avoid rendering errors:
app/_layout.tsx
Note - you should have both a lang property on your html tag, and a <meta charSet /> tag in your head. Otherwise, React can have hydration re-rendering issues.
Layouts support render mode suffixes just like pages. This is especially useful for the root layout:
Terminal
When to use layout render modes:
_layout+ssg.tsx - Best for static navigation, site chrome, and head tags. The layout HTML is generated once at build time and served instantly for all pages.
_layout+ssr.tsx - Use when your layout needs dynamic data per-request (e.g., auth state in the nav, user-specific content). The layout loader runs on each request.
_layout.tsx (no suffix) - Client-rendered. Use when the layout has no server requirements.
SPA pages with SSG/SSR layouts:
When an SPA page has a parent layout with +ssg or +ssr render mode, One automatically renders the layout shell on the server. This gives users immediate visual content (navigation, sidebar, head tags, CSS) while page content remains client-rendered.
app/_layout+ssg.tsx
With this setup, even dashboard+spa.tsx pages show the nav instantly - only the main content area waits for JavaScript.
The root _layout.tsx can use a special hook that allows it to insert tags into your <head /> tag after server rendering finishes. This is an edge-case that is mostly only useful for CSS-in-JS libraries. You can use it like so:
If you’d like to logically group together some pages without creating a sub-route, you can use a group folder by naming it like so:
Groups let you add extra layouts. If done right, this gives you a lot of control over how your app feels.
A common pattern that apps have is something like Twitter/X, where you have bottom tabs for your “top level” views, but then on some of the bottom tab sections, you want to have a Stack that remembers its state inside just that tab.
This pattern can be incredibly verbose to link together with React Navigation, and takes a bit of tinkering to figure out with One. Since it is common and a useful example, lets walk through how you’d build on using One’s file system routing.
Here’s our file structure:
The top level Layout will define our tabs:
app/_layout.tsx
This will set us up with three bottom tabs: Feed, Notifications, and Profile. The Notifications and Profile tabs for now will just show their content directly, but inside of the Feed tab, we want to show a stack.
We set up the stack in (feed)/_layout.tsx:
(feed)/_layout.tsx
One thing we’re showing here is that the layout is diverging between web and native. On web, we are showing a Slot, while on Native we show a Stack. This is because browsers feel better without stacks - the native back/forward button serves as our stack controller.
On Native we are defining the configuration for each sub-screen with the Stack.Screen component. The Stack component is a React Navigation native stack navigator and nothing more, it accepts all the props you’d expect.
You’ll notice we are matching the name to the file names names of the sub-routes, without the .tsx extension.
This will get you a nice Stack-inside-Tabs pattern that is common on native apps, all with just two layouts and a few routes.
Note that useParams won’t work in layouts, as they are never nested inside a route. Instead, you can use useActiveParams.
Edit this page on GitHub.