Skip to content

Commit 07f6610

Browse files
matthewpematipico
andauthored
Avoid SSR renderer imports for API-only SSR builds (#15507)
* fix: avoid SSR renderer imports for API-only builds * fix: map Astro command to Vite env * rebase and fix lock file --------- Co-authored-by: ematipico <estoppa@cloudflare.com>
1 parent a509941 commit 07f6610

File tree

10 files changed

+118
-3
lines changed

10 files changed

+118
-3
lines changed

.changeset/quiet-owls-jump.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Avoid bundling SSR renderers when only API endpoints are dynamic

packages/astro/src/core/create-vite.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,11 @@ export async function createVite(
121121
appType: 'custom',
122122
plugins: [
123123
serializedManifestPlugin({ settings, command, sync }),
124-
vitePluginRenderers({ settings }),
124+
vitePluginRenderers({
125+
settings,
126+
routesList,
127+
command: command === 'dev' ? 'serve' : 'build',
128+
}),
125129
vitePluginStaticPaths(),
126130
await astroPluginRoutes({ routesList, settings, logger, fsMod: fs, command }),
127131
astroVirtualManifestPlugin(),

packages/astro/src/vite-plugin-renderers/index.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
1-
import type { Plugin as VitePlugin } from 'vite';
2-
import type { AstroSettings } from '../types/astro.js';
1+
import type { ConfigEnv, Plugin as VitePlugin } from 'vite';
2+
import { ASTRO_VITE_ENVIRONMENT_NAMES } from '../core/constants.js';
3+
import type { AstroSettings, RoutesList } from '../types/astro.js';
34

45
export const ASTRO_RENDERERS_MODULE_ID = 'virtual:astro:renderers';
56
const RESOLVED_ASTRO_RENDERERS_MODULE_ID = `\0${ASTRO_RENDERERS_MODULE_ID}`;
67

78
interface PluginOptions {
89
settings: AstroSettings;
10+
routesList: RoutesList;
11+
command: ConfigEnv['command'];
12+
}
13+
14+
/**
15+
* Checks whether any non-prerendered route needs component rendering (i.e., is a page).
16+
* Internal routes like `_server-islands` are excluded because they only need renderers
17+
* when server islands are actually used, and those are detected separately during the build.
18+
*/
19+
function ssrBuildNeedsRenderers(routesList: RoutesList): boolean {
20+
return routesList.routes.some(
21+
(route) => route.type === 'page' && !route.prerender && route.origin !== 'internal',
22+
);
923
}
1024

1125
export default function vitePluginRenderers(options: PluginOptions): VitePlugin {
@@ -29,6 +43,15 @@ export default function vitePluginRenderers(options: PluginOptions): VitePlugin
2943
id: new RegExp(`^${RESOLVED_ASTRO_RENDERERS_MODULE_ID}$`),
3044
},
3145
handler() {
46+
if (
47+
options.command === 'build' &&
48+
this.environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr &&
49+
renderers.length > 0 &&
50+
!ssrBuildNeedsRenderers(options.routesList)
51+
) {
52+
return { code: `export const renderers = [];` };
53+
}
54+
3255
if (renderers.length > 0) {
3356
const imports: string[] = [];
3457
const exports: string[] = [];
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import vue from '@astrojs/vue';
2+
import { defineConfig } from 'astro/config';
3+
4+
export default defineConfig({
5+
integrations: [vue()],
6+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "@test/ssr-renderers-static-vue",
3+
"version": "0.0.0",
4+
"private": true,
5+
"dependencies": {
6+
"@astrojs/vue": "workspace:*",
7+
"astro": "workspace:*",
8+
"vue": "^3.5.27"
9+
}
10+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<div class="counter">Hello from Vue</div>
3+
</template>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const prerender = false;
2+
3+
export async function GET() {
4+
return new Response(JSON.stringify({ ok: true }), {
5+
headers: {
6+
'content-type': 'application/json',
7+
},
8+
});
9+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
export const prerender = true;
3+
import Counter from '../components/Counter.vue';
4+
---
5+
6+
<html>
7+
<head>
8+
<title>Static Vue</title>
9+
</head>
10+
<body>
11+
<h1>Static page</h1>
12+
<Counter />
13+
</body>
14+
</html>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import assert from 'node:assert/strict';
2+
import { before, describe, it } from 'node:test';
3+
import testAdapter from './test-adapter.js';
4+
import { loadFixture } from './test-utils.js';
5+
6+
describe('SSR renderers with static framework pages', () => {
7+
/** @type {import('./test-utils.js').Fixture} */
8+
let fixture;
9+
10+
before(async () => {
11+
fixture = await loadFixture({
12+
root: './fixtures/ssr-renderers-static-vue/',
13+
output: 'server',
14+
adapter: testAdapter(),
15+
});
16+
await fixture.build();
17+
});
18+
19+
it('does not include SSR renderers when only endpoints are dynamic', async () => {
20+
const app = await fixture.loadTestAdapterApp();
21+
assert.ok(app.manifest, 'expected runtime manifest from SSR build');
22+
assert.equal(app.manifest.renderers.length, 0);
23+
});
24+
25+
it('prerendered page renders the Vue component', async () => {
26+
const html = await fixture.readFile('/client/index.html');
27+
assert.ok(html.includes('Hello from Vue'));
28+
});
29+
});

pnpm-lock.yaml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)