Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(middleware): add light mode #9

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ 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 light, redirection.io middleware will only redirect and not override the response
// Default: "full"
mode: "light",
// Optional: If true, redirection.io middleware will log information in Redirection.io
// Default: true
logged: true,
});

export default middleware;
Expand All @@ -78,13 +84,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