From 051fe7ebe3aa231ec5ffc1214dcfdb5f6e2151a7 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sat, 3 Aug 2024 19:17:01 +0900 Subject: [PATCH 01/13] feat: support `global-error` --- .../examples/basic/src/routes/global-error.tsx | 11 +++++++++++ packages/react-server/src/plugin/index.ts | 3 +++ packages/react-server/src/plugin/virtual.d.ts | 2 ++ 3 files changed, 16 insertions(+) create mode 100644 packages/react-server/examples/basic/src/routes/global-error.tsx diff --git a/packages/react-server/examples/basic/src/routes/global-error.tsx b/packages/react-server/examples/basic/src/routes/global-error.tsx new file mode 100644 index 000000000..9b8a6df4b --- /dev/null +++ b/packages/react-server/examples/basic/src/routes/global-error.tsx @@ -0,0 +1,11 @@ +"use client"; + +export default function GlobalError() { + return ( + + +

Something went wrong!

+ + + ); +} diff --git a/packages/react-server/src/plugin/index.ts b/packages/react-server/src/plugin/index.ts index 4b6e67e29..96ee31df7 100644 --- a/packages/react-server/src/plugin/index.ts +++ b/packages/react-server/src/plugin/index.ts @@ -181,6 +181,9 @@ export function vitePluginReactServer( ) ); + const globGlobalError = import.meta.glob("/global-error.(js|jsx|ts|tsx)", { eager: true }); + export const globalError = Object.values(globGlobalError)[0]; + const globMiddleware = import.meta.glob("/middleware.(js|jsx|ts|tsx)", { eager: true }); export const middleware = Object.values(globMiddleware)[0]; `; diff --git a/packages/react-server/src/plugin/virtual.d.ts b/packages/react-server/src/plugin/virtual.d.ts index e3cf4f693..43427840b 100644 --- a/packages/react-server/src/plugin/virtual.d.ts +++ b/packages/react-server/src/plugin/virtual.d.ts @@ -2,6 +2,8 @@ declare module "virtual:server-routes" { const $default: Record; export default $default; + export const globalError: React.Component | undefined; + export const middleware: | import("../features/next/middleware").MiddlewareModule | undefined; From 563924a590c57960ef9c7dccfee34d65ac6ee079 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 11:59:29 +0900 Subject: [PATCH 02/13] refactor: move code --- packages/react-server/src/entry/browser.tsx | 7 +++-- .../src/features/error/error-boundary.tsx | 30 +------------------ .../src/features/error/global-error.tsx | 25 ++++++++++++++++ .../src/features/error/not-found.tsx | 12 ++++++++ .../src/features/router/server.tsx | 13 +------- 5 files changed, 43 insertions(+), 44 deletions(-) create mode 100644 packages/react-server/src/features/error/global-error.tsx create mode 100644 packages/react-server/src/features/error/not-found.tsx diff --git a/packages/react-server/src/entry/browser.tsx b/packages/react-server/src/entry/browser.tsx index 429411d44..87e1768c1 100644 --- a/packages/react-server/src/entry/browser.tsx +++ b/packages/react-server/src/entry/browser.tsx @@ -3,7 +3,8 @@ import type { RouterHistory } from "@tanstack/history"; import React from "react"; import ReactDOMClient from "react-dom/client"; import { initializeReactClientBrowser } from "../features/client-component/browser"; -import { RootErrorBoundary } from "../features/error/error-boundary"; +import { ErrorBoundary } from "../features/error/error-boundary"; +import { DefaultGlobalErrorPage } from "../features/error/global-error"; import { FlightDataContext, LayoutRoot, @@ -182,14 +183,14 @@ async function start() { const routeManifest = await importRouteManifest(); let reactRootEl = ( - + - + ); if (!window.location.search.includes("__noStrict")) { diff --git a/packages/react-server/src/features/error/error-boundary.tsx b/packages/react-server/src/features/error/error-boundary.tsx index bc6bc9e0c..19538a446 100644 --- a/packages/react-server/src/features/error/error-boundary.tsx +++ b/packages/react-server/src/features/error/error-boundary.tsx @@ -2,12 +2,7 @@ import { tinyassert } from "@hiogawa/utils"; import React from "react"; import { useRouter } from "../router/client/router"; import type { ErrorPageProps } from "../router/server"; -import { - getErrorContext, - getStatusText, - isNotFoundError, - isRedirectError, -} from "./shared"; +import { getErrorContext, isNotFoundError, isRedirectError } from "./shared"; // cf. // https://github.com/vercel/next.js/blob/33f8428f7066bf8b2ec61f025427ceb2a54c4bdf/packages/next/src/client/components/error-boundary.tsx @@ -71,29 +66,6 @@ function ErrorAutoReset(props: Pick) { return null; } -export function RootErrorBoundary(props: React.PropsWithChildren) { - return ; -} - -// TODO: customizable -function DefaultRootErrorPage(props: ErrorPageProps) { - const status = props.serverError?.status; - const message = status - ? `${status} ${getStatusText(status)}` - : "Unknown Error"; - return ( - - {message} - -

{message}

-
- Back to Home -
- - - ); -} - export class RedirectBoundary extends React.Component { override state: { error: null } | { error: Error; redirectLocation: string } = { error: null }; diff --git a/packages/react-server/src/features/error/global-error.tsx b/packages/react-server/src/features/error/global-error.tsx new file mode 100644 index 000000000..7c7286dfe --- /dev/null +++ b/packages/react-server/src/features/error/global-error.tsx @@ -0,0 +1,25 @@ +"use client"; + +import type { ErrorPageProps } from "../../server"; +import { getStatusText } from "./shared"; + +// https://github.com/vercel/next.js/blob/677c9b372faef680d17e9ba224743f44e1107661/packages/next/src/build/webpack/loaders/next-app-loader.ts#L73 +// https://github.com/vercel/next.js/blob/677c9b372faef680d17e9ba224743f44e1107661/packages/next/src/client/components/error-boundary.tsx#L145 +export function DefaultGlobalErrorPage(props: ErrorPageProps) { + const status = props.serverError?.status; + const message = status + ? `${status} ${getStatusText(status)}` + : "Unknown Error"; + + return ( + + {message} + +

{message}

+
+ Back to Home +
+ + + ); +} diff --git a/packages/react-server/src/features/error/not-found.tsx b/packages/react-server/src/features/error/not-found.tsx new file mode 100644 index 000000000..ea149637f --- /dev/null +++ b/packages/react-server/src/features/error/not-found.tsx @@ -0,0 +1,12 @@ +// https://github.com/vercel/next.js/blob/8f5f0ef141a907d083eedb7c7aca52b04f9d258b/packages/next/src/client/components/not-found-error.tsx#L34-L39 +export function DefaultNotFoundPage() { + return ( + <> + 404 Not Found +

404 Not Found

+
+ Back to Home +
+ + ); +} diff --git a/packages/react-server/src/features/router/server.tsx b/packages/react-server/src/features/router/server.tsx index 2ecb48273..f7c67928b 100644 --- a/packages/react-server/src/features/router/server.tsx +++ b/packages/react-server/src/features/router/server.tsx @@ -1,5 +1,6 @@ import { sortBy, tinyassert } from "@hiogawa/utils"; import React from "react"; +import { DefaultNotFoundPage } from "../error/not-found"; import { type ReactServerErrorContext } from "../error/shared"; import { renderMetadata } from "../meta/server"; import type { Metadata } from "../meta/utils"; @@ -58,18 +59,6 @@ export function generateRouteModuleTree(globEntries: Record) { return { tree, manifest }; } -// https://github.com/vercel/next.js/blob/8f5f0ef141a907d083eedb7c7aca52b04f9d258b/packages/next/src/client/components/not-found-error.tsx#L34-L39 -function DefaultNotFoundPage() { - return ( - <> -

404 Not Found

-
- Back to Home -
- - ); -} - // use own "use client" components as external function importRuntimeClient(): Promise { return import("@hiogawa/react-server/runtime/client" as string); From d5f44ede00abf2b4bf5891cd5764cd4a0f966f97 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 12:04:58 +0900 Subject: [PATCH 03/13] chore: tweak --- packages/react-server/src/features/error/not-found.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-server/src/features/error/not-found.tsx b/packages/react-server/src/features/error/not-found.tsx index ea149637f..9352dc047 100644 --- a/packages/react-server/src/features/error/not-found.tsx +++ b/packages/react-server/src/features/error/not-found.tsx @@ -2,7 +2,6 @@ export function DefaultNotFoundPage() { return ( <> - 404 Not Found

404 Not Found

Back to Home From bebcab12c2263ad005e32f50a57652d61da349b8 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 12:11:41 +0900 Subject: [PATCH 04/13] wip: add virtual:client-routes --- packages/react-server/src/entry/browser.tsx | 3 ++- packages/react-server/src/plugin/index.ts | 8 ++++++++ packages/react-server/src/plugin/virtual.d.ts | 6 ++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/react-server/src/entry/browser.tsx b/packages/react-server/src/entry/browser.tsx index 87e1768c1..2205a537c 100644 --- a/packages/react-server/src/entry/browser.tsx +++ b/packages/react-server/src/entry/browser.tsx @@ -1,3 +1,4 @@ +import { globalError } from "virtual:client-routes"; import { createDebug, memoize, tinyassert } from "@hiogawa/utils"; import type { RouterHistory } from "@tanstack/history"; import React from "react"; @@ -183,7 +184,7 @@ async function start() { const routeManifest = await importRouteManifest(); let reactRootEl = ( - + diff --git a/packages/react-server/src/plugin/index.ts b/packages/react-server/src/plugin/index.ts index 96ee31df7..ee7dbdf9c 100644 --- a/packages/react-server/src/plugin/index.ts +++ b/packages/react-server/src/plugin/index.ts @@ -438,6 +438,14 @@ export function vitePluginReactServer( return `export * from "/${outDir}/rsc/index.js";`; }), + createVirtualPlugin("client-routes", () => { + return ` + export const globalError = Object.values( + import.meta.glob("/global-error.(js|jsx|ts|tsx)", { eager: true }), + )[0]; + `; + }), + createVirtualPlugin(ENTRY_BROWSER_WRAPPER.slice("virtual:".length), () => { // dev if (!manager.buildType) { diff --git a/packages/react-server/src/plugin/virtual.d.ts b/packages/react-server/src/plugin/virtual.d.ts index 43427840b..aad19e539 100644 --- a/packages/react-server/src/plugin/virtual.d.ts +++ b/packages/react-server/src/plugin/virtual.d.ts @@ -2,9 +2,11 @@ declare module "virtual:server-routes" { const $default: Record; export default $default; - export const globalError: React.Component | undefined; - export const middleware: | import("../features/next/middleware").MiddlewareModule | undefined; } + +declare module "virtual:client-routes" { + export const globalError: React.FC | undefined; +} From 4480c0aef0ff8655c75720591c63fc24b6d74068 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 12:13:06 +0900 Subject: [PATCH 05/13] wip: crawl css --- packages/react-server/src/features/assets/plugin.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-server/src/features/assets/plugin.ts b/packages/react-server/src/features/assets/plugin.ts index 52efe0d67..25e36c806 100644 --- a/packages/react-server/src/features/assets/plugin.ts +++ b/packages/react-server/src/features/assets/plugin.ts @@ -105,6 +105,7 @@ export function vitePluginServerAssets({ collectStyle($__global.dev.server, { entries: [ entryBrowser, + "virtual:client-routes", // TODO: dev should also use RouteManifest to manage client css ...manager.clientReferenceMap.keys(), ], From 84d85becd827b7a3272035c86ceba989521c153b Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 12:15:23 +0900 Subject: [PATCH 06/13] chore: tweak --- packages/react-server/src/entry/browser.tsx | 8 ++++++-- packages/react-server/src/plugin/index.ts | 2 +- packages/react-server/src/plugin/virtual.d.ts | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/react-server/src/entry/browser.tsx b/packages/react-server/src/entry/browser.tsx index 2205a537c..09221006a 100644 --- a/packages/react-server/src/entry/browser.tsx +++ b/packages/react-server/src/entry/browser.tsx @@ -1,4 +1,4 @@ -import { globalError } from "virtual:client-routes"; +import * as virtualClientRoutes from "virtual:client-routes"; import { createDebug, memoize, tinyassert } from "@hiogawa/utils"; import type { RouterHistory } from "@tanstack/history"; import React from "react"; @@ -184,7 +184,11 @@ async function start() { const routeManifest = await importRouteManifest(); let reactRootEl = ( - + diff --git a/packages/react-server/src/plugin/index.ts b/packages/react-server/src/plugin/index.ts index ee7dbdf9c..4ea50639f 100644 --- a/packages/react-server/src/plugin/index.ts +++ b/packages/react-server/src/plugin/index.ts @@ -440,7 +440,7 @@ export function vitePluginReactServer( createVirtualPlugin("client-routes", () => { return ` - export const globalError = Object.values( + export const GlobalErrorPage = Object.values( import.meta.glob("/global-error.(js|jsx|ts|tsx)", { eager: true }), )[0]; `; diff --git a/packages/react-server/src/plugin/virtual.d.ts b/packages/react-server/src/plugin/virtual.d.ts index aad19e539..26aa9d72e 100644 --- a/packages/react-server/src/plugin/virtual.d.ts +++ b/packages/react-server/src/plugin/virtual.d.ts @@ -8,5 +8,5 @@ declare module "virtual:server-routes" { } declare module "virtual:client-routes" { - export const globalError: React.FC | undefined; + export const GlobalErrorPage: React.FC | undefined; } From 3752865e20943ed91f79a882a6fba57ce5dd7bcc Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 12:16:29 +0900 Subject: [PATCH 07/13] chore: cleanup --- packages/react-server/src/plugin/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/react-server/src/plugin/index.ts b/packages/react-server/src/plugin/index.ts index 4ea50639f..e88fc58cf 100644 --- a/packages/react-server/src/plugin/index.ts +++ b/packages/react-server/src/plugin/index.ts @@ -181,9 +181,6 @@ export function vitePluginReactServer( ) ); - const globGlobalError = import.meta.glob("/global-error.(js|jsx|ts|tsx)", { eager: true }); - export const globalError = Object.values(globGlobalError)[0]; - const globMiddleware = import.meta.glob("/middleware.(js|jsx|ts|tsx)", { eager: true }); export const middleware = Object.values(globMiddleware)[0]; `; From d246f1dfdf69911c04a7afc7541a8ba687496024 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 12:17:32 +0900 Subject: [PATCH 08/13] chore: revert some --- .../react-server/src/features/error/not-found.tsx | 11 ----------- .../react-server/src/features/router/server.tsx | 13 ++++++++++++- 2 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 packages/react-server/src/features/error/not-found.tsx diff --git a/packages/react-server/src/features/error/not-found.tsx b/packages/react-server/src/features/error/not-found.tsx deleted file mode 100644 index 9352dc047..000000000 --- a/packages/react-server/src/features/error/not-found.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// https://github.com/vercel/next.js/blob/8f5f0ef141a907d083eedb7c7aca52b04f9d258b/packages/next/src/client/components/not-found-error.tsx#L34-L39 -export function DefaultNotFoundPage() { - return ( - <> -

404 Not Found

-
- Back to Home -
- - ); -} diff --git a/packages/react-server/src/features/router/server.tsx b/packages/react-server/src/features/router/server.tsx index f7c67928b..2ecb48273 100644 --- a/packages/react-server/src/features/router/server.tsx +++ b/packages/react-server/src/features/router/server.tsx @@ -1,6 +1,5 @@ import { sortBy, tinyassert } from "@hiogawa/utils"; import React from "react"; -import { DefaultNotFoundPage } from "../error/not-found"; import { type ReactServerErrorContext } from "../error/shared"; import { renderMetadata } from "../meta/server"; import type { Metadata } from "../meta/utils"; @@ -59,6 +58,18 @@ export function generateRouteModuleTree(globEntries: Record) { return { tree, manifest }; } +// https://github.com/vercel/next.js/blob/8f5f0ef141a907d083eedb7c7aca52b04f9d258b/packages/next/src/client/components/not-found-error.tsx#L34-L39 +function DefaultNotFoundPage() { + return ( + <> +

404 Not Found

+
+ Back to Home +
+ + ); +} + // use own "use client" components as external function importRuntimeClient(): Promise { return import("@hiogawa/react-server/runtime/client" as string); From f585870b94d3ee91563d55d23f3353305e7e5cd7 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 12:19:18 +0900 Subject: [PATCH 09/13] chore: cleanup --- packages/react-server/src/entry/browser.tsx | 6 +++-- .../src/features/error/error-boundary.tsx | 25 ++++++++++++++++++- .../src/features/error/global-error.tsx | 25 ------------------- 3 files changed, 28 insertions(+), 28 deletions(-) delete mode 100644 packages/react-server/src/features/error/global-error.tsx diff --git a/packages/react-server/src/entry/browser.tsx b/packages/react-server/src/entry/browser.tsx index 09221006a..9d93b1b0b 100644 --- a/packages/react-server/src/entry/browser.tsx +++ b/packages/react-server/src/entry/browser.tsx @@ -4,8 +4,10 @@ import type { RouterHistory } from "@tanstack/history"; import React from "react"; import ReactDOMClient from "react-dom/client"; import { initializeReactClientBrowser } from "../features/client-component/browser"; -import { ErrorBoundary } from "../features/error/error-boundary"; -import { DefaultGlobalErrorPage } from "../features/error/global-error"; +import { + DefaultGlobalErrorPage, + ErrorBoundary, +} from "../features/error/error-boundary"; import { FlightDataContext, LayoutRoot, diff --git a/packages/react-server/src/features/error/error-boundary.tsx b/packages/react-server/src/features/error/error-boundary.tsx index 19538a446..35536db67 100644 --- a/packages/react-server/src/features/error/error-boundary.tsx +++ b/packages/react-server/src/features/error/error-boundary.tsx @@ -2,7 +2,12 @@ import { tinyassert } from "@hiogawa/utils"; import React from "react"; import { useRouter } from "../router/client/router"; import type { ErrorPageProps } from "../router/server"; -import { getErrorContext, isNotFoundError, isRedirectError } from "./shared"; +import { + getErrorContext, + getStatusText, + isNotFoundError, + isRedirectError, +} from "./shared"; // cf. // https://github.com/vercel/next.js/blob/33f8428f7066bf8b2ec61f025427ceb2a54c4bdf/packages/next/src/client/components/error-boundary.tsx @@ -66,6 +71,24 @@ function ErrorAutoReset(props: Pick) { return null; } +export function DefaultGlobalErrorPage(props: ErrorPageProps) { + const status = props.serverError?.status; + const message = status + ? `${status} ${getStatusText(status)}` + : "Unknown Error"; + return ( + + {message} + +

