Skip to content

Reduce per-request URL parsing and allocations in the SSR request pipeline#17227

Open
iseraph-dev wants to merge 3 commits into
withastro:mainfrom
iseraph-dev:perf/ssr-request-pipeline
Open

Reduce per-request URL parsing and allocations in the SSR request pipeline#17227
iseraph-dev wants to merge 3 commits into
withastro:mainfrom
iseraph-dev:perf/ssr-request-pipeline

Conversation

@iseraph-dev

@iseraph-dev iseraph-dev commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

The core SSR request path parses request.url and rebuilds a few small things more often than it has to. This cuts that repeated work on the hot path. Nothing about the rendered output changes, and no public API moves; it's all internal.

This started as a bigger PR and was trimmed after review. The cross-adapter URL sharing (a new RenderOptions.url option) and the createOutgoingHttpHeaders rewrite are out, and the adapter side will come back on its own as a minor. What's left here is internal to astro core and stays a patch.

What changed

  • Domain-based i18n only looks at the Host header when a domains-* strategy is configured. That condition is now worked out once in the constructor, so every other app skips the probe, and the URL parse behind it, in both match() and render().
  • TrailingSlashHandler uses the raw pathname and search that FetchState captures before it normalizes the URL, instead of parsing request.url a second time to get them back.
  • getParams checks the route's own pattern and then its fallback routes, stopping at the first match, rather than building throwaway arrays with .map().map().find().
  • The in-memory cache provider reuses the URL already sitting on context.url (and checks the request method first) instead of parsing context.request.url again.
  • Domain-based i18n parses each domainLookupTable key once and keeps it per table, instead of re-parsing every key on every request.

Testing

No behavior change, so nothing new to test. The paths above are covered by the existing unit suites (routing, i18n, app, cache, fetch), which pass, and the typecheck is clean.

Benchmarks

Micro-benchmarks, median of 7 runs on the same machine, for the changes that stayed in this PR:

Before After
Domain i18n lookup (4 domains) 1331 ns 285 ns
Cache request (memory provider) 474 ns 243 ns
getParams matching 54 ns 40 ns

On a render-heavy page this is lost in the noise, since HTML rendering dominates. It shows up more on light responses like endpoints, redirects and 304s, where per-request setup is a bigger slice of the work.

Investigated and measured with help from Claude Opus 4.8.

@changeset-bot

changeset-bot Bot commented Jun 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bc79d7d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 393 packages
Name Type
astro Patch
@e2e/astro-linked-lib Patch
@e2e/actions-blog Patch
@e2e/actions-react-19 Patch
@e2e/astro-component Patch
@e2e/astro-envs Patch
@e2e/astro-island-hydration-error Patch
@test/astro-cloudflare-node-prerender-mdx Patch
@test/astro-cloudflare Patch
@e2e/content-collections Patch
@e2e/csp-server-islands Patch
@e2e/css Patch
@test/custom-client-directives Patch
@e2e/dev-toolbar Patch
@e2e/error-cyclic Patch
@e2e/error-sass Patch
@e2e/errors Patch
@e2e/hydration-race Patch
@e2e/i18n Patch
@test/nested-style-bug-e22e Patch
@e2e/preact-compat-component Patch
@e2e/preact-component Patch
@e2e/preact-lazy-component Patch
@e2e/prefetch Patch
@e2e/react-component Patch
@e2e/server-islands-key Patch
@e2e/server-islands Patch
@e2e/solid-circular Patch
@e2e/solid-component Patch
@e2e/solid-recurse Patch
@e2e/svelte-component Patch
@e2e/e2e-tailwindcss Patch
@e2e/ts-resolution Patch
@e2e/view-transitions Patch
@e2e/vite-virtual-modules Patch
@e2e/vue-component Patch
@performance/md Patch
@performance/mdoc Patch
@performance/mdx Patch
@test/0-css Patch
fake-astro-library Patch
@test/actions Patch
@test/alias-css-url Patch
@test/alias-path-alias-style Patch
@test/ts-paths-no-baseurl Patch
@test/aliases-tsconfig Patch
@test/aliases Patch
@test/api-routes Patch
@test/asset-query-params-chunks Patch
@test/asset-url-base Patch
@test/astro-pages Patch
@test/astro-assets-prefix Patch
@test/astro-assets Patch
@test/astro-basic Patch
@test/astro-check-errors Patch
@test/astro-check-no-errors Patch
@test/astro-check-watch Patch
@test/astro-children Patch
@test/astro-client-only Patch
@test/astro-component-bundling Patch
@test/astro-component-code Patch
@test/astro-css-bundling Patch
@test/astro-dev-headers Patch
@test/astro-dev-http2 Patch
@test/astro-doctype Patch
@test/astro-dynamic Patch
@test/astro-env-content-collections Patch
@test/astro-env-required-public Patch
@test/astro-env-server-fail Patch
@test/astro-env-server-secret Patch
@test/astro-env Patch
@test/astro-envs Patch
@test/astro-expr Patch
@test/astro-get-static-paths Patch
@test/astro-head Patch
@test/astro-manifest-client-script Patch
@test/astro-manifest-invalid Patch
@test/astro-manifest Patch
@test/astro-markdown-frontmatter-injection Patch
@test/astro-markdown-plugins Patch
@test/astro-markdown-remarkRehype Patch
@test/astro-markdown-skiki-default-color Patch
@test/astro-markdown-skiki-langs Patch
@test/astro-markdown-skiki-themes-custom Patch
@test/astro-markdown-skiki-themes-integrated Patch
@test/astro-markdown-skiki-wrap-false Patch
@test/astro-markdown-skiki-wrap-null Patch
@test/astro-markdown-skiki-wrap-true Patch
@test/astro-markdown-url Patch
@test/astro-markdown Patch
@test/astro-mode Patch
@test/astro-page-directory-url Patch
@test/astro-partial-html Patch
@test/astro-preview-allowed-hosts Patch
@test/astro-preview-headers Patch
@test/astro-public Patch
@test/astro-script-template-dedup Patch
@test/astro-scripts Patch
@test/astro-slots-nested Patch
@test/concurrency Patch
@test/build-readonly-file Patch
@test/cache-memory-query-include Patch
@test/cache-memory-query Patch
@test/client-address-node Patch
@test/client-only-css-chunk-leak Patch
@test/code-component Patch
@test/component-library Patch
@test/config-vite-css-target Patch
@test/config-vite Patch
@test/react-container Patch
@test/content-with-spaces-in-folder-name Patch
@test/content-collection-picture-render Patch
@example/content-collection-references Patch
@test/content-collection-tla-svg Patch
@test/content-collections-base Patch
@test/content-collections-empty-dir Patch
@test/content-collections-empty-md-file Patch
@test/content-collections-image-hmr Patch
@test/content-collections-mutation Patch
@test/content-collections-number-id Patch
@test/content-collections-type-inference Patch
@test/content-collections-with-config-mjs Patch
@test/content-collections Patch
@test/content-frontmatter Patch
@test/content-intellisense Patch
@test/content-layer-loader-schema-function Patch
@test/content-layer-remark-plugins Patch
@test/content-layer Patch
@test/content-ssr-integration Patch
@test/content-static-paths-integration Patch
@test/content Patch
@test/core-image-data-url Patch
@test/core-image-deletion-ssr Patch
@test/core-image-deletion Patch
@test/core-image-errors Patch
@test/core-image-fs-config Patch
@test/core-image-remark-infersize Patch
@test/core-image-layout Patch
@test/core-image-picture-emit-file Patch
@test/core-image-remark-imgattr Patch
@test/core-image-ssg Patch
@test/core-image-ssr Patch
@test/core-image-svg-in-client Patch
@test/core-image-svg Patch
@test/core-image-unconventional-settings Patch
@test/core-image Patch
@test/csp-adapter Patch
@test/csp-fonts Patch
@test/csp Patch
@test/css-assets Patch
@test/css-dangling-references Patch
@test/css-deduplication Patch
@test/css-double-bundle Patch
@test/css-dynamic-import-dev Patch
@test/css-import-as-inline Patch
@test/css-inline-stylesheets Patch
@test/css-no-code-split Patch
@test/css-path-case Patch
@test/css-pure-chunk-query-params Patch
@test/custom-404-injected-from-dep Patch
@test/custom-404-pkg Patch
custom-fetch-error-pages Patch
@test/custom-renderer Patch
@test/data-collections-schema Patch
@test/data-collections Patch
@test/debug-component Patch
@test/dev-container Patch
@test/dev-render Patch
@test/dev-request-url Patch
@test/dynamic-endpoint-collision Patch
@test/dynamic-route-build-file Patch
@test/endpoint-routing Patch
@test/error-bad-js Patch
@test/error-build-location Patch
@test/error-non-error Patch
@test/extension-matching Patch
@test/fetch Patch
@test/fonts Patch
@test/astro-fontsource-package Patch
@test/get-static-paths-pages Patch
@test/glob-pages-css Patch
@test/head-propagation-prerender-env Patch
@test/hmr-markdown Patch
@test/hmr-new-page Patch
@test/hmr-slots-render Patch
@test/hoisted-imports Patch
@test/html-component Patch
@test/html-escape Patch
@test/html-page Patch
@test/html-slots Patch
@test/hydration-race Patch
@test/i18n-client-import Patch
@test/i18n-css-leak-basic Patch
@test/import-ts-with-js Patch
@test/impostor-md-file Patch
@test/integration-add-page-extension Patch
@test/integration-server-setup Patch
@test/jsx-queue-rendering Patch
@test/large-array-solid Patch
@test/legacy-collections-backwards-compat Patch
@test/lightningcss-scoped-nesting Patch
@test/live-loaders Patch
@test/markdown Patch
@test/middleware-dev Patch
@test/middleware-full-ssr Patch
@test/middleware-no-user-middlewaqre Patch
@test/middleware-tailwind Patch
@test/minification-html-default Patch
@test/minification-html-jsx Patch
@test/minification-html Patch
@test/non-ascii-path Patch
@test/non-html-pages Patch
@test/page-format Patch
@test/page-level-styles Patch
@test/parallel-components Patch
@test/partials-css-boundary Patch
@test/partials Patch
@test/passthrough-image-service Patch
@test/postcss Patch
@test/preact-compat-component Patch
@test/preact-component Patch
@test/remote-css Patch
@test/request-signal Patch
@test/reuse-injected-entrypoint Patch
@test/root-srcdir-css Patch
@test/scoped-style-strategy Patch
@test/server-entry-fake-adapter Patch
@test/server-entry Patch
@test/server-islands-hybrid Patch
@test/server-islands-ssr Patch
@test/sessions Patch
@test/slots-preact Patch
@test/slots-react Patch
@test/slots-solid Patch
@test/slots-svelte Patch
@test/slots-vue Patch
@test/solid-component Patch
@test/sourcemap Patch
@test/space-in-folder-name Patch
@test/special-chars-in-component-imports Patch
@test/ssr-assets Patch
@test/ssr-dynamic Patch
@test/ssr-partytown Patch
@test/ssr-prerender-get-static-paths Patch
@test/ssr-prerender Patch
@test/ssr-preview Patch
@test/ssr-renderers-static-vue Patch
@test/ssr-request Patch
@test/ssr-hoisted-script Patch
@test/ssr-scripts Patch
@test/static-build-code-component Patch
@test/static-build-dir Patch
@test/static-build-frameworks Patch
@test/static-build-page-url-format Patch
@test/static-build-ssr Patch
@test/static-build Patch
@test/static-redirect Patch
@test/svelte-component Patch
@test/svg-deduplication Patch
@test/tailwindcss Patch
@e2e/third-party-astro Patch
@test/url-import-suffix Patch
@test/view-transitions Patch
@test/virtual-astro-file Patch
@test/vitest Patch
@test/vue-component Patch
@test/vue-with-multi-renderer Patch
@test/alpinejs-basics Patch
@test/alpinejs-directive Patch
@test/alpinejs-plugin-script-import Patch
@test/astro-cloudflare-allowed-hosts Patch
@test/astro-cloudflare-astro-dev-platform Patch
@test/astro-cloudflare-astro-env Patch
@test/astro-cloudflare-binding-image-service Patch
@test/astro-cloudflare-cache-provider-wait-until Patch
@test/astro-cloudflare-cache-provider Patch
@test/astro-cloudflare-client-address Patch
@test/astro-cloudflare-compile-image-service Patch
@test/astro-cloudflare-custom-entryfile Patch
@test/astro-cloudflare-dev-image-endpoint Patch
@test/astro-cloudflare-external-image-service Patch
@test/astro-cloudflare-external-redirects Patch
@test/astro-cloudflare-internal-redirects Patch
@test/astro-cloudflare-no-output Patch
@test/astro-cloudflare-prerender-node-env Patch
@test/astro-cloudflare-prerender-queue-consumers Patch
@test/astro-cloudflare-prerender-styles Patch
@test/astro-cloudflare-prerenderer-errors Patch
@test/astro-cloudflare-prerenderer-render-error Patch
@test/routing-priority-cloudflare Patch
@test/cf-server-entry Patch
@test/astro-cloudflare-server-island-prerender-framework Patch
@test/astro-cloudflare-sql-import Patch
@test/cf-ssr-deps Patch
@test/astro-cloudflare-static Patch
@test/astro-cloudflare-svelte-rune-deps Patch
@test/astro-cloudflare-top-level-return Patch
@test/cf-user-optimize-deps Patch
@test/astro-cloudflare-vite-plugin Patch
@test/astro-cloudflare-with-base Patch
@test/astro-cloudflare-with-react Patch
@test/astro-cloudflare-with-solid-js Patch
@test/astro-cloudflare-with-svelte Patch
@test/astro-cloudflare-with-vue Patch
@test/astro-cloudflare-wrangler-preview-platform Patch
@test/markdoc-content-collections Patch
@test/content-layer-markdoc Patch
@test/headings-custom Patch
@test/headings Patch
@test/image-assets-custom Patch
@test/image-assets Patch
@test/markdoc-propagated-assets Patch
@test/markdoc-render-with-space Patch
@test/markdoc-render-html Patch
@test/markdoc-render-null Patch
@test/markdoc-render-partials Patch
@test/markdoc-render-simple Patch
@test/markdoc-render-table-attrs Patch
@test/markdoc-render-typographer Patch
@test/markdoc-render-with-components Patch
@test/markdoc-render-with-config Patch
@test/markdoc-render-with-extends-components Patch
@test/markdoc-render-with-indented-components Patch
@test/markdoc-render-with-transform Patch
@test/markdoc-variables Patch
@test/content-layer-rendering Patch
@test/mdx-css-head-mdx Patch
@test/image-remark-imgattr Patch
@test/mdx-astro-container-escape Patch
@test/mdx-frontmatter-injection Patch
@test/netlify-skew-protection Patch
@test/netlify-hosted-astro-project Patch
@test/nodejs-api-route Patch
@test/nodejs-badurls Patch
@test/nodejs-encoded Patch
@test/nodejs-errors Patch
@test/nodejs-headers Patch
@test/nodejs-image Patch
@test/locals Patch
@test/node-middleware Patch
@test/nodejs-prerender-404-500 Patch
@test/nodejs-prerender Patch
@test/nodejs-prerendered-error-page-fetch Patch
@test/nodejs-preview-headers Patch
@test/redirects Patch
@test/node-sessions Patch
@test/ssr-assets-middleware Patch
@test/node-static-headers Patch
@test/node-trailingslash Patch
@test/url Patch
@test/well-known-locations Patch
@test/react-component Patch
@test/sitemap-chunks Patch
@test/sitemap-dynamic Patch
@test/sitemap-i18n-fallback Patch
@test/sitemap-ssr Patch
@test/sitemap-static Patch
@test/sitemap-trailing-slash Patch
async-rendering Patch
conditional-rendering Patch
@test/empty-class Patch
svelte-prop-types Patch
@test/astro-vercel-basic Patch
@test/astro-vercel-image Patch
@test/astro-vercel-integration-assets Patch
@test/vercel-isr Patch
@test/vercel-max-duration Patch
@test/vercel-edge-middleware-with-edge-file Patch
@test/vercel-edge-middleware-without-edge-file Patch
@test/astro-vercel-no-output Patch
@test/astro-vercel-prerendered-error-pages Patch
@test/astro-vercel-redirects-serverless Patch
@test/astro-vercel-redirects Patch
@test/vercel-server-islands Patch
@test/astro-vercel-serverless-prerender Patch
@test/astro-vercel-serverless-with-dynamic-routes Patch
@test/astro-vercel-static-assets Patch
@test/vercel-static-headers Patch
@test/astro-vercel-static Patch
@test/vercel-streaming Patch
@test/astro-vercel-with-web-analytics-enabled-output-as-static Patch
vercel-hosted-astro-project Patch
@test/vue-app-entrypoint-async Patch
@test/vue-app-entrypoint-css Patch
@test/vue-app-entrypoint-no-export-default Patch
@test/vue-app-entrypoint-relative Patch
@test/vue-app-entrypoint-src-absolute Patch
@test/vue-app-entrypoint Patch
@test/vue-basics Patch
vue-prop-types Patch
astro-benchmark Patch
@benchmark/adapter Patch
@benchmark/timer Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added pkg: integration Related to any renderer integration (scope) pkg: astro Related to the core `astro` package (scope) labels Jun 28, 2026
@codspeed-hq

