Add support for the object notation in redirects
This commit is contained in:
parent
b8ef648f8c
commit
4adedbaafe
10 changed files with 91 additions and 29 deletions
|
@ -1582,6 +1582,8 @@ export interface AstroAdapter {
|
|||
|
||||
type Body = string;
|
||||
|
||||
export type ValidRedirectStatus = 300 | 301 | 302 | 303 | 304 | 307 | 308;
|
||||
|
||||
// Shared types between `Astro` global and API context object
|
||||
interface AstroSharedContext<Props extends Record<string, any> = Record<string, any>> {
|
||||
/**
|
||||
|
@ -1611,7 +1613,7 @@ interface AstroSharedContext<Props extends Record<string, any> = Record<string,
|
|||
/**
|
||||
* Redirect to another page (**SSR Only**).
|
||||
*/
|
||||
redirect(path: string, status?: 301 | 302 | 303 | 307 | 308): Response;
|
||||
redirect(path: string, status?: ValidRedirectStatus): Response;
|
||||
|
||||
/**
|
||||
* Object accessed via Astro middleware
|
||||
|
@ -1834,6 +1836,11 @@ export interface RoutePart {
|
|||
spread: boolean;
|
||||
}
|
||||
|
||||
type RedirectConfig = string | {
|
||||
status: ValidRedirectStatus;
|
||||
destination: string;
|
||||
}
|
||||
|
||||
export interface RouteData {
|
||||
route: string;
|
||||
component: string;
|
||||
|
@ -1846,7 +1853,7 @@ export interface RouteData {
|
|||
segments: RoutePart[][];
|
||||
type: RouteType;
|
||||
prerender: boolean;
|
||||
redirect?: string;
|
||||
redirect?: RedirectConfig;
|
||||
redirectRoute?: RouteData;
|
||||
}
|
||||
|
||||
|
|
|
@ -518,14 +518,16 @@ async function generatePath(
|
|||
throw err;
|
||||
}
|
||||
|
||||
switch(response.status) {
|
||||
case 301:
|
||||
case 302: {
|
||||
switch(true) {
|
||||
case (response.status >= 300 && response.status < 400): {
|
||||
const location = getRedirectLocationOrThrow(response.headers);
|
||||
body = `<!doctype html>
|
||||
<title>Redirecting to: ${location}</title>
|
||||
<meta http-equiv="refresh" content="0;url=${location}" />`;
|
||||
pageData.route.redirect = location;
|
||||
// A dynamic redirect, set the location so that integrations know about it.
|
||||
if(pageData.route.type !== 'redirect') {
|
||||
pageData.route.redirect = location;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { RouteData, RedirectRouteData, Params } from '../../@types/astro';
|
||||
import type { RouteData, RedirectRouteData, Params, ValidRedirectStatus } from '../../@types/astro';
|
||||
|
||||
export function routeIsRedirect(route: RouteData | undefined): route is RedirectRouteData {
|
||||
return route?.type === 'redirect';
|
||||
|
@ -8,5 +8,20 @@ export function redirectRouteGenerate(redirectRoute: RouteData, data: Params): s
|
|||
const routeData = redirectRoute.redirectRoute;
|
||||
const route = redirectRoute.redirect;
|
||||
|
||||
return routeData?.generate(data) || routeData?.pathname || route || '/';
|
||||
if(typeof routeData !== 'undefined') {
|
||||
return routeData?.generate(data) || routeData?.pathname || '/';
|
||||
} else if(typeof route === 'string') {
|
||||
return route;
|
||||
} else if(typeof route === 'undefined') {
|
||||
return '/';
|
||||
}
|
||||
return route.destination;
|
||||
}
|
||||
|
||||
export function redirectRouteStatus(redirectRoute: RouteData): ValidRedirectStatus {
|
||||
const routeData = redirectRoute.redirectRoute;
|
||||
if(typeof routeData?.redirect === 'object') {
|
||||
return routeData.redirect.status;
|
||||
}
|
||||
return 301;
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export { getRedirectLocationOrThrow } from './validate.js';
|
||||
export { routeIsRedirect, redirectRouteGenerate } from './helpers.js';
|
||||
export { routeIsRedirect, redirectRouteGenerate, redirectRouteStatus } from './helpers.js';
|
||||
|
|
|
@ -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, redirectRouteGenerate } from '../redirects/index.js';
|
||||
import { routeIsRedirect, redirectRouteGenerate, redirectRouteStatus } from '../redirects/index.js';
|
||||
|
||||
interface GetParamsAndPropsOptions {
|
||||
mod: ComponentInstance;
|
||||
|
@ -114,9 +114,9 @@ export type RenderPage = {
|
|||
export async function renderPage({ mod, renderContext, env, apiContext }: RenderPage) {
|
||||
if(routeIsRedirect(renderContext.route)) {
|
||||
return new Response(null, {
|
||||
status: 301,
|
||||
status: redirectRouteStatus(renderContext.route),
|
||||
headers: {
|
||||
location: redirectRouteGenerate(renderContext.route!, renderContext.params)
|
||||
location: redirectRouteGenerate(renderContext.route, renderContext.params)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -48,7 +48,11 @@ describe('Astro.redirect', () => {
|
|||
redirects: {
|
||||
'/one': '/',
|
||||
'/two': '/',
|
||||
'/blog/[...slug]': '/articles/[...slug]'
|
||||
'/blog/[...slug]': '/articles/[...slug]',
|
||||
'/three': {
|
||||
status: 302,
|
||||
destination: '/'
|
||||
}
|
||||
}
|
||||
});
|
||||
await fixture.build();
|
||||
|
@ -68,6 +72,10 @@ describe('Astro.redirect', () => {
|
|||
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 () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { AstroConfig, RouteData } from 'astro';
|
||||
import type { AstroConfig, RouteData, ValidRedirectStatus } from 'astro';
|
||||
import fs from 'fs';
|
||||
|
||||
export type RedirectDefinition = {
|
||||
|
@ -6,9 +6,16 @@ export type RedirectDefinition = {
|
|||
input: string;
|
||||
target: string;
|
||||
weight: 0 | 1;
|
||||
status: 200 | 404 | 301;
|
||||
status: 200 | 404 | ValidRedirectStatus;
|
||||
};
|
||||
|
||||
function getRedirectStatus(route: RouteData): ValidRedirectStatus {
|
||||
if(typeof route.redirect === 'object') {
|
||||
return route.redirect.status;
|
||||
}
|
||||
return 301;
|
||||
}
|
||||
|
||||
export async function createRedirects(
|
||||
config: AstroConfig,
|
||||
routes: RouteData[],
|
||||
|
@ -27,8 +34,8 @@ export async function createRedirects(
|
|||
definitions.push({
|
||||
dynamic: false,
|
||||
input: route.pathname,
|
||||
target: route.redirect,
|
||||
status: 301,
|
||||
target: typeof route.redirect === 'object' ? route.redirect.destination : route.redirect,
|
||||
status: getRedirectStatus(route),
|
||||
weight: 1
|
||||
});
|
||||
continue;
|
||||
|
|
|
@ -15,6 +15,10 @@ describe('SSG - Redirects', () => {
|
|||
integrations: [testIntegration()],
|
||||
redirects: {
|
||||
'/other': '/',
|
||||
'/two': {
|
||||
status: 302,
|
||||
destination: '/'
|
||||
},
|
||||
'/blog/[...slug]': '/team/articles/[...slug]'
|
||||
}
|
||||
});
|
||||
|
@ -26,6 +30,7 @@ describe('SSG - Redirects', () => {
|
|||
let parts = redirects.split(/\s+/);
|
||||
expect(parts).to.deep.equal([
|
||||
'/blog/*', '/team/articles/*/index.html', '301',
|
||||
'/two', '/', '302',
|
||||
'/other', '/', '301',
|
||||
'/nope', '/', '301',
|
||||
'/team/articles/*', '/team/articles/*/index.html', '200'
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import type { AstroConfig, RouteData, RoutePart } from 'astro';
|
||||
import nodePath from 'node:path';
|
||||
|
||||
const pathJoin = nodePath.posix.join;
|
||||
|
||||
// https://vercel.com/docs/project-configuration#legacy/routes
|
||||
interface VercelRoute {
|
||||
|
@ -62,26 +65,33 @@ 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;
|
||||
return pathJoin(config.base, path);
|
||||
} else if(typeof route.redirect === 'object') {
|
||||
return pathJoin(config.base, route.redirect.destination);
|
||||
} else {
|
||||
return config.base + route.redirect;
|
||||
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[] = [];
|
||||
|
||||
|
||||
|
||||
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`);
|
||||
}
|
||||
redirects.push({
|
||||
src: config.base + getMatchPattern(route.segments),
|
||||
headers: { Location: getRedirectLocation(route, config) },
|
||||
status: getRedirectStatus(route)
|
||||
});
|
||||
} else if (route.type === 'page') {
|
||||
if (config.trailingSlash === 'always') {
|
||||
redirects.push({
|
||||
|
|
|
@ -12,6 +12,10 @@ describe('Redirects', () => {
|
|||
redirects: {
|
||||
'/one': '/',
|
||||
'/two': '/',
|
||||
'/three': {
|
||||
status: 302,
|
||||
destination: '/'
|
||||
},
|
||||
'/blog/[...slug]': '/team/articles/[...slug]',
|
||||
}
|
||||
});
|
||||
|
@ -32,9 +36,13 @@ describe('Redirects', () => {
|
|||
expect(oneRoute.headers.Location).to.equal('/');
|
||||
expect(oneRoute.status).to.equal(301);
|
||||
|
||||
const twoRoute = config.routes.find(r => r.src === '/\\/one');
|
||||
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 () => {
|
||||
|
|
Loading…
Reference in a new issue