diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index e0a576542..dec42e78a 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -177,7 +177,7 @@ async function generatePage( if(pageData.route.redirectRoute) { pageModulePromise = ssrEntry.pageMap?.get(pageData.route.redirectRoute!.component); } else { - pageModulePromise = { default: () => {} } as any; + pageModulePromise = () => Promise.resolve({ default: () => {} }); } } if (!pageModulePromise) { diff --git a/packages/integrations/vercel/src/lib/redirects.ts b/packages/integrations/vercel/src/lib/redirects.ts index c11d74802..4f0629172 100644 --- a/packages/integrations/vercel/src/lib/redirects.ts +++ b/packages/integrations/vercel/src/lib/redirects.ts @@ -36,6 +36,10 @@ function getMatchPattern(segments: RoutePart[][]) { .join(''); } +function appendTrailingSlash(route: string): string { + return route.at(-1) === '/' ? route : route + '/'; +} + function getReplacePattern(segments: RoutePart[][]) { let n = 0; let result = ''; @@ -54,28 +58,44 @@ function getReplacePattern(segments: RoutePart[][]) { return result; } +function getRedirectLocation(route: RouteData, config: AstroConfig): string { + if(route.redirectRoute) { + const pattern = getReplacePattern(route.redirectRoute.segments); + const path = (config.trailingSlash === 'always' ? appendTrailingSlash(pattern) : pattern); + return config.base + path; + } else { + return config.base + route.redirect; + } +} + export function getRedirects(routes: RouteData[], config: AstroConfig): VercelRoute[] { let redirects: VercelRoute[] = []; - if (config.trailingSlash === 'always') { - for (const route of routes) { - if (route.type !== 'page' || route.segments.length === 0) continue; - - redirects.push({ - src: config.base + getMatchPattern(route.segments), - headers: { Location: config.base + getReplacePattern(route.segments) + '/' }, - status: 308, - }); - } - } else if (config.trailingSlash === 'never') { - for (const route of routes) { - if (route.type !== 'page' || route.segments.length === 0) continue; - - redirects.push({ - src: config.base + getMatchPattern(route.segments) + '/', - headers: { Location: config.base + getReplacePattern(route.segments) }, - status: 308, - }); + for(const route of routes) { + if(route.type === 'redirect') { + if(true || route.pathname) { + redirects.push({ + src: config.base + getMatchPattern(route.segments), + headers: { Location: getRedirectLocation(route, config) }, + status: 301 + }) + } else { + console.error(`Dynamic routes not yet supported`); + } + } else if (route.type === 'page') { + if (config.trailingSlash === 'always') { + redirects.push({ + src: config.base + getMatchPattern(route.segments), + headers: { Location: config.base + getReplacePattern(route.segments) + '/' }, + status: 308, + }); + } else if (config.trailingSlash === 'never') { + redirects.push({ + src: config.base + getMatchPattern(route.segments) + '/', + headers: { Location: config.base + getReplacePattern(route.segments) }, + status: 308, + }); + } } } diff --git a/packages/integrations/vercel/test/fixtures/redirects/astro.config.mjs b/packages/integrations/vercel/test/fixtures/redirects/astro.config.mjs new file mode 100644 index 000000000..a38be5065 --- /dev/null +++ b/packages/integrations/vercel/test/fixtures/redirects/astro.config.mjs @@ -0,0 +1,9 @@ +import vercel from '@astrojs/vercel/static'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + adapter: vercel({imageService: true}), + experimental: { + assets: true + } +}); diff --git a/packages/integrations/vercel/test/fixtures/redirects/package.json b/packages/integrations/vercel/test/fixtures/redirects/package.json new file mode 100644 index 000000000..d7dcc5471 --- /dev/null +++ b/packages/integrations/vercel/test/fixtures/redirects/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/astro-vercel-redirects", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/vercel": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/vercel/test/fixtures/redirects/src/pages/index.astro b/packages/integrations/vercel/test/fixtures/redirects/src/pages/index.astro new file mode 100644 index 000000000..9c077e2a3 --- /dev/null +++ b/packages/integrations/vercel/test/fixtures/redirects/src/pages/index.astro @@ -0,0 +1,8 @@ + + + Testing + + +

Testing

+ + diff --git a/packages/integrations/vercel/test/fixtures/redirects/src/pages/team/articles/[...slug].astro b/packages/integrations/vercel/test/fixtures/redirects/src/pages/team/articles/[...slug].astro new file mode 100644 index 000000000..716d3bd5d --- /dev/null +++ b/packages/integrations/vercel/test/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/vercel/test/redirects.test.js b/packages/integrations/vercel/test/redirects.test.js new file mode 100644 index 000000000..33b0fe7b4 --- /dev/null +++ b/packages/integrations/vercel/test/redirects.test.js @@ -0,0 +1,48 @@ +import { expect } from 'chai'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('Redirects', () => { + /** @type {import('../../../astro/test/test-utils.js').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/redirects/', + redirects: { + '/one': '/', + '/two': '/', + '/blog/[...slug]': '/team/articles/[...slug]', + } + }); + await fixture.build(); + }); + + async function getConfig() { + const json = await fixture.readFile('../.vercel/output/config.json'); + const config = JSON.parse(json); + + return config; + } + + it('define static routes', async () => { + const config = await getConfig(); + + const oneRoute = config.routes.find(r => r.src === '/\\/one'); + expect(oneRoute.headers.Location).to.equal('/'); + expect(oneRoute.status).to.equal(301); + + const twoRoute = config.routes.find(r => r.src === '/\\/one'); + expect(twoRoute.headers.Location).to.equal('/'); + expect(twoRoute.status).to.equal(301); + }); + + it('defines dynamic routes', async () => { + const config = await getConfig(); + + const blogRoute = config.routes.find(r => r.src.startsWith('/\\/blog')); + expect(blogRoute).to.not.be.undefined; + expect(blogRoute.headers.Location.startsWith('/team/articles')).to.equal(true); + expect(blogRoute.status).to.equal(301); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20ca7bcb5..5f48316c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4868,6 +4868,15 @@ importers: specifier: workspace:* version: link:../../../../../astro + packages/integrations/vercel/test/fixtures/redirects: + dependencies: + '@astrojs/vercel': + specifier: workspace:* + version: link:../../.. + astro: + specifier: workspace:* + version: link:../../../../../astro + packages/integrations/vercel/test/fixtures/serverless-prerender: dependencies: '@astrojs/vercel':