The "root" route (app/root.tsx
) is the only required route in your Remix application because it is the parent to all routes in your routes/
directory and is in charge of rendering the root <html>
document.
Beyond that, it's mostly just like any other route and supports all of the standard route exports:
headers
meta
links
loader
clientLoader
action
clientAction
default
ErrorBoundary
HydrateFallback
handle
shouldRevalidate
Because the root route manages your document, it is the proper place to render a handful of "document-level" components Remix provides. These components are to be used once inside your root route and they include everything Remix figured out or built in order for your page to render properly.
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import globalStylesheetUrl from "./global-styles.css";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: globalStylesheetUrl }];
};
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
{/* All `meta` exports on all routes will render here */}
<Meta />
{/* All `link` exports on all routes will render here */}
<Links />
</head>
<body>
{/* Child routes render here */}
<Outlet />
{/* Manages scroll position for client-side transitions */}
{/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */}
<ScrollRestoration />
{/* Script tags go here */}
{/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */}
<Scripts />
{/* Sets up automatic reload when you change code */}
{/* and only does anything during development */}
{/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */}
<LiveReload />
</body>
</html>
);
}
Because the root route manages the document for all routes, it also supports an additional optional Layout
export. You can read the details in this RFC but the layout route serves 2 purposes:
HydrateFallback
, and ErrorBoundary
HydrateFallback
/ErrorBoundary
which can cause a FOUC if React removes and re-adds <link rel="stylesheet">
tags from your <Links>
component.import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
export function Layout({ children }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<Meta />
<Links />
</head>
<body>
{/* children will be the root Component, ErrorBoundary, or HydrateFallback */}
{children}
<Scripts />
<ScrollRestoration />
<LiveReload />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<>
<h1>
{error.status} {error.statusText}
</h1>
<p>{error.data}</p>
</>
);
}
return (
<>
<h1>Error!</h1>
<p>{error?.message ?? "Unknown error"}</p>
</>
);
}
A note on useLoaderData
in the Layout
Component
useLoaderData
is not permitted to be used in ErrorBoundary
components because it is intended for the happy-path route rendering, and its typings have a built-in assumption that the loader
ran successfully and returned something. That assumption doesn't hold in an ErrorBoundary
because it could have been the loader
that threw and triggered the boundary! In order to access loader data in ErrorBoundary
's, you can use useRouteLoaderData
which accounts for the loader data potentially being undefined
.
Because your Layout
component is used in both success and error flows, this same restriction holds. If you need to fork logic in your Layout
depending on if it was a successful request or not, you can use useRouteLoaderData("root")
and useRouteError()
.
<Layout>
component is used for rendering the ErrorBoundary
, you should be very defensive to ensure that you can render your ErrorBoundary
without encountering any render errors. If your Layout
throws another error trying to render the boundary, then it can't can't be used and your UI will fall back to the very minimal built-in default ErrorBoundary
.
export function Layout({
children,
}: {
children: React.ReactNode;
}) {
const data = useRouteLoaderData("root");
const error = useRouteError();
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<Meta />
<Links />
<style
dangerouslySetInnerHTML={{
__html: `
:root {
--themeVar: ${
data?.themeVar || defaultThemeVar
}
}
`,
}}
/>
</head>
<body>
{data ? (
<Analytics token={data.analyticsToken} />
) : null}
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
See also: