astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
88 lines (87 loc) • 3.9 kB
JavaScript
import { provideCache } from "../cache/handler.js";
import { getEnvironment } from "../environment/index.js";
import { FetchState } from "../fetch/fetch-state.js";
import { getLogger } from "../logger/manifest-logger.js";
import { handleMiddleware } from "../middleware/astro-middleware.js";
import { handlePages } from "../pages/handler.js";
import { getCustom404Route, getCustom500Route } from "../routing/helpers.js";
import { getRouteTable } from "../routing/route-table.js";
import { isAstroError } from "./index.js";
import { MiddlewareNoDataOrNextCalled, MiddlewareNotAResponse } from "./errors-data.js";
import { rewroteToEmptyErrorResponse } from "./handler.js";
/**
* The dev-server error strategy. Renders custom 404/500 routes if the user
* has them, otherwise throws so Vite's dev overlay is shown. Shared between
* the Vite dev server and the non-runnable dev pipeline; only
* `shouldInjectCspMetaTags` differs between them (carried by the
* environment record's `injectCspMetaTagsOnErrorPages` static).
*/
export async function renderDevError(manifest, request, { skipMiddleware = false, error, status, response: _response, pathname, ...resolvedRenderOptions }, { shouldInjectCspMetaTags }) {
// we always throw when we have Astro errors around the middleware
if (isAstroError(error) && [MiddlewareNoDataOrNextCalled.name, MiddlewareNotAResponse.name].includes(error.name)) {
throw error;
}
const resolvedPathname = pathname ?? new FetchState(manifest, request).pathname;
const renderRoute = async (routeData) => {
try {
const preloadedComponent = await getEnvironment(manifest).getComponentByRoute(manifest, routeData);
const errorState = new FetchState(manifest, request);
errorState.skipMiddleware = skipMiddleware;
errorState.clientAddress = resolvedRenderOptions.clientAddress;
errorState.shouldInjectCspMetaTags = shouldInjectCspMetaTags ? !!manifest.csp : false;
errorState.routeData = routeData;
errorState.pathname = resolvedPathname;
errorState.status = status;
errorState.componentInstance = preloadedComponent;
errorState.locals = resolvedRenderOptions.locals ?? {};
errorState.initialProps = { error };
await provideCache(errorState);
const response = await handleMiddleware(errorState, handlePages);
// A middleware rewrite issued while rendering the error page swaps the
// state's routeData away from the error route. If that hijacked render
// produced another empty reroutable error response, retry rendering the
// error page without middleware (same fallback used when middleware throws).
if (rewroteToEmptyErrorResponse(skipMiddleware, routeData, errorState.routeData, response)) {
return renderDevError(manifest, request, {
...resolvedRenderOptions,
status,
error,
skipMiddleware: true,
pathname: resolvedPathname
}, { shouldInjectCspMetaTags });
}
if (error) {
// Log useful information that the custom 500 page may not display unlike the default error overlay
getLogger(manifest).error("router", error.stack || error.message);
}
return response;
} catch (_err) {
if (skipMiddleware === false) {
return renderDevError(manifest, request, {
...resolvedRenderOptions,
status: 500,
skipMiddleware: true,
error: _err,
pathname: resolvedPathname
}, { shouldInjectCspMetaTags });
}
// If even skipping the middleware isn't enough to prevent the error, show the dev overlay
throw _err;
}
};
// Custom error routes are read off the single per-manifest route table, so
// they stay HMR-fresh.
if (status === 404) {
const custom404 = getCustom404Route(getRouteTable(manifest));
if (custom404) {
return renderRoute(custom404);
}
}
const custom500 = getCustom500Route(getRouteTable(manifest));
// Show dev overlay
if (!custom500) {
throw error;
} else {
return renderRoute(custom500);
}
}