Skip to content

Commit

Permalink
Redirects (#7067)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
5 people authored Jun 5, 2023
1 parent dd1a6b6 commit 57f8d14
Show file tree
Hide file tree
Showing 70 changed files with 1,733 additions and 319 deletions.
33 changes: 33 additions & 0 deletions .changeset/chatty-actors-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
'astro': minor
---

Experimental redirects support

This change adds support for the redirects RFC, currently in stage 3: https://github.com/withastro/roadmap/pull/587

Now you can specify redirects in your Astro config:

```js
import { defineConfig } from 'astro/config';

export defineConfig({
redirects: {
'/blog/old-post': '/blog/new-post'
}
});
```

You can also specify spread routes using the same syntax as in file-based routing:

```js
import { defineConfig } from 'astro/config';

export defineConfig({
redirects: {
'/blog/[...slug]': '/articles/[...slug]'
}
});
```

By default Astro will build HTML files that contain the `<meta http-equiv="refresh">` tag. Adapters can also support redirect routes and create configuration for real HTTP-level redirects in production.
7 changes: 7 additions & 0 deletions .changeset/fuzzy-ladybugs-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astrojs/cloudflare': minor
---

Support for experimental redirects

This adds support for the redirects RFC in the Cloudflare adapter. No changes are necessary, simply use configured redirects and the adapter will update your `_redirects` file.
7 changes: 7 additions & 0 deletions .changeset/hip-news-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astrojs/vercel': minor
---

Support for experimental redirects

This adds support for the redirects RFC in the Vercel adapter. No changes are necessary, simply use configured redirects and the adapter will output the vercel.json file with the configuration values.
9 changes: 9 additions & 0 deletions .changeset/twenty-suns-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@astrojs/netlify': minor
---

Support for experimental redirects

This adds support for the redirects RFC in the Netlify adapter, including a new `@astrojs/netlify/static` adapter for static sites.

No changes are necessary when using SSR. Simply use configured redirects and the adapter will update your `_redirects` file.
1 change: 1 addition & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
},
"dependencies": {
"@astrojs/compiler": "^1.4.0",
"@astrojs/internal-helpers": "^0.1.0",
"@astrojs/language-server": "^1.0.0",
"@astrojs/markdown-remark": "^2.2.1",
"@astrojs/telemetry": "^2.1.1",
Expand Down
111 changes: 108 additions & 3 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export interface CLIFlags {
open?: boolean;
experimentalAssets?: boolean;
experimentalMiddleware?: boolean;
experimentalRedirects?: boolean;
}

export interface BuildConfig {
Expand Down Expand Up @@ -452,6 +453,53 @@ export interface AstroUserConfig {
*/
cacheDir?: string;



/**
* @docs
* @name redirects (Experimental)
* @type {RedirectConfig}
* @default `{}`
* @version 2.6.0
* @description Specify a mapping of redirects where the key is the route to match
* and the value is the path to redirect to.
*
* You can redirect both static and dynamic routes, but only to the same kind of route.
* For example you cannot have a `'/article': '/blog/[...slug]'` redirect.
*
*
* ```js
* {
* redirects: {
* '/old': '/new',
* '/blog/[...slug]': '/articles/[...slug]',
* }
* }
* ```
*
*
* For statically-generated sites with no adapter installed, this will produce a client redirect using a [`<meta http-equiv="refresh">` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#http-equiv) and does not support status codes.
*
* When using SSR or with a static adapter in `output: static`
* mode, status codes are supported.
* Astro will serve redirected GET requests with a status of `301`
* and use a status of `308` for any other request method.
*
* You can customize the [redirection status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages) using an object in the redirect config:
*
* ```js
* {
* redirects: {
* '/other': {
* status: 302,
* destination: '/place',
* },
* }
* }
* ```
*/
redirects?: RedirectConfig;

/**
* @docs
* @name site
Expand Down Expand Up @@ -733,6 +781,29 @@ export interface AstroUserConfig {
* ```
*/
serverEntry?: string;
/**
* @docs
* @name build.redirects
* @type {boolean}
* @default `true`
* @version 2.6.0
* @description
* Specifies whether redirects will be output to HTML during the build.
* This option only applies to `output: 'static'` mode; in SSR redirects
* are treated the same as all responses.
*
* This option is mostly meant to be used by adapters that have special
* configuration files for redirects and do not need/want HTML based redirects.
*
* ```js
* {
* build: {
* redirects: false
* }
* }
* ```
*/
redirects?: boolean;
};

/**
Expand Down Expand Up @@ -1179,6 +1250,27 @@ export interface AstroUserConfig {
* ```
*/
hybridOutput?: boolean;

/**
* @docs
* @name experimental.redirects
* @type {boolean}
* @default `false`
* @version 2.6.0
* @description
* Enable experimental support for redirect configuration. With this enabled
* you can set redirects via the top-level `redirects` property. To enable
* this feature, set `experimental.redirects` to `true`.
*
* ```js
* {
* experimental: {
* redirects: true,
* },
* }
* ```
*/
redirects?: boolean;
};

// Legacy options to be removed
Expand Down Expand Up @@ -1578,6 +1670,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>> {
/**
Expand Down Expand Up @@ -1607,7 +1701,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
Expand Down Expand Up @@ -1805,7 +1899,7 @@ export type MiddlewareNext<R> = () => Promise<R>;
export type MiddlewareHandler<R> = (
context: APIContext,
next: MiddlewareNext<R>
) => Promise<R> | Promise<void> | void;
) => Promise<R> | R | Promise<void> | void;

export type MiddlewareResponseHandler = MiddlewareHandler<Response>;
export type MiddlewareEndpointHandler = MiddlewareHandler<Response | EndpointOutput>;
Expand All @@ -1822,14 +1916,19 @@ export interface AstroPluginOptions {
logging: LogOptions;
}

export type RouteType = 'page' | 'endpoint';
export type RouteType = 'page' | 'endpoint' | 'redirect';

export interface RoutePart {
content: string;
dynamic: boolean;
spread: boolean;
}

type RedirectConfig = string | {
status: ValidRedirectStatus;
destination: string;
}

export interface RouteData {
route: string;
component: string;
Expand All @@ -1842,6 +1941,12 @@ export interface RouteData {
segments: RoutePart[][];
type: RouteType;
prerender: boolean;
redirect?: RedirectConfig;
redirectRoute?: RouteData;
}

export type RedirectRouteData = RouteData & {
redirect: string;
}

export type SerializedRouteData = Omit<RouteData, 'generate' | 'pattern'> & {
Expand Down
28 changes: 20 additions & 8 deletions packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
createStylesheetElementSet,
} from '../render/ssr-element.js';
import { matchRoute } from '../routing/match.js';
import { RedirectComponentInstance } from '../redirects/index.js';
export { deserializeManifest } from './common.js';

const clientLocalsSymbol = Symbol.for('astro.locals');
Expand Down Expand Up @@ -137,22 +138,20 @@ export class App {
defaultStatus = 404;
}

let page = await this.#manifest.pageMap.get(routeData.component)!();
let mod = await page.page();
let mod = await this.#getModuleForRoute(routeData);

if (routeData.type === 'page') {
if (routeData.type === 'page' || routeData.type === 'redirect') {
let response = await this.#renderPage(request, routeData, mod, defaultStatus);

// If there was a known error code, try sending the according page (e.g. 404.astro / 500.astro).
if (response.status === 500 || response.status === 404) {
const errorPageData = matchRoute('/' + response.status, this.#manifestData);
if (errorPageData && errorPageData.route !== routeData.route) {
page = await this.#manifest.pageMap.get(errorPageData.component)!();
mod = await page.page();
const errorRouteData = matchRoute('/' + response.status, this.#manifestData);
if (errorRouteData && errorRouteData.route !== routeData.route) {
mod = await this.#getModuleForRoute(errorRouteData);
try {
let errorResponse = await this.#renderPage(
request,
errorPageData,
errorRouteData,
mod,
response.status
);
Expand All @@ -172,6 +171,19 @@ export class App {
return getSetCookiesFromResponse(response);
}

async #getModuleForRoute(route: RouteData): Promise<ComponentInstance> {
if(route.type === 'redirect') {
return RedirectComponentInstance;
} else {
const importComponentInstance = this.#manifest.pageMap.get(route.component);
if(!importComponentInstance) {
throw new Error(`Unexpectedly unable to find a component instance for route ${route.route}`);
}
const built = await importComponentInstance();
return built.page();
}
}

async #renderPage(
request: Request,
routeData: RouteData,
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/build/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function getOutFolder(
case 'endpoint':
return new URL('.' + appendForwardSlash(npath.dirname(pathname)), outRoot);
case 'page':
case 'redirect':
switch (astroConfig.build.format) {
case 'directory': {
if (STATUS_CODE_PAGES.has(pathname)) {
Expand All @@ -51,6 +52,7 @@ export function getOutFile(
case 'endpoint':
return new URL(npath.basename(pathname), outFolder);
case 'page':
case 'redirect':
switch (astroConfig.build.format) {
case 'directory': {
if (STATUS_CODE_PAGES.has(pathname)) {
Expand Down
Loading

0 comments on commit 57f8d14

Please sign in to comment.