Repository: remix-run/react-router
File: packages/react-router-dev/vite/plugin.ts (TypeScript)

Code before:
    // render into `/items.json`
    let leafRoute = matches ? matches[matches.length - 1].route : null;
    let manifestRoute = leafRoute ? build.routes[leafRoute.id]?.module : null;
    let isResourceRoute =
      manifestRoute && !manifestRoute.default && !manifestRoute.ErrorBoundary;

    if (isResourceRoute) {
      invariant(leafRoute);
      invariant(manifestRoute);
      if (manifestRoute.loader) {
        // Prerender a .data file for turbo-stream consumption
        await prerenderData(
          handler,
          path,
          [leafRoute.id],
          clientBuildDirectory,
          reactRouterConfig,
          viteConfig,
          { headers }
        );
        // Prerender a raw file for external consumption
        await prerenderResourceRoute(
          handler,
          path,
          clientBuildDirectory,
          reactRouterConfig,
          viteConfig,
          { headers }
        );
      } else {
        viteConfig.logger.warn(
          `⚠️ Skipping prerendering for resource route without a loader: ${leafRoute?.id}`
        );
      }
    } else {
      let hasLoaders = matches.some(
        (m) => build.assets.routes[m.route.id]?.hasLoader
      );
      let data: string | undefined;
      if (!isResourceRoute && hasLoaders) {
        data = await prerenderData(
          handler,
          path,
          null,
          clientBuildDirectory,
          reactRouterConfig,
          viteConfig,
          { headers }
        );
      }


Review comment (suggestion):
It's valid for an individual server bundle _not_ to contain a matching route. Instead, we now perform validation earlier as a separate step, checking the routes coming from `react-router.config.ts` which contains everything, not just the potential subset in this server build.

Code after:
    viteConfig,
    serverBuildDirectory,
    serverBuildPath
  );

  let routes = createPrerenderRoutes(reactRouterConfig.routes);
  for (let path of build.prerender) {
    let matches = matchRoutes(routes, `/${path}/`.replace(/^\/\/+/, "/"));
    if (!matches) {
      throw new Error(
        `Unable to prerender path because it does not match any routes: ${path}`
      );
    }
  }

  let buildRoutes = createPrerenderRoutes(build.routes);
  let headers = {
    // Header that can be used in the loader to know if you're running at
    // build time or runtime
    "X-React-Router-Prerender": "yes",
  };
  for (let path of build.prerender) {
    // Ensure we have a leading slash for matching
    let matches = matchRoutes(buildRoutes, `/${path}/`.replace(/^\/\/+/, "/"));
    if (!matches) {
      continue;
    }
    // When prerendering a resource route, we don't want to pass along the
    // `.data` file since we want to prerender the raw Response returned from
    // the loader.  Presumably this is for routes where a file extension is
    // already included, such as `app/routes/items[.json].tsx` that will
    // render into `/items.json`
    let leafRoute = matches ? matches[matches.length - 1].route : null;
    let manifestRoute = leafRoute ? build.routes[leafRoute.id]?.module : null;
    let isResourceRoute =
      manifestRoute && !manifestRoute.default && !manifestRoute.ErrorBoundary;

    if (isResourceRoute) {
      invariant(leafRoute);
      invariant(manifestRoute);
      if (manifestRoute.loader) {
        // Prerender a .data file for turbo-stream consumption
        await prerenderData(
          handler,
          path,
          [leafRoute.id],
          clientBuildDirectory,
          reactRouterConfig,
          viteConfig,
          { headers }
        );