Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ssr-renderers-injected-routes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes SSR builds failing with "No matching renderer found" when a project only has injected routes and no `src/pages/` directory
4 changes: 2 additions & 2 deletions packages/astro/src/actions/integration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AstroError } from '../core/errors/errors.js';
import { ActionsWithoutServerOutputError } from '../core/errors/errors-data.js';
import { hasNonPrerenderedProjectRoute } from '../core/routing/helpers.js';
import { hasNonPrerenderedRoute } from '../core/routing/helpers.js';
import { viteID } from '../core/util.js';
import type { AstroSettings } from '../types/astro.js';
import type { AstroIntegration } from '../types/public/integrations.js';
Expand Down Expand Up @@ -41,7 +41,7 @@ export default function astroIntegrationActionsRouteHandler({
});
},
'astro:routes:resolved': ({ routes }) => {
if (!hasNonPrerenderedProjectRoute(routes)) {
if (!hasNonPrerenderedRoute(routes)) {
const error = new AstroError(ActionsWithoutServerOutputError);
error.stack = undefined;
throw error;
Expand Down
16 changes: 9 additions & 7 deletions packages/astro/src/core/routing/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,26 +75,28 @@ export function routeHasHtmlExtension(route: RouteData): boolean {
);
}

export function hasNonPrerenderedProjectRoute(
export function hasNonPrerenderedRoute(
routes: Array<Pick<RouteData, 'type' | 'origin' | 'prerender'>>,
options?: { includeEndpoints?: boolean },
options?: { includeEndpoints?: boolean; includeExternal?: boolean },
): boolean;
export function hasNonPrerenderedProjectRoute(
export function hasNonPrerenderedRoute(
routes: Array<Pick<IntegrationResolvedRoute, 'type' | 'origin' | 'isPrerendered'>>,
options?: { includeEndpoints?: boolean },
options?: { includeEndpoints?: boolean; includeExternal?: boolean },
): boolean;
export function hasNonPrerenderedProjectRoute(
export function hasNonPrerenderedRoute(
routes: Array<
| Pick<RouteData, 'type' | 'origin' | 'prerender'>
| Pick<IntegrationResolvedRoute, 'type' | 'origin' | 'isPrerendered'>
>,
options?: { includeEndpoints?: boolean },
options?: { includeEndpoints?: boolean; includeExternal?: boolean },
): boolean {
const includeEndpoints = options?.includeEndpoints ?? true;
const includeExternal = options?.includeExternal ?? false;
const routeTypes: ReadonlyArray<string> = includeEndpoints ? ['page', 'endpoint'] : ['page'];
const origins: ReadonlyArray<string> = includeExternal ? ['project', 'external'] : ['project'];

return routes.some((route) => {
const isPrerendered = 'isPrerendered' in route ? route.isPrerendered : route.prerender;
return routeTypes.includes(route.type) && route.origin === 'project' && !isPrerendered;
return routeTypes.includes(route.type) && origins.includes(route.origin) && !isPrerendered;
});
}
5 changes: 3 additions & 2 deletions packages/astro/src/vite-plugin-renderers/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ConfigEnv, Plugin as VitePlugin } from 'vite';
import { ASTRO_VITE_ENVIRONMENT_NAMES } from '../core/constants.js';
import { hasNonPrerenderedProjectRoute } from '../core/routing/helpers.js';
import { hasNonPrerenderedRoute } from '../core/routing/helpers.js';
import type { ServerIslandsState } from '../core/server-islands/shared-state.js';
import type { AstroSettings, RoutesList } from '../types/astro.js';

Expand Down Expand Up @@ -40,8 +40,9 @@ export default function vitePluginRenderers(options: PluginOptions): VitePlugin
this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr &&
renderers.length > 0 &&
!options.serverIslandsState.hasIslands() &&
!hasNonPrerenderedProjectRoute(options.routesList.routes, {
!hasNonPrerenderedRoute(options.routesList.routes, {
includeEndpoints: false,
includeExternal: true,
})
) {
return { code: `export const renderers = [];` };
Expand Down
28 changes: 28 additions & 0 deletions packages/astro/test/units/routing/routing-helpers.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @ts-check
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { hasNonPrerenderedRoute } from '../../../dist/core/routing/helpers.js';

describe('hasNonPrerenderedRoute', () => {
it('returns true when a non-prerendered project page exists', () => {
const routes = [{ type: 'page', origin: 'project', prerender: false }];
assert.equal(hasNonPrerenderedRoute(routes), true);
});

it('returns false when all project pages are prerendered', () => {
const routes = [{ type: 'page', origin: 'project', prerender: true }];
assert.equal(hasNonPrerenderedRoute(routes), false);
});

it('excludes endpoints when includeEndpoints is false', () => {
const routes = [{ type: 'endpoint', origin: 'project', prerender: false }];
assert.equal(hasNonPrerenderedRoute(routes, { includeEndpoints: false }), false);
assert.equal(hasNonPrerenderedRoute(routes, { includeEndpoints: true }), true);
});

it('returns true for injected (external) non-prerendered pages when includeExternal is true', () => {
const routes = [{ type: 'page', origin: 'external', prerender: false }];
assert.equal(hasNonPrerenderedRoute(routes, { includeExternal: true }), true);
assert.equal(hasNonPrerenderedRoute(routes), false);
});
});
Loading