diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index ec9822912..9d09d4260 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1841,6 +1841,7 @@ export interface RouteData { type: RouteType; prerender: boolean; redirect?: string; + redirectRoute?: RouteData; } export type RedirectRouteData = RouteData & { diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index dcd550896..e0a576542 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -35,8 +35,8 @@ import { debug, info } from '../logger/core.js'; import { callMiddleware } from '../middleware/callMiddleware.js'; import { createEnvironment, createRenderContext, renderPage } from '../render/index.js'; import { callGetStaticPaths } from '../render/route-cache.js'; -import { getRedirectLocationOrThrow } from '../redirects/index.js'; -import { +import { getRedirectLocationOrThrow, routeIsRedirect } from '../redirects/index.js'; +import { createAssetLink, createModuleScriptsSet, createStylesheetElementSet, @@ -173,15 +173,18 @@ async function generatePage( let pageModulePromise = ssrEntry.pageMap?.get(pageData.component); const middleware = ssrEntry.middleware; - if (!pageModulePromise) { - if(pageData.route.type === 'redirect') { - pageModulePromise = () => Promise.resolve({ 'default': Function.prototype }) as any; + if (!pageModulePromise && routeIsRedirect(pageData.route)) { + if(pageData.route.redirectRoute) { + pageModulePromise = ssrEntry.pageMap?.get(pageData.route.redirectRoute!.component); } else { - throw new Error( - `Unable to find the module for ${pageData.component}. This is unexpected and likely a bug in Astro, please report.` - ); + pageModulePromise = { default: () => {} } as any; } } + if (!pageModulePromise) { + throw new Error( + `Unable to find the module for ${pageData.component}. This is unexpected and likely a bug in Astro, please report.` + ); + } const pageModule = await pageModulePromise(); if (shouldSkipDraft(pageModule, opts.settings)) { info(opts.logging, null, `${magenta('⚠️')} Skipping draft ${pageData.route.component}`); @@ -519,7 +522,9 @@ async function generatePath( case 301: case 302: { const location = getRedirectLocationOrThrow(response.headers); - body = ``; + body = ` +Redirecting to: ${location} +`; pageData.route.redirect = location; break; } diff --git a/packages/astro/src/core/redirects/helpers.ts b/packages/astro/src/core/redirects/helpers.ts index 05e4e83a0..b65e216e6 100644 --- a/packages/astro/src/core/redirects/helpers.ts +++ b/packages/astro/src/core/redirects/helpers.ts @@ -1,5 +1,12 @@ -import type { RouteData, RedirectRouteData } from '../../@types/astro'; +import type { RouteData, RedirectRouteData, Params } from '../../@types/astro'; export function routeIsRedirect(route: RouteData | undefined): route is RedirectRouteData { return route?.type === 'redirect'; } + +export function redirectRouteGenerate(redirectRoute: RouteData, data: Params): string { + const routeData = redirectRoute.redirectRoute; + const route = redirectRoute.redirect; + + return routeData?.generate(data) || routeData?.pathname || route || '/'; +} diff --git a/packages/astro/src/core/redirects/index.ts b/packages/astro/src/core/redirects/index.ts index 1f6996048..3a8326de6 100644 --- a/packages/astro/src/core/redirects/index.ts +++ b/packages/astro/src/core/redirects/index.ts @@ -1,2 +1,2 @@ export { getRedirectLocationOrThrow } from './validate.js'; -export { routeIsRedirect } from './helpers.js'; +export { routeIsRedirect, redirectRouteGenerate } from './helpers.js'; diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts index eb86b3193..3eb085fda 100644 --- a/packages/astro/src/core/render/core.ts +++ b/packages/astro/src/core/render/core.ts @@ -8,7 +8,7 @@ import type { RenderContext } from './context.js'; import type { Environment } from './environment.js'; import { createResult } from './result.js'; import { callGetStaticPaths, findPathItemByKey, RouteCache } from './route-cache.js'; -import { routeIsRedirect } from '../redirects/index.js'; +import { routeIsRedirect, redirectRouteGenerate } from '../redirects/index.js'; interface GetParamsAndPropsOptions { mod: ComponentInstance; @@ -116,7 +116,7 @@ export async function renderPage({ mod, renderContext, env, apiContext }: Render return new Response(null, { status: 301, headers: { - location: renderContext.route!.redirect + location: redirectRouteGenerate(renderContext.route!, renderContext.params) } }); } diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts index 25258e3e5..81ff18634 100644 --- a/packages/astro/src/core/routing/manifest/create.ts +++ b/packages/astro/src/core/routing/manifest/create.ts @@ -444,6 +444,7 @@ export function createRouteManifest( .map(([{ dynamic, content }]) => (dynamic ? `[${content}]` : content)) .join('/')}`.toLowerCase(); + routes.unshift({ type: 'redirect', @@ -451,11 +452,12 @@ export function createRouteManifest( pattern, segments, params, - component: '', + component: from, generate, pathname: pathname || void 0, prerender: false, - redirect: to + redirect: to, + redirectRoute: routes.find(r => r.route === to) }); }); diff --git a/packages/astro/test/fixtures/ssr-redirect/src/pages/articles/[...slug].astro b/packages/astro/test/fixtures/ssr-redirect/src/pages/articles/[...slug].astro new file mode 100644 index 000000000..716d3bd5d --- /dev/null +++ b/packages/astro/test/fixtures/ssr-redirect/src/pages/articles/[...slug].astro @@ -0,0 +1,25 @@ +--- +export const getStaticPaths = (async () => { + const posts = [ + { slug: 'one', data: {draft: false, title: 'One'} }, + { slug: 'two', data: {draft: false, title: 'Two'} } + ]; + return posts.map((post) => { + return { + params: { slug: post.slug }, + props: { draft: post.data.draft, title: post.data.title }, + }; + }); +}) + +const { slug } = Astro.params; +const { title } = Astro.props; +--- + + + { title } + + +

{ title }

+ + diff --git a/packages/astro/test/fixtures/ssr-redirect/src/pages/index.astro b/packages/astro/test/fixtures/ssr-redirect/src/pages/index.astro new file mode 100644 index 000000000..e06d49b85 --- /dev/null +++ b/packages/astro/test/fixtures/ssr-redirect/src/pages/index.astro @@ -0,0 +1,10 @@ +--- +--- + + + Testing + + +

Testing

+ + diff --git a/packages/astro/test/redirects.test.js b/packages/astro/test/redirects.test.js index f3e6c1121..b8fd723ff 100644 --- a/packages/astro/test/redirects.test.js +++ b/packages/astro/test/redirects.test.js @@ -45,14 +45,39 @@ describe('Astro.redirect', () => { fixture = await loadFixture({ root: './fixtures/ssr-redirect/', output: 'static', + redirects: { + '/one': '/', + '/two': '/', + '/blog/[...slug]': '/articles/[...slug]' + } }); await fixture.build(); }); - it('Includes the meta refresh tag.', async () => { + 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=/'); + }); + + 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'); + }); }); }); diff --git a/packages/integrations/netlify/src/shared.ts b/packages/integrations/netlify/src/shared.ts index d7843ae06..479c03907 100644 --- a/packages/integrations/netlify/src/shared.ts +++ b/packages/integrations/netlify/src/shared.ts @@ -65,46 +65,18 @@ export async function createRedirects( } } } else { - const pattern = - '/' + - route.segments - .map(([part]) => { - //(part.dynamic ? '*' : part.content) - if (part.dynamic) { - if (part.spread) { - return '*'; - } else { - return ':' + part.content; - } - } else { - return part.content; - } - }) - .join('/'); + const pattern = generateDynamicPattern(route); - //if(kind === 'static') { - if(route.redirect) { - definitions.push({ - dynamic: true, - input: pattern, - target: route.redirect, - status: 301, - weight: 1 - }); - continue; - } - - if(kind === 'static') { - continue; - } - else if (route.distURL) { + if (route.distURL) { + const targetRoute = route.redirectRoute ?? route; + const targetPattern = generateDynamicPattern(targetRoute); const target = - `${pattern}` + (config.build.format === 'directory' ? '/index.html' : '.html'); + `${targetPattern}` + (config.build.format === 'directory' ? '/index.html' : '.html'); definitions.push({ dynamic: true, input: pattern, target, - status: 200, + status: route.type === 'redirect' ? 301 : 200, weight: 1, }); } else { @@ -127,6 +99,26 @@ export async function createRedirects( await fs.promises.appendFile(_redirectsURL, _redirects, 'utf-8'); } +function generateDynamicPattern(route: RouteData) { + const pattern = + '/' + + route.segments + .map(([part]) => { + //(part.dynamic ? '*' : part.content) + if (part.dynamic) { + if (part.spread) { + return '*'; + } else { + return ':' + part.content; + } + } else { + return part.content; + } + }) + .join('/'); + return pattern; +} + function prettify(definitions: RedirectDefinition[]) { let minInputLength = 4, minTargetLength = 4; diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index f14b0dde7..8de4cbc9b 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -33,7 +33,10 @@ describe('SSG - Redirects', () => { // This uses the dynamic Astro.redirect, so we don't know that it's a redirect // until runtime. This is correct! - '/nope', '/.netlify/functions/entry', '200' + '/nope', '/.netlify/functions/entry', '200', + + // A real route + '/team/articles/*', '/.netlify/functions/entry', '200', ]); }); }); diff --git a/packages/integrations/netlify/test/static/fixtures/redirects/src/pages/team/articles/[...slug].astro b/packages/integrations/netlify/test/static/fixtures/redirects/src/pages/team/articles/[...slug].astro new file mode 100644 index 000000000..716d3bd5d --- /dev/null +++ b/packages/integrations/netlify/test/static/fixtures/redirects/src/pages/team/articles/[...slug].astro @@ -0,0 +1,25 @@ +--- +export const getStaticPaths = (async () => { + const posts = [ + { slug: 'one', data: {draft: false, title: 'One'} }, + { slug: 'two', data: {draft: false, title: 'Two'} } + ]; + return posts.map((post) => { + return { + params: { slug: post.slug }, + props: { draft: post.data.draft, title: post.data.title }, + }; + }); +}) + +const { slug } = Astro.params; +const { title } = Astro.props; +--- + + + { title } + + +

{ title }

+ + diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.js index cab238c39..68fbc60f8 100644 --- a/packages/integrations/netlify/test/static/redirects.test.js +++ b/packages/integrations/netlify/test/static/redirects.test.js @@ -1,8 +1,6 @@ import { expect } from 'chai'; -import { load as cheerioLoad } from 'cheerio'; import { loadFixture, testIntegration } from './test-utils.js'; import { netlifyStatic } from '../../dist/index.js'; -import { fileURLToPath } from 'url'; describe('SSG - Redirects', () => { /** @type {import('../../../astro/test/test-utils').Fixture} */ @@ -16,7 +14,8 @@ describe('SSG - Redirects', () => { site: `http://example.com`, integrations: [testIntegration()], redirects: { - '/other': '/' + '/other': '/', + '/blog/[...slug]': '/team/articles/[...slug]' } }); await fixture.build(); @@ -26,8 +25,10 @@ describe('SSG - Redirects', () => { let redirects = await fixture.readFile('/_redirects'); let parts = redirects.split(/\s+/); expect(parts).to.deep.equal([ + '/blog/*', '/team/articles/*/index.html', '301', '/other', '/', '301', - '/nope', '/', '301' + '/nope', '/', '301', + '/team/articles/*', '/team/articles/*/index.html', '200' ]); }); });