-
-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: handle Next.js errors internally
- Loading branch information
Showing
9 changed files
with
204 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
packages/next-safe-action/src/next/errors/bailout-to-csr.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Comes from https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/lazy-dynamic/bailout-to-csr.ts | ||
|
||
// This has to be a shared module which is shared between client component error boundary and dynamic component | ||
const BAILOUT_TO_CSR = "BAILOUT_TO_CLIENT_SIDE_RENDERING"; | ||
|
||
/** An error that should be thrown when we want to bail out to client-side rendering. */ | ||
class BailoutToCSRError extends Error { | ||
public readonly digest = BAILOUT_TO_CSR; | ||
|
||
constructor(public readonly reason: string) { | ||
super(`Bail out to client-side rendering: ${reason}`); | ||
} | ||
} | ||
|
||
/** Checks if a passed argument is an error that is thrown if we want to bail out to client-side rendering. */ | ||
export function isBailoutToCSRError(err: unknown): err is BailoutToCSRError { | ||
if (typeof err !== "object" || err === null || !("digest" in err)) { | ||
return false; | ||
} | ||
|
||
return err.digest === BAILOUT_TO_CSR; | ||
} |
45 changes: 45 additions & 0 deletions
45
packages/next-safe-action/src/next/errors/dynamic-usage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Comes from https://github.com/vercel/next.js/blob/canary/packages/next/src/export/helpers/is-dynamic-usage-error.ts | ||
|
||
import { isBailoutToCSRError } from "./bailout-to-csr"; | ||
import { isNextRouterError } from "./router"; | ||
|
||
const DYNAMIC_ERROR_CODE = "DYNAMIC_SERVER_USAGE"; | ||
|
||
class DynamicServerError extends Error { | ||
digest: typeof DYNAMIC_ERROR_CODE = DYNAMIC_ERROR_CODE; | ||
|
||
constructor(public readonly description: string) { | ||
super(`Dynamic server usage: ${description}`); | ||
} | ||
} | ||
|
||
function isDynamicServerError(err: unknown): err is DynamicServerError { | ||
if (typeof err !== "object" || err === null || !("digest" in err) || typeof err.digest !== "string") { | ||
return false; | ||
} | ||
|
||
return err.digest === DYNAMIC_ERROR_CODE; | ||
} | ||
|
||
function isDynamicPostponeReason(reason: string) { | ||
return ( | ||
reason.includes("needs to bail out of prerendering at this point because it used") && | ||
reason.includes("Learn more: https://nextjs.org/docs/messages/ppr-caught-error") | ||
); | ||
} | ||
|
||
function isDynamicPostpone(err: unknown) { | ||
if ( | ||
typeof err === "object" && | ||
err !== null && | ||
// eslint-disable-next-line | ||
typeof (err as any).message === "string" | ||
) { | ||
// eslint-disable-next-line | ||
return isDynamicPostponeReason((err as any).message); | ||
} | ||
return false; | ||
} | ||
|
||
export const isDynamicUsageError = (err: unknown) => | ||
isDynamicServerError(err) || isBailoutToCSRError(err) || isNextRouterError(err) || isDynamicPostpone(err); |
36 changes: 36 additions & 0 deletions
36
packages/next-safe-action/src/next/errors/http-access-fallback.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Comes from https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/http-access-fallback/http-access-fallback.ts | ||
|
||
const HTTPAccessErrorStatus = { | ||
NOT_FOUND: 404, | ||
FORBIDDEN: 403, | ||
UNAUTHORIZED: 401, | ||
}; | ||
|
||
const ALLOWED_CODES = new Set(Object.values(HTTPAccessErrorStatus)); | ||
|
||
const HTTP_ERROR_FALLBACK_ERROR_CODE = "NEXT_HTTP_ERROR_FALLBACK"; | ||
|
||
export type HTTPAccessFallbackError = Error & { | ||
digest: `${typeof HTTP_ERROR_FALLBACK_ERROR_CODE};${string}`; | ||
}; | ||
|
||
/** | ||
* Checks an error to determine if it's an error generated by | ||
* the HTTP navigation APIs `notFound()`, `forbidden()` or `unauthorized()`. | ||
* | ||
* @param error the error that may reference a HTTP access error | ||
* @returns true if the error is a HTTP access error | ||
*/ | ||
export function isHTTPAccessFallbackError(error: unknown): error is HTTPAccessFallbackError { | ||
if (typeof error !== "object" || error === null || !("digest" in error) || typeof error.digest !== "string") { | ||
return false; | ||
} | ||
const [prefix, httpStatus] = error.digest.split(";"); | ||
|
||
return prefix === HTTP_ERROR_FALLBACK_ERROR_CODE && ALLOWED_CODES.has(Number(httpStatus)); | ||
} | ||
|
||
export function getAccessFallbackHTTPStatus(error: HTTPAccessFallbackError): number { | ||
const httpStatus = error.digest.split(";")[1]; | ||
return Number(httpStatus); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { isBailoutToCSRError } from "./bailout-to-csr"; | ||
import { isDynamicUsageError } from "./dynamic-usage"; | ||
import { | ||
getAccessFallbackHTTPStatus, | ||
isHTTPAccessFallbackError, | ||
type HTTPAccessFallbackError, | ||
} from "./http-access-fallback"; | ||
import { isPostpone } from "./postpone"; | ||
import { isNextRouterError } from "./router"; | ||
|
||
export function isNotFoundError(error: unknown): error is HTTPAccessFallbackError { | ||
return isHTTPAccessFallbackError(error) && getAccessFallbackHTTPStatus(error) === 404; | ||
} | ||
|
||
export function isForbiddenError(error: unknown): error is HTTPAccessFallbackError { | ||
return isHTTPAccessFallbackError(error) && getAccessFallbackHTTPStatus(error) === 403; | ||
} | ||
|
||
export function isUnauthorizedError(error: unknown): error is HTTPAccessFallbackError { | ||
return isHTTPAccessFallbackError(error) && getAccessFallbackHTTPStatus(error) === 401; | ||
} | ||
|
||
// Next.js error handling | ||
export function isFrameworkError(error: unknown): error is Error { | ||
return isNextRouterError(error) || isBailoutToCSRError(error) || isDynamicUsageError(error) || isPostpone(error); | ||
} | ||
|
||
export { isRedirectError } from "./redirect"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Comes from https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/is-postpone.ts | ||
|
||
const REACT_POSTPONE_TYPE: symbol = Symbol.for("react.postpone"); | ||
|
||
export function isPostpone(error: any): boolean { | ||
return ( | ||
typeof error === "object" && | ||
error !== null && | ||
// eslint-disable-next-line | ||
error.$$typeof === REACT_POSTPONE_TYPE | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// Comes from: https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/redirect-error.ts | ||
|
||
enum RedirectStatusCode { | ||
SeeOther = 303, | ||
TemporaryRedirect = 307, | ||
PermanentRedirect = 308, | ||
} | ||
|
||
const REDIRECT_ERROR_CODE = "NEXT_REDIRECT"; | ||
|
||
enum RedirectType { | ||
push = "push", | ||
replace = "replace", | ||
} | ||
|
||
export type RedirectError = Error & { | ||
digest: `${typeof REDIRECT_ERROR_CODE};${RedirectType};${string};${RedirectStatusCode};`; | ||
}; | ||
|
||
/** | ||
* Checks an error to determine if it's an error generated by the | ||
* `redirect(url)` helper. | ||
* | ||
* @param error the error that may reference a redirect error | ||
* @returns true if the error is a redirect error | ||
*/ | ||
export function isRedirectError(error: unknown): error is RedirectError { | ||
if (typeof error !== "object" || error === null || !("digest" in error) || typeof error.digest !== "string") { | ||
return false; | ||
} | ||
|
||
const digest = error.digest.split(";"); | ||
const [errorCode, type] = digest; | ||
const destination = digest.slice(2, -2).join(";"); | ||
const status = digest.at(-2); | ||
|
||
const statusCode = Number(status); | ||
|
||
return ( | ||
errorCode === REDIRECT_ERROR_CODE && | ||
(type === "replace" || type === "push") && | ||
typeof destination === "string" && | ||
!isNaN(statusCode) && | ||
statusCode in RedirectStatusCode | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Comes from https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/is-next-router-error.ts | ||
|
||
import { isHTTPAccessFallbackError, type HTTPAccessFallbackError } from "./http-access-fallback"; | ||
import { isRedirectError, type RedirectError } from "./redirect"; | ||
|
||
/** | ||
* Returns true if the error is a navigation signal error. These errors are | ||
* thrown by user code to perform navigation operations and interrupt the React | ||
* render. | ||
*/ | ||
export function isNextRouterError(error: unknown): error is RedirectError | HTTPAccessFallbackError { | ||
return isRedirectError(error) || isHTTPAccessFallbackError(error); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters