diff --git a/.changeset/three-cheetahs-lick.md b/.changeset/three-cheetahs-lick.md
new file mode 100644
index 00000000000..3a6d06828e3
--- /dev/null
+++ b/.changeset/three-cheetahs-lick.md
@@ -0,0 +1,7 @@
+---
+"@remix-run/dev": patch
+"@remix-run/react": patch
+"@remix-run/server-runtime": patch
+---
+
+Deprecate `CatchBoundary` in favor of `future.v2_errorBoundary`
diff --git a/docs/route/catch-boundary.md b/docs/route/catch-boundary.md
index bd5985a8d58..556fdc64d9b 100644
--- a/docs/route/catch-boundary.md
+++ b/docs/route/catch-boundary.md
@@ -4,6 +4,8 @@ title: CatchBoundary
# `CatchBoundary`
+The separation of `CatchBoundary` and `ErrorBoundary` has been deprecated and Remix v2 will use a singular `ErrorBoundary` for all thrown Responses and Errors. It is recommended that you opt-into the new behavior in Remix v1 via the `future.v2_errorBoundary` flag in your `remix.config.js` file. Please refer to the [ErrorBoundary (v2)][error-boundary-v2] docs for more information.
+
A `CatchBoundary` is a React component that renders whenever an action or loader throws a `Response`.
**Note:** We use the word "catch" to represent the codepath taken when a `Response` type is thrown; you thought about bailing from the "happy path". This is different from an uncaught error you did not expect to occur.
@@ -29,3 +31,5 @@ export function CatchBoundary() {
);
}
```
+
+[error-boundary-v2]: ./error-boundary-v2
diff --git a/docs/route/error-boundary-v2.md b/docs/route/error-boundary-v2.md
new file mode 100644
index 00000000000..c1a03390587
--- /dev/null
+++ b/docs/route/error-boundary-v2.md
@@ -0,0 +1,11 @@
+---
+title: ErrorBoundary (v2)
+---
+
+# `ErrorBoundary (v2)`
+
+You can opt into the Remix v2 `ErrorBoundary` behavior via the `future.v2_errorBoundary` flag in your `remix.config.js`
+
+If you export an `ErrorBoundary` component from your route module, it will be used as the React Router [`errorElement`][rr-error-element] and will render if you throw from a loader/action or if React throws during rendering your Route component.
+
+[rr-error-element]: https://reactrouter.com/route/error-element
diff --git a/docs/route/error-boundary.md b/docs/route/error-boundary.md
index 8266833e628..5b8e9118a0d 100644
--- a/docs/route/error-boundary.md
+++ b/docs/route/error-boundary.md
@@ -4,6 +4,8 @@ title: ErrorBoundary
# `ErrorBoundary`
+The separation of `CatchBoundary` and `ErrorBoundary` has been deprecated and Remix v2 will use a singular `ErrorBoundary` for all thrown Responses and Errors. It is recommended that you opt-into the new behavior in Remix v1 via the `future.v2_errorBoundary` flag in your `remix.config.js` file. Please refer to the [ErrorBoundary (v2)][error-boundary-v2] docs for more information.
+
An `ErrorBoundary` is a React component that renders whenever there is an error anywhere on the route, either during rendering or during data loading.
**Note:** We use the word "error" to mean an uncaught exception; something you didn't anticipate happening. This is different from other types of "errors" that you are able to recover from easily, for example a 404 error where you can still show something in the user interface to indicate you weren't able to find some data.
@@ -26,3 +28,4 @@ export function ErrorBoundary({ error }) {
```
[error-boundaries]: https://reactjs.org/docs/error-boundaries.html
+[error-boundary-v2]: ./error-boundary-v2
diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts
index 8686777438a..af87d1c55ec 100644
--- a/packages/remix-dev/config.ts
+++ b/packages/remix-dev/config.ts
@@ -401,6 +401,10 @@ export async function readConfig(
warnOnce(serverBuildTargetWarning, "v2_serverBuildTarget");
}
+ if (!appConfig.future?.v2_errorBoundary) {
+ warnOnce(errorBoundaryWarning, "v2_errorBoundary");
+ }
+
let isCloudflareRuntime = ["cloudflare-pages", "cloudflare-workers"].includes(
appConfig.serverBuildTarget ?? ""
);
@@ -726,11 +730,27 @@ const resolveServerBuildPath = (
// @ts-expect-error available in node 12+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat#browser_compatibility
-let listFormat = new Intl.ListFormat("en", {
+const listFormat = new Intl.ListFormat("en", {
style: "long",
type: "conjunction",
});
-export let serverBuildTargetWarning = `⚠️ DEPRECATED: The "serverBuildTarget" config option is deprecated. Use a combination of "publicPath", "serverBuildPath", "serverConditions", "serverDependenciesToBundle", "serverMainFields", "serverMinify", "serverModuleFormat" and/or "serverPlatform" instead.`;
-
-export let flatRoutesWarning = `⚠️ DEPRECATED: The old nested folders route convention has been deprecated in favor of "flat routes". Please enable the new routing convention via the \`future.v2_routeConvention\` flag in your \`remix.config.js\` file. For more information, please see https://remix.run/docs/en/main/file-conventions/route-files-v2.`;
+export const serverBuildTargetWarning =
+ '⚠️ DEPRECATED: The "serverBuildTarget" config option is deprecated. Use a ' +
+ 'combination of "publicPath", "serverBuildPath", "serverConditions", ' +
+ '"serverDependenciesToBundle", "serverMainFields", "serverMinify", ' +
+ '"serverModuleFormat" and/or "serverPlatform" instead.';
+
+export const flatRoutesWarning =
+ "⚠️ DEPRECATED: The old nested folders route convention has been deprecated " +
+ 'in favor of "flat routes". Please enable the new routing convention via the ' +
+ "`future.v2_routeConvention` flag in your `remix.config.js` file. For more " +
+ "information, please see https://remix.run/docs/en/main/file-conventions/route-files-v2.";
+
+export const errorBoundaryWarning =
+ "⚠️ DEPRECATED: The separation of `CatchBoundary` and `ErrorBoundary` has " +
+ "been deprecated and Remix v2 will use a singular `ErrorBoundary` for " +
+ "all thrown values (`Response` and `Error`). Please migrate to the new " +
+ "behavior in Remix v1 via the `future.v2_errorBoundary` flag in your " +
+ "`remix.config.js` file. For more information, see " +
+ "https://remix.run/docs/route/error-boundary-v2";
diff --git a/packages/remix-react/browser.tsx b/packages/remix-react/browser.tsx
index fb3d1f153f9..70eeb7095d5 100644
--- a/packages/remix-react/browser.tsx
+++ b/packages/remix-react/browser.tsx
@@ -14,6 +14,7 @@ import {
import { deserializeErrors } from "./errors";
import type { RouteModules } from "./routeModules";
import { createClientRoutes } from "./routes";
+import { warnOnce } from "./warnings";
/* eslint-disable prefer-let/prefer-let */
declare global {
@@ -138,6 +139,18 @@ if (import.meta && import.meta.hot) {
*/
export function RemixBrowser(_props: RemixBrowserProps): ReactElement {
if (!router) {
+ if (!window.__remixContext.future.v2_errorBoundary) {
+ warnOnce(
+ false,
+ "⚠️ DEPRECATED: The separation of `CatchBoundary` and `ErrorBoundary` has " +
+ "been deprecated and Remix v2 will use a singular `ErrorBoundary` for " +
+ "all thrown values (`Response` and `Error`). Please migrate to the new " +
+ "behavior in Remix v1 via the `future.v2_errorBoundary` flag in your " +
+ "`remix.config.js` file. For more information, see " +
+ "https://remix.run/docs/route/error-boundary-v2"
+ );
+ }
+
let routes = createClientRoutes(
window.__remixManifest.routes,
window.__remixRouteModules,
diff --git a/packages/remix-react/errorBoundaries.tsx b/packages/remix-react/errorBoundaries.tsx
index faa50dd4193..5e083af4d36 100644
--- a/packages/remix-react/errorBoundaries.tsx
+++ b/packages/remix-react/errorBoundaries.tsx
@@ -133,6 +133,8 @@ let RemixCatchContext = React.createContext(
/**
* Returns the status code and thrown response data.
*
+ * @deprecated Please enable the v2_errorBoundary flag
+ *
* @see https://remix.run/route/catch-boundary
*/
export function useCatch<
diff --git a/packages/remix-react/routeModules.ts b/packages/remix-react/routeModules.ts
index 785add903db..147d799bdff 100644
--- a/packages/remix-react/routeModules.ts
+++ b/packages/remix-react/routeModules.ts
@@ -32,6 +32,8 @@ export interface RouteModule {
/**
* A React component that is rendered when the server throws a Response.
*
+ * @deprecated Please enable the v2_errorBoundary flag
+ *
* @see https://remix.run/route/catch-boundary
*/
export type CatchBoundaryComponent = ComponentType<{}>;
@@ -39,6 +41,8 @@ export type CatchBoundaryComponent = ComponentType<{}>;
/**
* A React component that is rendered when there is an error on a route.
*
+ * @deprecated Please enable the v2_errorBoundary flag
+ *
* @see https://remix.run/route/error-boundary
*/
export type ErrorBoundaryComponent = ComponentType<{ error: Error }>;
diff --git a/packages/remix-server-runtime/routeModules.ts b/packages/remix-server-runtime/routeModules.ts
index 380b2549d1f..25a7bb19820 100644
--- a/packages/remix-server-runtime/routeModules.ts
+++ b/packages/remix-server-runtime/routeModules.ts
@@ -36,14 +36,25 @@ export interface ActionFunction {
/**
* A React component that is rendered when the server throws a Response.
+ *
+ * @deprecated Please enable the v2_errorBoundary flag
*/
export type CatchBoundaryComponent = ComponentType;
/**
* A React component that is rendered when there is an error on a route.
+ *
+ * @deprecated Please enable the v2_errorBoundary flag
*/
export type ErrorBoundaryComponent = ComponentType<{ error: Error }>;
+/**
+ * V2 version of the ErrorBoundary that eliminates the distinction between
+ * Error and Catch Boundaries and behaves like RR 6.4 errorElement and captures
+ * errors with useRouteError()
+ */
+export type V2_ErrorBoundaryComponent = ComponentType;
+
/**
* A function that returns HTTP headers to be used for a route. These headers
* will be merged with (and take precedence over) headers from parent routes.
@@ -224,7 +235,7 @@ export type RouteHandle = any;
export interface EntryRouteModule {
CatchBoundary?: CatchBoundaryComponent;
- ErrorBoundary?: ErrorBoundaryComponent;
+ ErrorBoundary?: ErrorBoundaryComponent | V2_ErrorBoundaryComponent;
default: RouteComponent;
handle?: RouteHandle;
links?: LinksFunction;