diff --git a/.changeset/swift-moons-drop.md b/.changeset/swift-moons-drop.md new file mode 100644 index 000000000..f181941e5 --- /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 b657e4943..d7d4241d2 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 0ef316ed6..e72e754e2 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); - - 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); - - 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', + 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; + }); + + after(async () => { + await server.stop(); + }); + + 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('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); + + 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); + + 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/'); }); });