Skip to content

Commit

Permalink
feat: rerouting
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico committed Apr 24, 2024
1 parent a5c474b commit e2d9f3a
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 11 deletions.
49 changes: 49 additions & 0 deletions .changeset/pink-ligers-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
"astro": minor
---

Add experimental rerouting in Astro, via `reroute()` function and `next()` function.

The feature is available via experimental flag:

```js
export default defineConfig({
experimental: {
rerouting: true
}
})
```

When enabled, you can use `reroute()` to **render** another page without changing the URL of the browser in Astro pages and endpoints.

```astro
---
// src/pages/dashboard.astro
if (!Astro.props.allowed) {
return Astro.reroute("/")
}
---
```

```js
// src/pages/api.js
export function GET(ctx) {
if (!ctx.locals.allowed) {
return ctx.reroute("/")
}
}
```

The middleware `next()` function now accepts the same payload of the `reroute()` function. For example, with `next("/")`, you can call the next middleware function with a new `Request`.

```js
// src/middleware.js
export function onRequest(ctx, next) {
if (!ctx.cookies.get("allowed")) {
return next("/") // new signature
}
return next();
}
```

> **NOTE**: please [read the RFC](https://github.com/withastro/roadmap/blob/feat/reroute/proposals/0047-rerouting.md) to understand the current expectations of the new APIs.
68 changes: 62 additions & 6 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,16 @@ export interface AstroGlobal<
*/
redirect: AstroSharedContext['redirect'];
/**
* TODO add documentation
* It reroutes to another page. As opposed to redirects, the URL won't change, and Astro will render the HTML emitted
* by the rerouted URL passed as argument.
*
* ## Example
*
* ```js
* if (pageIsNotEnabled) {
* return Astro.reroute('/fallback-page')
* }
* ```
*/
reroute: AstroSharedContext['reroute'];
/**
Expand Down Expand Up @@ -1640,7 +1649,7 @@ export interface AstroUserConfig {
domains?: Record<string, string>;
};

/** ⚠️ WARNING: SUBJECT TO CHANGE */
/** ! WARNING: SUBJECT TO CHANGE */
db?: Config.Database;

/**
Expand Down Expand Up @@ -1927,10 +1936,38 @@ export interface AstroUserConfig {
* @name experimental.rerouting
* @type {boolean}
* @default `false`
* @version 4.6.0
* @version 4.8.0
* @description
*
* TODO
* Enables the use of rerouting features in Astro pages, Endpoints and Astro middleware:
*
* ```astro
* ---
* // src/pages/dashboard.astro
* if (!Astro.props.allowed) {
* return Astro.reroute("/")
* }
* ---
* ```
*
* ```js
* // src/pages/api.js
* export function GET(ctx) {
* if (!ctx.locals.allowed) {
* return ctx.reroute("/")
* }
* }
* ```
*
* ```js
* // src/middleware.js
* export function onRequest(ctx, next) {
* if (!ctx.cookies.get("allowed")) {
* return next("/") // new signature
* }
* return next();
* }
* ```
*/
rerouting: boolean;
};
Expand Down Expand Up @@ -2495,7 +2532,16 @@ interface AstroSharedContext<
redirect(path: string, status?: ValidRedirectStatus): Response;

/**
* TODO: add documentation
* It reroutes to another page. As opposed to redirects, the URL won't change, and Astro will render the HTML emitted
* by the rerouted URL passed as argument.
*
* ## Example
*
* ```js
* if (pageIsNotEnabled) {
* return Astro.reroute('/fallback-page')
* }
* ```
*/
reroute(reroutePayload: ReroutePayload): Promise<Response>;

Expand Down Expand Up @@ -2614,7 +2660,17 @@ export interface APIContext<
redirect: AstroSharedContext['redirect'];

/**
* TODO: docs
* It reroutes to another page. As opposed to redirects, the URL won't change, and Astro will render the HTML emitted
* by the rerouted URL passed as argument.
*
* ## Example
*
* ```ts
* // src/pages/secret.ts
* export function GET(ctx) {
* return ctx.reroute(new URL("../"), ctx.url);
* }
* ```
*/
reroute: AstroSharedContext['reroute'];

Expand Down
1 change: 0 additions & 1 deletion packages/astro/src/core/app/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ export class AppPipeline extends Pipeline {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
} else {
// TODO: handle error properly
throw new Error('Route not found');
}
}
Expand Down
16 changes: 14 additions & 2 deletions packages/astro/src/core/middleware/callMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
ReroutePayload,
} from '../../@types/astro.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import type { Logger } from '../logger/core.js';

/**
* Utility function that is in charge of calling the middleware.
Expand Down Expand Up @@ -46,14 +47,25 @@ export async function callMiddleware(
responseFunction: (
apiContext: APIContext,
reroutePayload?: ReroutePayload
) => Promise<Response> | Response
) => Promise<Response> | Response,
// TODO: remove these two arguments once rerouting goes out of experimental
enableRerouting: boolean,
logger: Logger
): Promise<Response> {
let nextCalled = false;
let responseFunctionPromise: Promise<Response> | Response | undefined = undefined;
const next: MiddlewareNext = async (payload) => {
nextCalled = true;
if (enableRerouting) {
responseFunctionPromise = responseFunction(apiContext, payload);
} else {
logger.warn(
'router',
'You tried to use the routing feature without enabling it via experimental flag. This is not allowed.'
);
responseFunctionPromise = responseFunction(apiContext);
}
// We need to pass the APIContext pass to `callMiddleware` because it can be mutated across middleware functions
responseFunctionPromise = responseFunction(apiContext, payload);
return responseFunctionPromise;
};

Expand Down
8 changes: 7 additions & 1 deletion packages/astro/src/core/render-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,13 @@ export class RenderContext {
}
};

const response = await callMiddleware(middleware, apiContext, lastNext);
const response = await callMiddleware(
middleware,
apiContext,
lastNext,
this.pipeline.manifest.reroutingEnabled,
this.pipeline.logger
);
if (response.headers.get(ROUTE_TYPE_HEADER)) {
response.headers.delete(ROUTE_TYPE_HEADER);
}
Expand Down
1 change: 0 additions & 1 deletion packages/astro/src/vite-plugin-astro-server/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ export class DevPipeline extends Pipeline {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
} else {
// TODO: handle error properly
throw new Error('Route not found');
}
}
Expand Down

0 comments on commit e2d9f3a

Please sign in to comment.