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/calm-bats-create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/preact': patch
---

Fixed an issue where the Preact integration would incorrectly intercept React 19 components, triggering "Invalid hook call" error logs.
7 changes: 7 additions & 0 deletions .changeset/fast-wolves-render.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'astro': patch
---

Improves `.astro` component SSR rendering performance by up to 2x.

This includes several optimizations to the way that Astro generates and renders components on the server. These are mostly micro-optimizations, but they add up to a significant improvement in performance. Most pages will benefit, but pages with many components will see the biggest improvement, as will pages with lots of strings (e.g. text-heavy pages with lots of HTML elements).
6 changes: 6 additions & 0 deletions .changeset/giant-bananas-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'astro': patch
'@astrojs/node': patch
---

Add a default body size limit for server actions to prevent oversized requests from exhausting memory.
5 changes: 5 additions & 0 deletions .changeset/jolly-dots-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/svelte': patch
---

Updates `svelte` to v5.51.5
5 changes: 5 additions & 0 deletions .changeset/tame-lemons-probe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"astro": patch
---

Fixes an issue where allowlists were not being enforced when handling remote images
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ jobs:
contents: read
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Biome
Expand Down
24 changes: 13 additions & 11 deletions benchmark/bench/codspeed-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ import { astroBin, makeProject } from './_util.js';

/**
* Setup script for codspeed benchmarks.
* This builds the render-bench project which is required for the rendering benchmarks.
* This builds the benchmark projects which are required for the rendering benchmarks.
* This is separated out so it can run in a separate CI job since it takes a long time.
*/
async function setup() {
console.log('Setting up render-bench project...');
const render = await makeProject('render-bench');
const root = fileURLToPath(render);
for (const name of ['render-bench', 'rendering-perf']) {
console.log(`Setting up ${name} project...`);
const projectDir = await makeProject(name);
const root = fileURLToPath(projectDir);

console.log(`Building project at ${root}...`);
await exec(astroBin, ['build'], {
nodeOptions: {
cwd: root,
stdio: 'inherit',
},
});
console.log(`Building project at ${root}...`);
await exec(astroBin, ['build'], {
nodeOptions: {
cwd: root,
stdio: 'inherit',
},
});
}
}

setup().catch((error) => {
Expand Down
90 changes: 90 additions & 0 deletions benchmark/bench/rendering-perf.bench.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { beforeAll, bench, describe } from 'vitest';

/**
* Rendering performance benchmarks targeting specific hot paths from RENDERING_PERF_PLAN.md.
*
* Each page isolates a different performance pattern:
* - many-components: #1 markHTMLString, #2 isHTMLString, #6 validateComponentProps
* - many-expressions: #2 isHTMLString, #5 renderChild dispatch, #10 escapeHTML
* - many-head-elements: #3 head dedup O(N²)
* - many-slots: #9 eager slot prerendering
* - large-array: #8 BufferedRenderer per array child
* - static-heavy: #1 markHTMLString baseline, #11/#12 future comparison
*
* Requires: pnpm run build:bench
*/

const projectRoot = new URL('../projects/rendering-perf/', import.meta.url);

let streamingApp;
let nonStreamingApp;

beforeAll(async () => {
const entry = new URL('./dist/server/entry.mjs', projectRoot);

if (!existsSync(fileURLToPath(entry))) {
throw new Error(
'rendering-perf project not built. Please run `pnpm run build:bench` before running the benchmarks.',
);
}

const { createApp } = await import(entry);
streamingApp = createApp(true);
nonStreamingApp = createApp(false);
}, 900000);

// Non-streaming (prerender path) — this is the primary target for most optimizations
// since it's the path where all the overhead is synchronous and measurable.
describe('Rendering perf (non-streaming)', () => {
bench('many-components (markHTMLString, isHTMLString, validateProps)', async () => {
const request = new Request(new URL('http://example.com/many-components'));
await nonStreamingApp.render(request);
});

bench('many-expressions (renderChild dispatch, escapeHTML)', async () => {
const request = new Request(new URL('http://example.com/many-expressions'));
await nonStreamingApp.render(request);
});

bench('many-head-elements (head dedup)', async () => {
const request = new Request(new URL('http://example.com/many-head-elements'));
await nonStreamingApp.render(request);
});

bench('many-slots (eager slot prerendering)', async () => {
const request = new Request(new URL('http://example.com/many-slots'));
await nonStreamingApp.render(request);
});

bench('large-array (BufferedRenderer per child)', async () => {
const request = new Request(new URL('http://example.com/large-array'));
await nonStreamingApp.render(request);
});

bench('static-heavy (markHTMLString baseline)', async () => {
const request = new Request(new URL('http://example.com/static-heavy'));
await nonStreamingApp.render(request);
});
});

// Streaming path — included for comparison. Optimizations to the sync path
// (#1, #2, #5, #6) should show up here too, but BufferedRenderer (#8) and
// slot prerendering (#9) may behave differently.
describe('Rendering perf (streaming)', () => {
bench('many-components [streaming]', async () => {
const request = new Request(new URL('http://example.com/many-components'));
await streamingApp.render(request);
});

bench('many-expressions [streaming]', async () => {
const request = new Request(new URL('http://example.com/many-expressions'));
await streamingApp.render(request);
});

bench('large-array [streaming]', async () => {
const request = new Request(new URL('http://example.com/large-array'));
await streamingApp.render(request);
});
});
Loading
Loading