mirror of
https://github.com/withastro/astro.git
synced 2025-03-10 23:01:26 -05:00
Redirects (#7067)
* Redirects spike * Allow redirects in static mode * Support in Netlify as well * Adding a changeset * Rename file * Fix build problem * Refactor to be more modular * Fix location ref * Late test should only run in SSR * Support redirects in Netlify SSR configuration (#7167) * Implement support for dynamic routes in redirects (#7173) * Implement support for dynamic routes in redirects * Remove the .only * No need to special-case redirects in static build * Implement support for redirects config in the Vercel adapter (#7182) * Implement support for redirects config in the Vercel adapter * Remove unused condition * Move to a internal helper package * Add support for the object notation in redirects * Use status 308 for non-GET redirects (#7186) * Implement redirects in Cloudflare (#7198) * Implement redirects in Cloudflare * Fix build * Update tests b/c of new ordering * Debug issue * Use posix.join * Update packages/underscore-redirects/package.json Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Update based on review comments * Update broken test --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> * Test that redirects can come from middleware (#7213) * Test that redirects can come from middleware * Allow non-promise returns for middleware * Implement priority (#7210) * Refactor * Fix netlify test ordering * Fix ordering again * Redirects: Allow preventing the output of the static HTML file (#7245) * Do a simple push for priority * Adding changesets * Put the implementation behind a flag. * Self review * Update .changeset/chatty-actors-stare.md Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/src/@types/astro.ts Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update docs on dynamic restrictions. * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Code review changes * Document netlify static adapter * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Slight reword * Update .changeset/twenty-suns-vanish.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Add a note about public/_redirects file * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> --------- Co-authored-by: Emanuele Stoppa <my.burning@gmail.com> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
This commit is contained in:
parent
a39fd40273
commit
21a0fdc390
8 changed files with 155 additions and 15 deletions
|
@ -50,6 +50,7 @@
|
|||
"test": "mocha --exit --timeout 20000 test/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/internal-helpers": "^0.1.0",
|
||||
"@astrojs/webapi": "^2.2.0",
|
||||
"@vercel/analytics": "^0.1.8",
|
||||
"@vercel/nft": "^0.22.1",
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import type { AstroConfig, RouteData, RoutePart } from 'astro';
|
||||
import { appendForwardSlash } from '@astrojs/internal-helpers/path';
|
||||
import nodePath from 'node:path';
|
||||
|
||||
const pathJoin = nodePath.posix.join;
|
||||
|
||||
|
||||
// https://vercel.com/docs/project-configuration#legacy/routes
|
||||
interface VercelRoute {
|
||||
|
@ -54,28 +59,51 @@ 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' ? appendForwardSlash(pattern) : pattern);
|
||||
return pathJoin(config.base, path);
|
||||
} else if(typeof route.redirect === 'object') {
|
||||
return pathJoin(config.base, route.redirect.destination);
|
||||
} else {
|
||||
return pathJoin(config.base, route.redirect || '');
|
||||
}
|
||||
}
|
||||
|
||||
function getRedirectStatus(route: RouteData): number {
|
||||
if(typeof route.redirect === 'object') {
|
||||
return route.redirect.status;
|
||||
}
|
||||
return 301;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
for(const route of routes) {
|
||||
if(route.type === 'redirect') {
|
||||
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,
|
||||
headers: { Location: getRedirectLocation(route, config) },
|
||||
status: getRedirectStatus(route)
|
||||
});
|
||||
} 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ export default function vercelStatic({
|
|||
outDir,
|
||||
build: {
|
||||
format: 'directory',
|
||||
redirects: false,
|
||||
},
|
||||
vite: {
|
||||
define: viteDefine,
|
||||
|
|
9
packages/integrations/vercel/test/fixtures/redirects/astro.config.mjs
vendored
Normal file
9
packages/integrations/vercel/test/fixtures/redirects/astro.config.mjs
vendored
Normal file
|
@ -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
|
||||
}
|
||||
});
|
9
packages/integrations/vercel/test/fixtures/redirects/package.json
vendored
Normal file
9
packages/integrations/vercel/test/fixtures/redirects/package.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "@test/astro-vercel-redirects",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@astrojs/vercel": "workspace:*",
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
8
packages/integrations/vercel/test/fixtures/redirects/src/pages/index.astro
vendored
Normal file
8
packages/integrations/vercel/test/fixtures/redirects/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Testing</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Testing</h1>
|
||||
</body>
|
||||
</html>
|
25
packages/integrations/vercel/test/fixtures/redirects/src/pages/team/articles/[...slug].astro
vendored
Normal file
25
packages/integrations/vercel/test/fixtures/redirects/src/pages/team/articles/[...slug].astro
vendored
Normal file
|
@ -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;
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>{ title }</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{ title }</h1>
|
||||
</body>
|
||||
</html>
|
59
packages/integrations/vercel/test/redirects.test.js
Normal file
59
packages/integrations/vercel/test/redirects.test.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
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': '/',
|
||||
'/three': {
|
||||
status: 302,
|
||||
destination: '/'
|
||||
},
|
||||
'/blog/[...slug]': '/team/articles/[...slug]',
|
||||
},
|
||||
experimental: {
|
||||
redirects: true,
|
||||
},
|
||||
});
|
||||
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 === '/\\/two');
|
||||
expect(twoRoute.headers.Location).to.equal('/');
|
||||
expect(twoRoute.status).to.equal(301);
|
||||
|
||||
const threeRoute = config.routes.find(r => r.src === '/\\/three');
|
||||
expect(threeRoute.headers.Location).to.equal('/');
|
||||
expect(threeRoute.status).to.equal(302);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue