diff --git a/.changeset/nice-hats-live.md b/.changeset/nice-hats-live.md new file mode 100644 index 000000000000..3bed91973244 --- /dev/null +++ b/.changeset/nice-hats-live.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Fixes an issue where prerendered pages had access to query params in dev mode. diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/contact.ts b/packages/astro/e2e/fixtures/view-transitions/src/pages/contact.ts index 055930dad8f0..7f9781d93d37 100644 --- a/packages/astro/e2e/fixtures/view-transitions/src/pages/contact.ts +++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/contact.ts @@ -1,5 +1,7 @@ import type { APIContext } from 'astro'; +export const prerender = false; + export const POST = async ({ request, redirect }: APIContext) => { const formData = await request.formData(); const name = formData.get('name'); diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro index 88a36251a72a..ca5e4cb8253c 100644 --- a/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro +++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro @@ -3,6 +3,7 @@ import Layout from '../components/Layout.astro'; const method = Astro.url.searchParams.get('method') ?? 'POST'; const enctype = Astro.url.searchParams.get('enctype'); const postShowThrow = Astro.url.searchParams.has('throw') ?? false; +export const prerender = false; --- diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/form-response.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/form-response.astro index c98beb20bc13..77133024342b 100644 --- a/packages/astro/e2e/fixtures/view-transitions/src/pages/form-response.astro +++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/form-response.astro @@ -1,6 +1,8 @@ --- import Layout from '../components/Layout.astro'; const name = Astro.url.searchParams.get('name'); + +export const prerender = false; ---
Submitted contact: {name}
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/form-two.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/form-two.astro index 01131ee84c4d..d07650bd9959 100644 --- a/packages/astro/e2e/fixtures/view-transitions/src/pages/form-two.astro +++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/form-two.astro @@ -6,6 +6,8 @@ if(Astro.request.method === 'POST') { const name = formData.get('name'); return Astro.redirect(`/form-response?name=${name}`); } + +export const prerender = false; ---

Contact Form

diff --git a/packages/astro/src/core/request.ts b/packages/astro/src/core/request.ts index 8bf52d11a1a6..dadd367cceb4 100644 --- a/packages/astro/src/core/request.ts +++ b/packages/astro/src/core/request.ts @@ -13,6 +13,7 @@ export interface CreateRequestOptions { logger: Logger; ssr: boolean; locals?: object | undefined; + removeParams?: boolean; } const clientAddressSymbol = Symbol.for('astro.clientAddress'); @@ -27,13 +28,21 @@ export function createRequest({ logger, ssr, locals, + removeParams = false, }: CreateRequestOptions): Request { let headersObj = headers instanceof Headers ? headers : new Headers(Object.entries(headers as Record)); - const request = new Request(url.toString(), { + if (typeof url === 'string') url = new URL(url); + + // HACK! astro:assets uses query params for the injected route in `dev` + if (removeParams && url.pathname !== '/_image') { + url.search = ''; + } + + const request = new Request(url, { method: method, headers: headersObj, body, diff --git a/packages/astro/src/vite-plugin-astro-server/request.ts b/packages/astro/src/vite-plugin-astro-server/request.ts index f86609c8c076..e9f49c21b7d0 100644 --- a/packages/astro/src/vite-plugin-astro-server/request.ts +++ b/packages/astro/src/vite-plugin-astro-server/request.ts @@ -40,17 +40,6 @@ export async function handleRequest({ // Add config.base back to url before passing it to SSR url.pathname = removeTrailingForwardSlash(config.base) + url.pathname; - // HACK! astro:assets uses query params for the injected route in `dev` - if (!buildingToSSR && pathname !== '/_image') { - // Prevent user from depending on search params when not doing SSR. - // NOTE: Create an array copy here because deleting-while-iterating - // creates bugs where not all search params are removed. - const allSearchParams = Array.from(url.searchParams); - for (const [key] of allSearchParams) { - url.searchParams.delete(key); - } - } - let body: ArrayBuffer | undefined = undefined; if (!(incomingRequest.method === 'GET' || incomingRequest.method === 'HEAD')) { let bytes: Uint8Array[] = []; diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index bc9c5de03072..a6457e5a04b0 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -229,6 +229,7 @@ export async function handleRoute({ logger, ssr: buildingToSSR, clientAddress: buildingToSSR ? incomingRequest.socket.remoteAddress : undefined, + removeParams: buildingToSSR === false || route.prerender }); // Set user specified headers to response object. diff --git a/packages/astro/test/units/vite-plugin-astro-server/request.test.js b/packages/astro/test/units/vite-plugin-astro-server/request.test.js index dfbb5a1b8ee5..85513daae1ff 100644 --- a/packages/astro/test/units/vite-plugin-astro-server/request.test.js +++ b/packages/astro/test/units/vite-plugin-astro-server/request.test.js @@ -1,11 +1,14 @@ import * as assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; +import { after, before, describe, it } from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { createContainer } from '../../../dist/core/dev/container.js'; import { createLoader } from '../../../dist/core/module-loader/index.js'; import { createRouteManifest } from '../../../dist/core/routing/index.js'; import { createComponent, render } from '../../../dist/runtime/server/index.js'; import { createController, handleRequest } from '../../../dist/vite-plugin-astro-server/index.js'; import { DevPipeline } from '../../../dist/vite-plugin-astro-server/pipeline.js'; import { createDevelopmentManifest } from '../../../dist/vite-plugin-astro-server/plugin.js'; +import testAdapter from '../../test-adapter.js'; import { createAstroModule, createBasicSettings, @@ -73,4 +76,55 @@ describe('vite-plugin-astro-server', () => { assert.equal(html.includes('
'), true); }); }); + + describe('url', () => { + let container; + let settings; + + before(async () => { + const root = new URL('../../fixtures/api-routes/', import.meta.url); + const fileSystem = { + '/src/pages/url.astro': `{Astro.request.url}`, + '/src/pages/prerendered.astro': `--- + export const prerender = true; + --- + {Astro.request.url}`, + }; + const fs = createFs(fileSystem, root); + settings = await createBasicSettings({ + root: fileURLToPath(root), + output: 'server', + adapter: testAdapter(), + }); + container = await createContainer({ + fs, + settings, + logger: defaultLogger, + }); + }); + + after(async () => { + await container.close(); + }); + + it('params are included', async () => { + const { req, res, text } = createRequestAndResponse({ + method: 'GET', + url: '/url?xyz=123', + }); + container.handle(req, res); + const html = await text(); + assert.deepEqual(html, 'http://undefined/url?xyz=123'); + }); + + it('params are excluded on prerendered routes', async () => { + const { req, res, text } = createRequestAndResponse({ + method: 'GET', + url: '/prerendered?xyz=123', + }); + container.handle(req, res); + const html = await text(); + assert.deepEqual(html, 'http://undefined/prerendered'); + }); + }); });