diff --git a/.changeset/swift-flies-press.md b/.changeset/swift-flies-press.md new file mode 100644 index 000000000000..f57237d0e9a6 --- /dev/null +++ b/.changeset/swift-flies-press.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix for experimental redirects in dev mode diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts index 4f3c5b0754d8..71a21850b8ab 100644 --- a/packages/astro/src/core/endpoint/index.ts +++ b/packages/astro/src/core/endpoint/index.ts @@ -161,7 +161,7 @@ function isRedirect(statusCode: number) { } export function throwIfRedirectNotAllowed(response: Response, config: AstroConfig) { - if (!isServerLikeOutput(config) && isRedirect(response.status)) { + if (!isServerLikeOutput(config) && isRedirect(response.status) && !config.experimental.redirects) { throw new AstroError(AstroErrorData.StaticRedirectNotAvailable); } } diff --git a/packages/astro/src/prerender/routing.ts b/packages/astro/src/prerender/routing.ts index 92878904400c..3c0e1f8a4f9d 100644 --- a/packages/astro/src/prerender/routing.ts +++ b/packages/astro/src/prerender/routing.ts @@ -1,6 +1,7 @@ import type { AstroSettings, RouteData } from '../@types/astro'; -import { preload, type DevelopmentEnvironment } from '../core/render/dev/index.js'; +import { preload, type DevelopmentEnvironment, type ComponentPreload } from '../core/render/dev/index.js'; import { getPrerenderStatus } from './metadata.js'; +import { routeIsRedirect, RedirectComponentInstance } from '../core/redirects/index.js'; type GetSortedPreloadedMatchesParams = { env: DevelopmentEnvironment; @@ -26,14 +27,31 @@ type PreloadAndSetPrerenderStatusParams = { matches: RouteData[]; settings: AstroSettings; }; + +type PreloadAndSetPrerenderStatusResult = { + filePath: URL; + route: RouteData; + preloadedComponent: ComponentPreload; +}; + async function preloadAndSetPrerenderStatus({ env, matches, settings, -}: PreloadAndSetPrerenderStatusParams) { +}: PreloadAndSetPrerenderStatusParams): Promise { const preloaded = await Promise.all( matches.map(async (route) => { const filePath = new URL(`./${route.component}`, settings.config.root); + + if(routeIsRedirect(route)) { + const preloadedComponent: ComponentPreload = [[], RedirectComponentInstance]; + return { + preloadedComponent, + route, + filePath + }; + } + const preloadedComponent = await preload({ env, filePath }); // gets the prerender metadata set by the `astro:scanner` vite plugin @@ -46,7 +64,7 @@ async function preloadAndSetPrerenderStatus({ route.prerender = prerenderStatus; } - return { preloadedComponent, route, filePath } as const; + return { preloadedComponent, route, filePath }; }) ); return preloaded; diff --git a/packages/astro/test/redirects.test.js b/packages/astro/test/redirects.test.js index 5cff8fe3d938..2e1e0b8b97e4 100644 --- a/packages/astro/test/redirects.test.js +++ b/packages/astro/test/redirects.test.js @@ -65,61 +65,93 @@ describe('Astro.redirect', () => { }); describe('output: "static"', () => { - before(async () => { - process.env.STATIC_MODE = true; - fixture = await loadFixture({ - root: './fixtures/ssr-redirect/', - output: 'static', - experimental: { - redirects: true, - }, - redirects: { - '/one': '/', - '/two': '/', - '/blog/[...slug]': '/articles/[...slug]', - '/three': { - status: 302, - destination: '/', + describe('build', () => { + before(async () => { + process.env.STATIC_MODE = true; + fixture = await loadFixture({ + root: './fixtures/ssr-redirect/', + output: 'static', + experimental: { + redirects: true, }, - }, + redirects: { + '/one': '/', + '/two': '/', + '/blog/[...slug]': '/articles/[...slug]', + '/three': { + status: 302, + destination: '/', + }, + }, + }); + await fixture.build(); + }); + + it('Includes the meta refresh tag in Astro.redirect pages', async () => { + const html = await fixture.readFile('/secret/index.html'); + expect(html).to.include('http-equiv="refresh'); + expect(html).to.include('url=/login'); + }); + + it('Includes the meta refresh tag in `redirect` config pages', async () => { + let html = await fixture.readFile('/one/index.html'); + expect(html).to.include('http-equiv="refresh'); + expect(html).to.include('url=/'); + + html = await fixture.readFile('/two/index.html'); + expect(html).to.include('http-equiv="refresh'); + expect(html).to.include('url=/'); + + html = await fixture.readFile('/three/index.html'); + expect(html).to.include('http-equiv="refresh'); + expect(html).to.include('url=/'); + }); + + it('Generates page for dynamic routes', async () => { + let html = await fixture.readFile('/blog/one/index.html'); + expect(html).to.include('http-equiv="refresh'); + expect(html).to.include('url=/articles/one'); + + html = await fixture.readFile('/blog/two/index.html'); + expect(html).to.include('http-equiv="refresh'); + expect(html).to.include('url=/articles/two'); + }); + + it('Generates redirect pages for redirects created by middleware', async () => { + let html = await fixture.readFile('/middleware-redirect/index.html'); + expect(html).to.include('http-equiv="refresh'); + expect(html).to.include('url=/'); }); - await fixture.build(); - }); - - it('Includes the meta refresh tag in Astro.redirect pages', async () => { - const html = await fixture.readFile('/secret/index.html'); - expect(html).to.include('http-equiv="refresh'); - expect(html).to.include('url=/login'); - }); - - it('Includes the meta refresh tag in `redirect` config pages', async () => { - let html = await fixture.readFile('/one/index.html'); - expect(html).to.include('http-equiv="refresh'); - expect(html).to.include('url=/'); - - html = await fixture.readFile('/two/index.html'); - expect(html).to.include('http-equiv="refresh'); - expect(html).to.include('url=/'); - - html = await fixture.readFile('/three/index.html'); - expect(html).to.include('http-equiv="refresh'); - expect(html).to.include('url=/'); }); - it('Generates page for dynamic routes', async () => { - let html = await fixture.readFile('/blog/one/index.html'); - expect(html).to.include('http-equiv="refresh'); - expect(html).to.include('url=/articles/one'); - - html = await fixture.readFile('/blog/two/index.html'); - expect(html).to.include('http-equiv="refresh'); - expect(html).to.include('url=/articles/two'); - }); + describe('dev', () => { + /** @type {import('./test-utils.js').DevServer} */ + let devServer; + before(async () => { + process.env.STATIC_MODE = true; + fixture = await loadFixture({ + root: './fixtures/ssr-redirect/', + output: 'static', + experimental: { + redirects: true, + }, + redirects: { + '/one': '/', + }, + }); + devServer = await fixture.startDevServer(); + }); - it('Generates redirect pages for redirects created by middleware', async () => { - let html = await fixture.readFile('/middleware-redirect/index.html'); - expect(html).to.include('http-equiv="refresh'); - expect(html).to.include('url=/'); + after(async () => { + await devServer.stop(); + }); + + it('Returns 301', async () => { + let res = await fixture.fetch('/one', { + redirect: 'manual' + }); + expect(res.status).to.equal(301); + }); }); });