diff --git a/.changeset/add-error-to-meta.md b/.changeset/add-error-to-meta.md new file mode 100644 index 00000000000..6d17a4f0756 --- /dev/null +++ b/.changeset/add-error-to-meta.md @@ -0,0 +1,11 @@ +--- +"@remix-run/react": patch +--- + +Add error to meta params so you can render error titles, etc. + +```tsx +export function meta({ error }) { + return [{ title: error.message }] +} +``` diff --git a/docs/route/meta.md b/docs/route/meta.md index 0abafbe963b..1ca7664a8fd 100644 --- a/docs/route/meta.md +++ b/docs/route/meta.md @@ -110,6 +110,16 @@ export const meta: MetaFunction = ({ The route's URL params. See [Dynamic Segments in the Routing Guide][url-params]. +### `error` + +Thrown errors that trigger error boundaries will be passed to the `meta` function. This is useful for generating metadata for error pages. + +```tsx +export const meta: MetaFunction = ({ error }) => { + return [{ title: error ? "oops!" : "Actual title" }]; +}; +``` + ## Accessing Data from Parent Route Loaders In addition to the current route's data, often you'll want to access data from a route higher up in the route hierarchy. You can look it up by its route ID in `matches`. diff --git a/integration/meta-test.ts b/integration/meta-test.ts index 9f8155ddec7..1315edbeca2 100644 --- a/integration/meta-test.ts +++ b/integration/meta-test.ts @@ -145,6 +145,26 @@ test.describe("meta", () => { return

Music

; } `, + + "app/routes/error.tsx": js` + import { Link, useRouteError } from '@remix-run/react' + + export function loader() { + throw new Error('lol oops') + } + + export const meta = (args) => { + return [{ title: args.error ? "Oops!" : "Home"}] + } + + export default function Index() { + return

Home

+ } + + export function ErrorBoundary() { + return

Home boundary

+ } + `, }, }); appFixture = await createAppFixture(fixture); @@ -227,4 +247,23 @@ test.describe("meta", () => { await app.goto("/authors/1"); expect(await app.getHtml('link[rel="canonical"]')).toBeTruthy(); }); + + test("loader errors are passed to meta", async ({ page }) => { + let restoreErrors = hideErrors(); + + new PlaywrightFixture(appFixture, page); + let response = await fixture.requestDocument("/error"); + expect(await response.text()).toMatch("Oops!"); + + restoreErrors(); + }); }); + +function hideErrors() { + let oldConsoleError: any; + oldConsoleError = console.error; + console.error = () => {}; + return () => { + console.error = oldConsoleError; + }; +} diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx index 4b79348dad3..9bf87599d38 100644 --- a/packages/remix-react/components.tsx +++ b/packages/remix-react/components.tsx @@ -516,6 +516,7 @@ export function Meta() { let meta: MetaDescriptor[] = []; let leafMeta: MetaDescriptor[] | null = null; let matches: MetaMatches = []; + let error = errors ? Object.values(errors)[0] : null; for (let i = 0; i < _matches.length; i++) { let _match = _matches[i]; let routeId = _match.route.id; @@ -531,6 +532,7 @@ export function Meta() { params: _match.params, pathname: _match.pathname, handle: _match.route.handle, + error, }; matches[i] = match; @@ -542,6 +544,7 @@ export function Meta() { params, location, matches, + error, }) : Array.isArray(routeModule.meta) ? [...routeModule.meta] diff --git a/packages/remix-react/routeModules.ts b/packages/remix-react/routeModules.ts index ff63ec03bb7..621276375e7 100644 --- a/packages/remix-react/routeModules.ts +++ b/packages/remix-react/routeModules.ts @@ -51,6 +51,7 @@ export interface MetaMatch< handle?: unknown; params: DataRouteMatch["params"]; meta: MetaDescriptor[]; + error?: unknown; } export type MetaMatches< @@ -74,6 +75,7 @@ export interface MetaArgs< params: Params; location: Location; matches: MetaMatches; + error?: unknown; } export interface MetaFunction<