codspeed-hq Bot commented Jun 28, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 17.69%

❌ 2 regressed benchmarks
✅ 16 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation many-components [streaming] 8.8 ms 11.6 ms -23.89%
Simulation Rendering: streaming [true], .md file 1.3 ms 1.4 ms -11%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing iseraph-dev:perf/ssr-request-pipeline (bc79d7d) with main (734739d)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (799e5cd) during the generation of this report, so 734739d was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@iseraph-dev iseraph-dev force-pushed the perf/ssr-request-pipeline branch from 6385486 to 1813d4f Compare June 29, 2026 11:22
…eline

The SSR hot path (app.render -> FetchState -> AstroHandler, shared by
astro/fetch and astro/hono) parsed request.url up to three times per request
and made a handful of avoidable per-request allocations. This collapses it to
one URL parse per request across the core and every server adapter, and trims
the allocations. Internal plumbing only - no behavior change.

One URL parse per request:

- Render path: the domain-i18n probe now runs only when a domains-* strategy is
  configured (isDomainI18nStrategy, computed once in the BaseApp constructor),
  and TrailingSlashHandler reuses the raw pathname/search that FetchState
  captures up front (refreshed when X-Forwarded-* rebuilds the request), leaving
  FetchState as the single parse.
- app.match() accepts an optional pre-parsed URL and uses the same domain-probe
  gate; RenderOptions.url lets FetchState reuse a caller-provided URL.
