Skip to content

Commit

Permalink
feat(middleware): add light mode
Browse files Browse the repository at this point in the history
  • Loading branch information
baptadn committed Nov 6, 2024
1 parent 26f9629 commit e768195
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 24 deletions.
32 changes: 26 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ const middleware = createRedirectionIoMiddleware({
// Optional: matcher to specify which routes should be ignored by redirection.io middleware
// Default: "^/((?!api/|_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)$"
matcherRegex: "^/((?!api/|_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)$",
// Optional: If true, redirection.io middleware will only redirect and not override the response
// Default: false
restricted: true,
});

export default middleware;
Expand All @@ -78,13 +81,30 @@ createRedirectionIoMiddleware({ matcherRegex: null });

Here's a summary of the middleware options:

| Option | Type | Description |
| -------------------- | -------------- | ---------------------------------------------------------------------------- |
| `previousMiddleware` | Function | Middleware to be executed before redirection.io middleware |
| `nextMiddleware` | Function | Middleware to be executed after redirection.io middleware |
| `matcherRegex` | String or null | Regex to specify which routes should be handled by redirection.io middleware |
| Option | Type | Description |
| -------------------- | ----------------- | -------------------------------------------------------------------------------------------------------- |
| `previousMiddleware` | Function | Middleware to be executed before redirection.io middleware |
| `nextMiddleware` | Function | Middleware to be executed after redirection.io middleware |
| `matcherRegex` | String or null | Regex to specify which routes should be handled by redirection.io middleware |
| `mode` | `full` or `light` | If `light`, redirection.io middleware will only redirect and not override the response (default: `full`) |
| `logged` | Boolean | If true, redirection.io middleware will log information in Redirection.io (default: `true`) |

### Next.js
## Light mode

The response rewriting features (e.g., SEO overrides, custom body, etc.) of redirection.io are currently not compatible with React Server Components (RSC). This is due to the fact that Vercel’s middleware implementation does not follow standard middleware protocols, requiring us to fetch requests, which is incompatible with both RSC and Vercel’s implementation.

However, we provide a light mode that supports RSC by offering only the redirection functionality. To enable this mode, simply set the `mode` option to `light`.

This allows you to implement redirection behavior without modifying response content, ensuring smooth operation with RSC.

```typescript
const middleware = createRedirectionIoMiddleware({
//
mode: "light",
});
```

## Next.js