{message}

+
+ Back to Home +
+ + + ); +} + export class RedirectBoundary extends React.Component { override state: { error: null } | { error: Error; redirectLocation: string } = { error: null }; diff --git a/packages/react-server/src/features/error/global-error.tsx b/packages/react-server/src/features/error/global-error.tsx deleted file mode 100644 index 7c7286dfe..000000000 --- a/packages/react-server/src/features/error/global-error.tsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; - -import type { ErrorPageProps } from "../../server"; -import { getStatusText } from "./shared"; - -// https://github.com/vercel/next.js/blob/677c9b372faef680d17e9ba224743f44e1107661/packages/next/src/build/webpack/loaders/next-app-loader.ts#L73 -// https://github.com/vercel/next.js/blob/677c9b372faef680d17e9ba224743f44e1107661/packages/next/src/client/components/error-boundary.tsx#L145 -export function DefaultGlobalErrorPage(props: ErrorPageProps) { - const status = props.serverError?.status; - const message = status - ? `${status} ${getStatusText(status)}` - : "Unknown Error"; - - return ( - - {message} - -

{message}

-
- Back to Home -
- - - ); -} From 288e80f4c9248e9684e003136d27cfd257c0b96c Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 12:35:02 +0900 Subject: [PATCH 10/13] fix: fix virtual:server-routes --- packages/react-server/examples/basic/e2e/basic.test.ts | 4 ++++ packages/react-server/examples/basic/src/routes/page.tsx | 7 ++++++- packages/react-server/src/plugin/index.ts | 5 ++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/react-server/examples/basic/e2e/basic.test.ts b/packages/react-server/examples/basic/e2e/basic.test.ts index c1aee3469..d514a5aa4 100644 --- a/packages/react-server/examples/basic/e2e/basic.test.ts +++ b/packages/react-server/examples/basic/e2e/basic.test.ts @@ -1599,3 +1599,7 @@ test("mdx dynamic", async ({ page }) => { await page.goto("/test/mdx/dynamic/no-such-post"); await page.getByText("Page not found :(").click(); }); + +test("global-error.js", async ({ page }) => { + await page.goto("/"); +}); diff --git a/packages/react-server/examples/basic/src/routes/page.tsx b/packages/react-server/examples/basic/src/routes/page.tsx index 4d8b456e1..f8c734f55 100644 --- a/packages/react-server/examples/basic/src/routes/page.tsx +++ b/packages/react-server/examples/basic/src/routes/page.tsx @@ -1,3 +1,8 @@ -export default function Page() { +import type { PageProps } from "@hiogawa/react-server/server"; + +export default function Page(props: PageProps) { + if ("test-global-error" in props.searchParams) { + throw new Error("boom!"); + } return
Choose a page from the menu
; } diff --git a/packages/react-server/src/plugin/index.ts b/packages/react-server/src/plugin/index.ts index e88fc58cf..ed164c1f0 100644 --- a/packages/react-server/src/plugin/index.ts +++ b/packages/react-server/src/plugin/index.ts @@ -437,9 +437,8 @@ export function vitePluginReactServer( createVirtualPlugin("client-routes", () => { return ` - export const GlobalErrorPage = Object.values( - import.meta.glob("/global-error.(js|jsx|ts|tsx)", { eager: true }), - )[0]; + const glob = import.meta.glob("/${routeDir}/global-error.(js|jsx|ts|tsx)", { eager: true }); + export const GlobalErrorPage = Object.values(glob)[0]?.default; `; }), From b0cfba626720988ed7906207dd1866434898bf6e Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 12:58:45 +0900 Subject: [PATCH 11/13] chore: tweak style chore: move code --- .../basic/src/routes/global-error.tsx | 3 +- .../examples/basic/src/routes/page.tsx | 2 +- packages/react-server/src/entry/browser.tsx | 6 +-- .../src/features/error/error-boundary.tsx | 25 +---------- .../src/features/error/global-error.tsx | 44 +++++++++++++++++++ 5 files changed, 50 insertions(+), 30 deletions(-) create mode 100644 packages/react-server/src/features/error/global-error.tsx diff --git a/packages/react-server/examples/basic/src/routes/global-error.tsx b/packages/react-server/examples/basic/src/routes/global-error.tsx index 9b8a6df4b..3d0ebbdc8 100644 --- a/packages/react-server/examples/basic/src/routes/global-error.tsx +++ b/packages/react-server/examples/basic/src/routes/global-error.tsx @@ -1,10 +1,11 @@ "use client"; -export default function GlobalError() { +export default function GlobalError(props: { reset: () => void }) { return (

Something went wrong!

+ ); diff --git a/packages/react-server/examples/basic/src/routes/page.tsx b/packages/react-server/examples/basic/src/routes/page.tsx index f8c734f55..8ceb1ac37 100644 --- a/packages/react-server/examples/basic/src/routes/page.tsx +++ b/packages/react-server/examples/basic/src/routes/page.tsx @@ -1,7 +1,7 @@ import type { PageProps } from "@hiogawa/react-server/server"; export default function Page(props: PageProps) { - if ("test-global-error" in props.searchParams) { + if ("test-error" in props.searchParams) { throw new Error("boom!"); } return
Choose a page from the menu
; diff --git a/packages/react-server/src/entry/browser.tsx b/packages/react-server/src/entry/browser.tsx index 9d93b1b0b..09221006a 100644 --- a/packages/react-server/src/entry/browser.tsx +++ b/packages/react-server/src/entry/browser.tsx @@ -4,10 +4,8 @@ import type { RouterHistory } from "@tanstack/history"; import React from "react"; import ReactDOMClient from "react-dom/client"; import { initializeReactClientBrowser } from "../features/client-component/browser"; -import { - DefaultGlobalErrorPage, - ErrorBoundary, -} from "../features/error/error-boundary"; +import { ErrorBoundary } from "../features/error/error-boundary"; +import { DefaultGlobalErrorPage } from "../features/error/global-error"; import { FlightDataContext, LayoutRoot, diff --git a/packages/react-server/src/features/error/error-boundary.tsx b/packages/react-server/src/features/error/error-boundary.tsx index 35536db67..19538a446 100644 --- a/packages/react-server/src/features/error/error-boundary.tsx +++ b/packages/react-server/src/features/error/error-boundary.tsx @@ -2,12 +2,7 @@ import { tinyassert } from "@hiogawa/utils"; import React from "react"; import { useRouter } from "../router/client/router"; import type { ErrorPageProps } from "../router/server"; -import { - getErrorContext, - getStatusText, - isNotFoundError, - isRedirectError, -} from "./shared"; +import { getErrorContext, isNotFoundError, isRedirectError } from "./shared"; // cf. // https://github.com/vercel/next.js/blob/33f8428f7066bf8b2ec61f025427ceb2a54c4bdf/packages/next/src/client/components/error-boundary.tsx @@ -71,24 +66,6 @@ function ErrorAutoReset(props: Pick) { return null; } -export function DefaultGlobalErrorPage(props: ErrorPageProps) { - const status = props.serverError?.status; - const message = status - ? `${status} ${getStatusText(status)}` - : "Unknown Error"; - return ( - - {message} - -

{message}

-
- Back to Home -
- - - ); -} - export class RedirectBoundary extends React.Component { override state: { error: null } | { error: Error; redirectLocation: string } = { error: null }; diff --git a/packages/react-server/src/features/error/global-error.tsx b/packages/react-server/src/features/error/global-error.tsx new file mode 100644 index 000000000..c6dfa23b7 --- /dev/null +++ b/packages/react-server/src/features/error/global-error.tsx @@ -0,0 +1,44 @@ +import type { ErrorPageProps } from "../../server"; + +// https://github.com/vercel/next.js/blob/677c9b372faef680d17e9ba224743f44e1107661/packages/next/src/build/webpack/loaders/next-app-loader.ts#L73 +// https://github.com/vercel/next.js/blob/677c9b372faef680d17e9ba224743f44e1107661/packages/next/src/client/components/error-boundary.tsx#L145 +export function DefaultGlobalErrorPage(props: ErrorPageProps) { + const message = props.serverError + ? `Unknown Server Error (see server logs for the details)` + : "Unknown Client Error (see browser console for the details)"; + return ( + + {message} + +

{message}

+
+ Back to{" "} + + Home + +
+ + + ); +} From 7f3b4cab80a161d34407c6948ffbc0e1e14a7c6d Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 13:17:22 +0900 Subject: [PATCH 12/13] test: add e2e --- .../examples/basic/e2e/basic.test.ts | 7 +++-- .../basic/src/routes/global-error.tsx | 30 +++++++++++++++++-- .../src/features/error/global-error.tsx | 20 ++----------- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/packages/react-server/examples/basic/e2e/basic.test.ts b/packages/react-server/examples/basic/e2e/basic.test.ts index d514a5aa4..b442902b4 100644 --- a/packages/react-server/examples/basic/e2e/basic.test.ts +++ b/packages/react-server/examples/basic/e2e/basic.test.ts @@ -1600,6 +1600,9 @@ test("mdx dynamic", async ({ page }) => { await page.getByText("Page not found :(").click(); }); -test("global-error.js", async ({ page }) => { - await page.goto("/"); +test("global-error", async ({ page }) => { + await page.goto("/?test-error"); + await page.getByRole("heading", { name: "Something went wrong." }).click(); + await page.getByRole("link", { name: "Home" }).click(); + await page.waitForURL("/"); }); diff --git a/packages/react-server/examples/basic/src/routes/global-error.tsx b/packages/react-server/examples/basic/src/routes/global-error.tsx index 3d0ebbdc8..aa27dc5b0 100644 --- a/packages/react-server/examples/basic/src/routes/global-error.tsx +++ b/packages/react-server/examples/basic/src/routes/global-error.tsx @@ -1,11 +1,35 @@ "use client"; -export default function GlobalError(props: { reset: () => void }) { +export default function GlobalError() { return ( -

Something went wrong!

- +
+

Something went wrong. Please try it again later.

+
+ Back to{" "} + + Home + +
+
); diff --git a/packages/react-server/src/features/error/global-error.tsx b/packages/react-server/src/features/error/global-error.tsx index c6dfa23b7..52b1b11ac 100644 --- a/packages/react-server/src/features/error/global-error.tsx +++ b/packages/react-server/src/features/error/global-error.tsx @@ -5,7 +5,7 @@ import type { ErrorPageProps } from "../../server"; export function DefaultGlobalErrorPage(props: ErrorPageProps) { const message = props.serverError ? `Unknown Server Error (see server logs for the details)` - : "Unknown Client Error (see browser console for the details)"; + : "Unknown Client Error (see browser console for the detail)"; return ( {message} @@ -14,30 +14,16 @@ export function DefaultGlobalErrorPage(props: ErrorPageProps) { fontFamily: 'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"', height: "100vh", - textAlign: "center", display: "flex", flexDirection: "column", - alignItems: "center", - justifyContent: "center", + placeContent: "center", + placeItems: "center", fontSize: "14px", fontWeight: 400, lineHeight: "28px", }} >

{message}

-
- Back to{" "} - - Home - -
); From 4d70f1412d888af900193e969321cdb13bb7cf79 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 4 Aug 2024 13:20:57 +0900 Subject: [PATCH 13/13] chore: typo --- packages/react-server/src/features/error/global-error.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-server/src/features/error/global-error.tsx b/packages/react-server/src/features/error/global-error.tsx index 52b1b11ac..67789d1b5 100644 --- a/packages/react-server/src/features/error/global-error.tsx +++ b/packages/react-server/src/features/error/global-error.tsx @@ -5,7 +5,7 @@ import type { ErrorPageProps } from "../../server"; export function DefaultGlobalErrorPage(props: ErrorPageProps) { const message = props.serverError ? `Unknown Server Error (see server logs for the details)` - : "Unknown Client Error (see browser console for the detail)"; + : `Unknown Client Error (see browser console for the details)`; return ( {message}