diff --git a/.changeset/swift-moons-drop.md b/.changeset/swift-moons-drop.md new file mode 100644 index 000000000000..f181941e51a2 --- /dev/null +++ b/.changeset/swift-moons-drop.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix double prepended forward slash in SSR diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index b657e4943689..d7d4241d20d4 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -14,7 +14,7 @@ import { call as callEndpoint, createAPIContext } from '../endpoint/index.js'; import { consoleLogDestination } from '../logger/console.js'; import { error, type LogOptions } from '../logger/core.js'; import { callMiddleware } from '../middleware/callMiddleware.js'; -import { removeTrailingForwardSlash } from '../path.js'; +import { prependForwardSlash, removeTrailingForwardSlash } from '../path.js'; import { createEnvironment, createRenderContext, @@ -101,7 +101,7 @@ export class App { if (this.#manifest.assets.has(url.pathname)) { return undefined; } - let pathname = '/' + this.removeBase(url.pathname); + let pathname = prependForwardSlash(this.removeBase(url.pathname)); let routeData = matchRoute(pathname, this.#manifestData); if (routeData) { @@ -178,7 +178,7 @@ export class App { status = 200 ): Promise { const url = new URL(request.url); - const pathname = '/' + this.removeBase(url.pathname); + const pathname = prependForwardSlash(this.removeBase(url.pathname)); const info = this.#routeDataToRouteInfo.get(routeData!)!; // may be used in the future for handling rel=modulepreload, rel=icon, rel=manifest etc. const links = new Set(); diff --git a/packages/integrations/node/test/prerender.test.js b/packages/integrations/node/test/prerender.test.js index 0ef316ed65a9..e72e754e268f 100644 --- a/packages/integrations/node/test/prerender.test.js +++ b/packages/integrations/node/test/prerender.test.js @@ -9,61 +9,108 @@ describe('Prerendering', () => { let fixture; let server; - before(async () => { - process.env.ASTRO_NODE_AUTOSTART = 'disabled'; - fixture = await loadFixture({ - base: '/some-base', - root: './fixtures/prerender/', - output: 'server', - adapter: nodejs({ mode: 'standalone' }), - }); - await fixture.build(); - const { startServer } = await await load(); - let res = startServer(); - server = res.server; - }); - - after(async () => { - await server.stop(); - }); - async function load() { const mod = await import('./fixtures/prerender/dist/server/entry.mjs'); return mod; } - it('Can render SSR route', async () => { - const res = await fetch(`http://${server.host}:${server.port}/some-base/one`); - const html = await res.text(); - const $ = cheerio.load(html); + describe('With base', () => { + before(async () => { + process.env.ASTRO_NODE_AUTOSTART = 'disabled'; + fixture = await loadFixture({ + base: '/some-base', + root: './fixtures/prerender/', + output: 'server', + adapter: nodejs({ mode: 'standalone' }), + }); + await fixture.build(); + const { startServer } = await await load(); + let res = startServer(); + server = res.server; + }); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('One'); - }); + after(async () => { + await server.stop(); + }); - it('Can render prerendered route', async () => { - const res = await fetch(`http://${server.host}:${server.port}/some-base/two`); - const html = await res.text(); - const $ = cheerio.load(html); + it('Can render SSR route', async () => { + const res = await fetch(`http://${server.host}:${server.port}/some-base/one`); + const html = await res.text(); + const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Two'); - }); + expect(res.status).to.equal(200); + expect($('h1').text()).to.equal('One'); + }); + + it('Can render prerendered route', async () => { + const res = await fetch(`http://${server.host}:${server.port}/some-base/two`); + const html = await res.text(); + const $ = cheerio.load(html); + + expect(res.status).to.equal(200); + expect($('h1').text()).to.equal('Two'); + }); - it('Can render prerendered route with query params', async () => { - const res = await fetch(`http://${server.host}:${server.port}/some-base/two/?foo=bar`); - const html = await res.text(); - const $ = cheerio.load(html); + it('Can render prerendered route with query params', async () => { + const res = await fetch(`http://${server.host}:${server.port}/some-base/two/?foo=bar`); + const html = await res.text(); + const $ = cheerio.load(html); - expect(res.status).to.equal(200); - expect($('h1').text()).to.equal('Two'); + expect(res.status).to.equal(200); + expect($('h1').text()).to.equal('Two'); + }); + + it('Omitting the trailing slash results in a redirect that includes the base', async () => { + const res = await fetch(`http://${server.host}:${server.port}/some-base/two`, { + redirect: 'manual', + }); + expect(res.status).to.equal(301); + expect(res.headers.get('location')).to.equal('/some-base/two/'); + }); }); + describe('Without base', () => { + before(async () => { + process.env.ASTRO_NODE_AUTOSTART = 'disabled'; + fixture = await loadFixture({ + root: './fixtures/prerender/', + output: 'server', + adapter: nodejs({ mode: 'standalone' }), + }); + await fixture.build(); + const { startServer } = await await load(); + let res = startServer(); + server = res.server; + }); + + after(async () => { + await server.stop(); + }); + + it('Can render SSR route', async () => { + const res = await fetch(`http://${server.host}:${server.port}/one`); + const html = await res.text(); + const $ = cheerio.load(html); + + expect(res.status).to.equal(200); + expect($('h1').text()).to.equal('One'); + }); + + it('Can render prerendered route', async () => { + const res = await fetch(`http://${server.host}:${server.port}/two`); + const html = await res.text(); + const $ = cheerio.load(html); + + expect(res.status).to.equal(200); + expect($('h1').text()).to.equal('Two'); + }); + + it('Can render prerendered route with query params', async () => { + const res = await fetch(`http://${server.host}:${server.port}/two/?foo=bar`); + const html = await res.text(); + const $ = cheerio.load(html); - it('Omitting the trailing slash results in a redirect that includes the base', async () => { - const res = await fetch(`http://${server.host}:${server.port}/some-base/two`, { - redirect: 'manual', + expect(res.status).to.equal(200); + expect($('h1').text()).to.equal('Two'); }); - expect(res.status).to.equal(301); - expect(res.headers.get('location')).to.equal('/some-base/two/'); }); });