If you are using next.js middlewares, you can use the `createRedirectionIoMiddleware` method
from `@redirection.io/vercel-middleware/next` which is compatible with `NextRequest` type.
Expand Down
2 changes: 2 additions & 0 deletions middleware.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ type CreateMiddlewareConfig = {
previousMiddleware?: Middleware;
nextMiddleware?: Middleware;
matcherRegex?: string | null;
mode?: "full" | "light";
logged?: boolean;
};
export declare const createRedirectionIoMiddleware: (config: CreateMiddlewareConfig) => Middleware;
declare const defaultMiddleware: Middleware;
Expand Down
29 changes: 21 additions & 8 deletions middleware.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { next } from "@vercel/edge";
import { ipAddress } from "@vercel/functions";
import * as redirectionio from "@redirection.io/redirectionio";
import { NextResponse } from "next/server";
const REDIRECTIONIO_TOKEN = process.env.REDIRECTIONIO_TOKEN || "";
const REDIRECTIONIO_INSTANCE_NAME = process.env.REDIRECTIONIO_INSTANCE_NAME || "redirection-io-vercel-middleware";
const REDIRECTIONIO_VERSION = "redirection-io-vercel-middleware/0.3.12";
Expand All @@ -10,6 +11,8 @@ const REDIRECTIONIO_ADD_HEADER_RULE_IDS = process.env.REDIRECTIONIO_ADD_HEADER_R
const REDIRECTIONIO_TIMEOUT = process.env.REDIRECTIONIO_TIMEOUT ? parseInt(process.env.REDIRECTIONIO_TIMEOUT, 10) : 500;
const DEFAULT_CONFIG = {
matcherRegex: "^/((?!api/|_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)$",
mode: "full",
logged: true,
};
export const createRedirectionIoMiddleware = (config) => {
return async (request, context) => {
Expand Down Expand Up @@ -41,13 +44,17 @@ export const createRedirectionIoMiddleware = (config) => {
}
middlewareRequest = middlewareResponseToRequest(middlewareRequest, response, body);
}
return handler(middlewareRequest, context, async (request, useFetch) => {
return handler(middlewareRequest, context, config, async (request, useFetch) => {
let response = null;
if (config.nextMiddleware) {
response = await config.nextMiddleware(request, context);
if (response.status !== 200) {
return response;
}
// If light mode, only return the response
if (config.mode === "light") {
return response;
}
request = middlewareResponseToRequest(request, response, body);
}
if (!useFetch) {
Expand All @@ -71,7 +78,7 @@ export const createRedirectionIoMiddleware = (config) => {
};
const defaultMiddleware = createRedirectionIoMiddleware({});
export default defaultMiddleware;
async function handler(request, context, fetchResponse) {
async function handler(request, context, config, fetchResponse) {
if (!REDIRECTIONIO_TOKEN) {
console.warn("No REDIRECTIONIO_TOKEN environment variable found. Skipping redirection.io middleware.");
return fetchResponse(request, false);
Expand All @@ -87,14 +94,20 @@ async function handler(request, context, fetchResponse) {
});
const url = new URL(request.url);
const location = response.headers.get("Location");
if (location && location.startsWith("/")) {
const hasLocation = location && location.startsWith("/");
if (hasLocation) {
response.headers.set("Location", url.origin + location);
}
context.waitUntil(
(async function () {
await log(response, backendStatusCode, redirectionIORequest, startTimestamp, action, ip);
})(),
);
if (config.logged) {
context.waitUntil(
(async function () {
await log(response, backendStatusCode, redirectionIORequest, startTimestamp, action, ip);
})(),
);
}
if (config.mode === "light" && hasLocation) {
return NextResponse.redirect(url.origin + location, response.status);
}
return response;
}
function splitSetCookies(cookiesString) {
Expand Down
41 changes: 31 additions & 10 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { next, RequestContext } from "@vercel/edge";
import { ipAddress } from "@vercel/functions";
import * as redirectionio from "@redirection.io/redirectionio";
import type { NextRequest } from "next/server";
import { NextResponse, type NextRequest } from "next/server";

const REDIRECTIONIO_TOKEN = process.env.REDIRECTIONIO_TOKEN || "";
const REDIRECTIONIO_INSTANCE_NAME = process.env.REDIRECTIONIO_INSTANCE_NAME || "redirection-io-vercel-middleware";
Expand All @@ -13,7 +13,9 @@ const REDIRECTIONIO_TIMEOUT = process.env.REDIRECTIONIO_TIMEOUT ? parseInt(proce

const DEFAULT_CONFIG = {
matcherRegex: "^/((?!api/|_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)$",
};
mode: "full",
logged: true,
} as const;

type Middleware = (request: Request | NextRequest, context: RequestContext) => Response | Promise<Response>;

Expand All @@ -23,6 +25,8 @@ type CreateMiddlewareConfig = {
previousMiddleware?: Middleware;
nextMiddleware?: Middleware;
matcherRegex?: string | null;
mode?: "full" | "light";
logged?: boolean;
};

export const createRedirectionIoMiddleware = (config: CreateMiddlewareConfig): Middleware => {
Expand Down Expand Up @@ -64,7 +68,7 @@ export const createRedirectionIoMiddleware = (config: CreateMiddlewareConfig): M
middlewareRequest = middlewareResponseToRequest(middlewareRequest, response, body);
}

return handler(middlewareRequest, context, async (request, useFetch): Promise<Response> => {
return handler(middlewareRequest, context, config, async (request, useFetch): Promise<Response> => {
let response: Response | null = null;

if (config.nextMiddleware) {
Expand All @@ -74,6 +78,11 @@ export const createRedirectionIoMiddleware = (config: CreateMiddlewareConfig): M
return response;
}

// If light mode, only return the response
if (config.mode === "light") {
return response;
}

request = middlewareResponseToRequest(request, response, body);
}

Expand Down Expand Up @@ -105,7 +114,12 @@ const defaultMiddleware = createRedirectionIoMiddleware({});

export default defaultMiddleware;

async function handler(request: Request, context: RequestContext, fetchResponse: FetchResponse): Promise<Response> {
async function handler(
request: Request,
context: RequestContext,
config: CreateMiddlewareConfig,
fetchResponse: FetchResponse,
): Promise<Response> {
if (!REDIRECTIONIO_TOKEN) {
console.warn("No REDIRECTIONIO_TOKEN environment variable found. Skipping redirection.io middleware.");

Expand All @@ -127,16 +141,23 @@ async function handler(request: Request, context: RequestContext, fetchResponse:

const url = new URL(request.url);
const location = response.headers.get("Location");
const hasLocation = location && location.startsWith("/");

if (location && location.startsWith("/")) {
if (hasLocation) {
response.headers.set("Location", url.origin + location);
}

context.waitUntil(
(async function () {
await log(response, backendStatusCode, redirectionIORequest, startTimestamp, action, ip);
})(),
);
if (config.logged) {
context.waitUntil(
(async function () {
await log(response, backendStatusCode, redirectionIORequest, startTimestamp, action, ip);
})(),
);
}

if (config.mode === "light" && hasLocation) {
return NextResponse.redirect(url.origin + location, response.status);
}

return response;
}
Expand Down
2 changes: 2 additions & 0 deletions next.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ type CreateMiddlewareConfig = {
previousMiddleware?: Middleware;
nextMiddleware?: Middleware;
matcherRegex?: string | null;
mode?: "full" | "light";
logged?: boolean;
};
export declare const createRedirectionIoMiddleware: (config: CreateMiddlewareConfig) => Middleware;
export {};
2 changes: 2 additions & 0 deletions next.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const createRedirectionIoMiddleware = (config) => {
previousMiddleware,
nextMiddleware,
...(configMatcherRegex ? { matcherRegex: configMatcherRegex } : {}),
mode: config.mode ?? "full",
logged: config.logged ?? true,
});
return async (req, context) => {
const response = await edgeMiddleware(req, context);
Expand Down
4 changes: 4 additions & 0 deletions next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ type CreateMiddlewareConfig = {
previousMiddleware?: Middleware;
nextMiddleware?: Middleware;
matcherRegex?: string | null;
mode?: "full" | "light";
logged?: boolean;
};

export const createRedirectionIoMiddleware = (config: CreateMiddlewareConfig): Middleware => {
Expand All @@ -34,6 +36,8 @@ export const createRedirectionIoMiddleware = (config: CreateMiddlewareConfig): M
previousMiddleware,
nextMiddleware,
...(configMatcherRegex ? { matcherRegex: configMatcherRegex } : {}),
mode: config.mode ?? "full",
logged: config.logged ?? true,
});

return async (req: NextRequest, context: NextFetchEvent) => {
Expand Down

0 comments on commit e768195

Please sign in to comment.