astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
100 lines (99 loc) • 4.05 kB
JavaScript
import { matchAllRoutes } from "./match.js";
import { getSortedPreloadedMatches } from "../../prerender/routing.js";
import { getProps } from "../render/index.js";
import { getCustom404Route } from "./helpers.js";
import { NoMatchingStaticPathFound } from "../errors/errors-data.js";
import { isAstroError } from "../errors/errors.js";
import { getEnvironment } from "../environment/index.js";
import { getLogger } from "../logger/manifest-logger.js";
import { getRouteCache } from "../render/route-cache.js";
import { getRouteTable } from "./route-table.js";
import { getErrorRoutePath } from "../../i18n/error-routes.js";
export async function matchRoute(manifest, pathname, { prerenderOnly } = {}) {
const logger = getLogger(manifest);
const routeCache = getRouteCache(manifest);
const env = getEnvironment(manifest);
// The single fresh route table: matching, the custom-404 fallback,
// and every other consumer read the same atomically-swapped list.
const routesList = getRouteTable(manifest);
const matches = matchAllRoutes(pathname, routesList);
const preloadedMatches = getSortedPreloadedMatches({
matches,
manifest
});
let firstError = null;
let skippedPrerenderOnly = false;
for await (const { route: maybeRoute, filePath } of preloadedMatches) {
// When running as the prerender handler, skip non-prerendered routes
// before importing their components. Their modules may use runtime-
// specific APIs (e.g. cloudflare:workers) unavailable in the prerender
// environment.
if (prerenderOnly && !maybeRoute.prerender) {
skippedPrerenderOnly = true;
continue;
}
// attempt to get static paths
// if this fails, we have a bad URL match!
try {
await getProps({
mod: await env.getComponentByRoute(manifest, maybeRoute),
routeData: maybeRoute,
routeCache,
pathname,
logger,
serverLike: manifest.serverLike,
base: manifest.base,
trailingSlash: manifest.trailingSlash
});
return {
route: maybeRoute,
filePath,
resolvedPathname: pathname
};
} catch (e) {
// Ignore error for no matching static paths
if (isAstroError(e) && e.title === NoMatchingStaticPathFound.title) {
continue;
}
// Store the first error but keep trying other candidate routes.
// A user error in one route's getStaticPaths() should not prevent
// other matching routes from being attempted.
firstError ??= e;
continue;
}
}
// If we exhausted all candidates and one threw a non-routing error,
// re-throw it so the dev server can surface it.
if (firstError) {
throw firstError;
}
// Try without `.html` extensions or `index.html` in request URLs to mimic
// routing behavior in production builds. This supports both file and directory
// build formats, and is necessary based on how the manifest tracks build targets.
const altPathname = pathname.replace(/\/index\.html$/, "/").replace(/\.html$/, "");
if (altPathname !== pathname) {
return await matchRoute(manifest, altPathname, { prerenderOnly });
}
// A non-prerendered route matched but was skipped above. Don't warn or fall
// back to the 404 route (which may be prerendered and would shadow the SSR
// route): returning undefined lets the caller mark the request as not
// handled, so it falls through to the SSR handler and its own full matching.
if (skippedPrerenderOnly) {
return undefined;
}
if (matches.length) {
const possibleRoutes = matches.flatMap((route) => route.component);
logger.warn("router", `${NoMatchingStaticPathFound.message(pathname)}\n\n${NoMatchingStaticPathFound.hint(possibleRoutes)}`);
}
const errorRoutePath = getErrorRoutePath(pathname, 404, routesList.routes, manifest.i18n?.locales, manifest.trailingSlash === "always");
const custom404 = routesList.routes.find((route) => route.route === errorRoutePath) ?? getCustom404Route(routesList);
if (custom404) {
const filePath = new URL(`./${custom404.component}`, manifest.rootDir);
return {
route: custom404,
filePath,
resolvedPathname: pathname
};
}
return undefined;
}