- Pre-matching adapters (Cloudflare, Netlify, Vercel) parse request.url once and
  pass it to both app.match() and app.render(): Cloudflare and Vercel drop from
  3 parses/request to 1, Netlify from 2 to 1. Cloudflare's matchStaticAsset()
  takes the parsed URL too.
- @astrojs/node: createRequestFromNodeRequest already built a URL to construct
  the Request, so it now returns { request, url }, threaded through serve-app,
  serve-static and NodeApp into app.match() and app.render(). Node drops from
  3 parses/request to 1.

Fewer per-request allocations:

- createOutgoingHttpHeaders builds the Node headers in one pass instead of
  Object.fromEntries(headers.entries()) plus a separate keys() check.
- getParams short-circuits on the first matching pattern (no .map().map().find()
  intermediate arrays).
- The memory cache provider reuses context.url instead of re-parsing
  new URL(context.request.url).
- Domain-based i18n parses each domainLookupTable key once (memoized per table).
- Netlify's cache-control header check short-circuits with || instead of
  allocating an array for .some().

Note: createRequestFromNodeRequest is exported from astro/app/node; its return
shape changes from Request to { request, url }, so external importers must now
destructure .request.

Covered by the existing astro unit (3014/0), astro integration (1175/0) and
@astrojs/{node,cloudflare,netlify,vercel} suites.
@iseraph-dev iseraph-dev force-pushed the perf/ssr-request-pipeline branch from 1813d4f to 7bd6a5a Compare June 29, 2026 17:29

@ematipico ematipico left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds too many things. I suggest splitting it in three PRs:

  • the reducing of the parsing
  • the addition of the new advanced API. Debatable, but with a new PR at least you can explain what's needed for
Comment thread packages/astro/src/core/app/base.ts Outdated
Comment on lines +87 to +95
/**
* **Advanced API**: you probably do not need to use this.
*
* A pre-parsed `URL` for this request, so adapters that already parsed
* `request.url` can avoid a second parse. It **must** equal
* `new URL(request.url)`, and it is normalized in place during rendering, so
* the object must not be reused after calling `render`.
*/
url?: URL;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR didn't mention this new API. It should be removed, or it should be documented and part of a minor changeset

Comment thread packages/astro/src/core/app/base.ts Outdated
public match(
request: Request,
allowPrerenderedRoutes = false,
url = new URL(request.url),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To remove in case we remove the "advanced" API

Comment thread packages/astro/src/core/app/base.ts Outdated
}

private computePathnameFromDomain(request: Request): string | undefined {
private computePathnameFromDomain(request: Request, url = new URL(request.url)): string | undefined {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To remove in case we remove the advanced api

Comment on lines +17 to +27
// Copy the Web Headers into a plain object for Node. Iterating `headers`
// yields lowercased names with any multi-value header already comma-joined;
// `isEmpty` records whether anything was copied so a header-less response
// returns `undefined`. A comma-joined `set-cookie` is invalid, so it is
// rebuilt as an array below.
const nodeHeaders: OutgoingHttpHeaders = {};
let isEmpty = true;
for (const [key, value] of headers) {
nodeHeaders[key] = value;
isEmpty = false;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be reverted. This a runtime-agnostic code and "Node" things aren't allowed.

const cookieHeaders = headers.getSetCookie();
if (cookieHeaders.length > 1) {
// the Headers.entries() API already normalized all header names to lowercase so we can safely index this as 'set-cookie'
// the Headers API already normalized all header names to lowercase so we can safely index this as 'set-cookie'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert

Comment thread packages/astro/src/core/app/node.ts Outdated
Comment on lines +354 to +368
if (req instanceof Request) {
return super.match(req, allowPrerenderedRoutes);
}
return super.match(req, allowPrerenderedRoutes);
const { request, url } = createRequestFromNodeRequest(req, {
skipBody: true,
});
return super.match(request, allowPrerenderedRoutes, url);
}

render(request: NodeRequest | Request, options?: RenderOptions): Promise<Response> {
if (!(request instanceof Request)) {
request = createRequestFromNodeRequest(request);
if (request instanceof Request) {
return super.render(request, options);
}
return super.render(request, options);
const { request: webRequest, url } = createRequestFromNodeRequest(request);
return super.render(webRequest, { ...options, url });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why the logic of this code needed to be changed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createRequestFromNodeRequest already builds a URL to construct the Request, so I had it return { request, url } and passed that url into match/render to avoid re-parsing it in FetchState. The early return is just to bring url into scope. It depends on the url API, though, so I'll move it to the follow-up PR and
put node.ts back as it was.

@iseraph-dev

Copy link
Copy Markdown
Contributor Author

You're right, it's doing too much. I'll split it.

I'll keep this PR to the internal changes that don't touch any public API: the
domains-* i18n gate (#hasDomainI18nRouting), memoizing the parsed domain keys,
getParams dropping the intermediate arrays, the memory cache provider reusing
context.url, and the render path parsing the URL once (FetchState, with the
trailing-slash handler reusing it instead of re-parsing). No signature changes,
so it stays a patch.

I'll move the url-threading part (RenderOptions.url, the optional url argument on
match(), and the adapter wiring) to a separate minor PR with docs, and sort out
the in-place URL mutation so it isn't a footgun on public API.

I'll also revert the createOutgoingHttpHeaders change here.

Does that work for you?

Keep only the changes internal to astro core and drop the parts that add
public API or help a single runtime, so this can land as a patch:

- revert the RenderOptions.url / match(url) threading and the adapter wiring
  (cloudflare, netlify, vercel, node); it'll come back as a separate minor PR
- revert createOutgoingHttpHeaders to its original form
- keep the domains-* i18n gate, the trailing-slash raw-path reuse, the
  getParams cleanup, the memory cache provider url reuse, and the
  domainLookupTable memoization

Reword the changeset to match and drop the adapters changeset.
@github-actions github-actions Bot removed the pkg: integration Related to any renderer integration (scope) label Jun 30, 2026
@iseraph-dev iseraph-dev requested a review from ematipico June 30, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg: astro Related to the core `astro` package (scope)

2 participants