Skip to content

Commit 17c58bd

Browse files
committed
feat(middleware): add restricted option
1 parent 26f9629 commit 17c58bd

File tree

7 files changed

+103
-50
lines changed

7 files changed

+103
-50
lines changed

Diff for: README.md

+25-6
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ const middleware = createRedirectionIoMiddleware({
5858
// Optional: matcher to specify which routes should be ignored by redirection.io middleware
5959
// Default: "^/((?!api/|_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)$"
6060
matcherRegex: "^/((?!api/|_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)$",
61+
// Optional: If true, redirection.io middleware will only redirect and not override the response
62+
// Default: false
63+
restricted: true,
6164
});
6265

6366
export default middleware;
@@ -78,13 +81,29 @@ createRedirectionIoMiddleware({ matcherRegex: null });
7881

7982
Here's a summary of the middleware options:
8083

81-
| Option | Type | Description |
82-
| -------------------- | -------------- | ---------------------------------------------------------------------------- |
83-
| `previousMiddleware` | Function | Middleware to be executed before redirection.io middleware |
84-
| `nextMiddleware` | Function | Middleware to be executed after redirection.io middleware |
85-
| `matcherRegex` | String or null | Regex to specify which routes should be handled by redirection.io middleware |
84+
| Option | Type | Description |
85+
| -------------------- | -------------- | ---------------------------------------------------------------------------------------------------- |
86+
| `previousMiddleware` | Function | Middleware to be executed before redirection.io middleware |
87+
| `nextMiddleware` | Function | Middleware to be executed after redirection.io middleware |
88+
| `matcherRegex` | String or null | Regex to specify which routes should be handled by redirection.io middleware |
89+
| `mode` | "full" or "light" | If "light", redirection.io middleware will only redirect and not override the response (default: "full") |
90+
| `logged` | Boolean | If true, redirection.io middleware will log information in Redirection.io (default: true) |
91+
## Light mode
8692

87-
### Next.js
93+
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.
94+
95+
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`.
96+
97+
This allows you to implement redirection behavior without modifying response content, ensuring smooth operation with RSC.
98+
99+
```typescript
100+
const middleware = createRedirectionIoMiddleware({
101+
//
102+
mode: "light",
103+
});
104+
```
105+
106+
## Next.js
88107

89108
If you are using next.js middlewares, you can use the `createRedirectionIoMiddleware` method
90109
from `@redirection.io/vercel-middleware/next` which is compatible with `NextRequest` type.

Diff for: middleware.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ type CreateMiddlewareConfig = {
55
previousMiddleware?: Middleware;
66
nextMiddleware?: Middleware;
77
matcherRegex?: string | null;
8+
mode?: "full" | "light";
9+
logged?: boolean;
810
};
911
export declare const createRedirectionIoMiddleware: (config: CreateMiddlewareConfig) => Middleware;
1012
declare const defaultMiddleware: Middleware;

Diff for: middleware.js

+38-35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { next } from "@vercel/edge";
22
import { ipAddress } from "@vercel/functions";
33
import * as redirectionio from "@redirection.io/redirectionio";
4+
import { NextResponse } from "next/server";
45
const REDIRECTIONIO_TOKEN = process.env.REDIRECTIONIO_TOKEN || "";
56
const REDIRECTIONIO_INSTANCE_NAME = process.env.REDIRECTIONIO_INSTANCE_NAME || "redirection-io-vercel-middleware";
67
const REDIRECTIONIO_VERSION = "redirection-io-vercel-middleware/0.3.12";
@@ -10,6 +11,8 @@ const REDIRECTIONIO_ADD_HEADER_RULE_IDS = process.env.REDIRECTIONIO_ADD_HEADER_R
1011
const REDIRECTIONIO_TIMEOUT = process.env.REDIRECTIONIO_TIMEOUT ? parseInt(process.env.REDIRECTIONIO_TIMEOUT, 10) : 500;
1112
const DEFAULT_CONFIG = {
1213
matcherRegex: "^/((?!api/|_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)$",
14+
mode: "full",
15+
logged: true,
1316
};
1417
export const createRedirectionIoMiddleware = (config) => {
1518
return async (request, context) => {
@@ -22,10 +25,8 @@ export const createRedirectionIoMiddleware = (config) => {
2225
return next();
2326
}
2427
// Avoid infinite loop
25-
if (
26-
request.headers.get("x-redirectionio-middleware") === "true" ||
27-
request.headers.get("User-Agent") === "Vercel Edge Functions"
28-
) {
28+
if (request.headers.get("x-redirectionio-middleware") === "true" ||
29+
request.headers.get("User-Agent") === "Vercel Edge Functions") {
2930
return next();
3031
}
3132
const body = request.body ? await request.arrayBuffer() : null;
@@ -41,13 +42,17 @@ export const createRedirectionIoMiddleware = (config) => {
4142
}
4243
middlewareRequest = middlewareResponseToRequest(middlewareRequest, response, body);
4344
}
44-
return handler(middlewareRequest, context, async (request, useFetch) => {
45+
return handler(middlewareRequest, context, config, async (request, useFetch) => {
4546
let response = null;
4647
if (config.nextMiddleware) {
4748
response = await config.nextMiddleware(request, context);
4849
if (response.status !== 200) {
4950
return response;
5051
}
52+
// If light mode, only return the response
53+
if (config.mode === "light") {
54+
return response;
55+
}
5156
request = middlewareResponseToRequest(request, response, body);
5257
}
5358
if (!useFetch) {
@@ -71,7 +76,7 @@ export const createRedirectionIoMiddleware = (config) => {
7176
};
7277
const defaultMiddleware = createRedirectionIoMiddleware({});
7378
export default defaultMiddleware;
74-
async function handler(request, context, fetchResponse) {
79+
async function handler(request, context, config, fetchResponse) {
7580
if (!REDIRECTIONIO_TOKEN) {
7681
console.warn("No REDIRECTIONIO_TOKEN environment variable found. Skipping redirection.io middleware.");
7782
return fetchResponse(request, false);
@@ -87,14 +92,18 @@ async function handler(request, context, fetchResponse) {
8792
});
8893
const url = new URL(request.url);
8994
const location = response.headers.get("Location");
90-
if (location && location.startsWith("/")) {
95+
const hasLocation = location && location.startsWith("/");
96+
if (hasLocation) {
9197
response.headers.set("Location", url.origin + location);
9298
}
93-
context.waitUntil(
94-
(async function () {
99+
if (config.logged) {
100+
context.waitUntil((async function () {
95101
await log(response, backendStatusCode, redirectionIORequest, startTimestamp, action, ip);
96-
})(),
97-
);
102+
})());
103+
}
104+
if (config.mode === "light" && hasLocation) {
105+
return NextResponse.redirect(url.origin + location, response.status);
106+
}
98107
return response;
99108
}
100109
function splitSetCookies(cookiesString) {
@@ -137,12 +146,14 @@ function splitSetCookies(cookiesString) {
137146
pos = nextStart;
138147
cookiesStrings.push(cookiesString.substring(start, lastComma));
139148
start = pos;
140-
} else {
149+
}
150+
else {
141151
// in param ',' or param separator ';',
142152
// we continue from that comma
143153
pos = lastComma + 1;
144154
}
145-
} else {
155+
}
156+
else {
146157
pos += 1;
147158
}
148159
}
@@ -154,12 +165,7 @@ function splitSetCookies(cookiesString) {
154165
}
155166
function createRedirectionIORequest(request, ip) {
156167
const urlObject = new URL(request.url);
157-
const redirectionioRequest = new redirectionio.Request(
158-
urlObject.pathname + urlObject.search,
159-
urlObject.host,
160-
urlObject.protocol.replace(":", ""),
161-
request.method,
162-
);
168+
const redirectionioRequest = new redirectionio.Request(urlObject.pathname + urlObject.search, urlObject.host, urlObject.protocol.replace(":", ""), request.method);
163169
request.headers.forEach((value, key) => {
164170
redirectionioRequest.add_header(key, value);
165171
});
@@ -195,7 +201,7 @@ function middlewareResponseToRequest(originalRequest, response, body) {
195201
}
196202
async function fetchRedirectionIOAction(redirectionIORequest) {
197203
try {
198-
const response = await Promise.race([
204+
const response = (await Promise.race([
199205
fetch("https://agent.redirection.io/" + REDIRECTIONIO_TOKEN + "/action", {
200206
method: "POST",
201207
body: redirectionIORequest.serialize().toString(),
@@ -206,13 +212,14 @@ async function fetchRedirectionIOAction(redirectionIORequest) {
206212
cache: "no-store",
207213
}),
208214
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), REDIRECTIONIO_TIMEOUT)),
209-
]);
215+
]));
210216
const actionStr = await response.text();
211217
if (actionStr === "") {
212218
return redirectionio.Action.empty();
213219
}
214220
return new redirectionio.Action(actionStr);
215-
} catch (e) {
221+
}
222+
catch (e) {
216223
console.error(e);
217224
return redirectionio.Action.empty();
218225
}
@@ -224,7 +231,8 @@ async function proxy(request, action, fetchResponse) {
224231
let response;
225232
if (statusCodeBeforeResponse === 0) {
226233
response = await fetchResponse(request, true);
227-
} else {
234+
}
235+
else {
228236
response = new Response("", {
229237
status: Number(statusCodeBeforeResponse),
230238
});
@@ -242,7 +250,8 @@ async function proxy(request, action, fetchResponse) {
242250
for (const cookie of cookies) {
243251
headerMap.add_header("set-cookie", cookie);
244252
}
245-
} else {
253+
}
254+
else {
246255
headerMap.add_header(key, value);
247256
}
248257
});
@@ -268,7 +277,8 @@ async function proxy(request, action, fetchResponse) {
268277
const { readable, writable } = new TransformStream();
269278
createBodyFilter(response.body, writable, bodyFilter);
270279
return [new Response(readable, response), backendStatusCode];
271-
} catch (err) {
280+
}
281+
catch (err) {
272282
console.error(err);
273283
const response = await fetchResponse(request, true);
274284
return [response, response.status];
@@ -315,15 +325,7 @@ async function log(response, backendStatusCode, redirectionioRequest, startTimes
315325
return;
316326
}
317327
try {
318-
const logAsJson = redirectionio.create_log_in_json(
319-
redirectionioRequest,
320-
response.status,
321-
responseHeaderMap,
322-
action,
323-
"vercel-edge-middleware/" + REDIRECTIONIO_VERSION,
324-
BigInt(startTimestamp),
325-
clientIP ?? "",
326-
);
328+
const logAsJson = redirectionio.create_log_in_json(redirectionioRequest, response.status, responseHeaderMap, action, "vercel-edge-middleware/" + REDIRECTIONIO_VERSION, BigInt(startTimestamp), clientIP ?? "");
327329
return await fetch("https://agent.redirection.io/" + REDIRECTIONIO_TOKEN + "/log", {
328330
method: "POST",
329331
body: logAsJson,
@@ -333,7 +335,8 @@ async function log(response, backendStatusCode, redirectionioRequest, startTimes
333335
},
334336
cache: "no-store",
335337
});
336-
} catch (err) {
338+
}
339+
catch (err) {
337340
console.error(err);
338341
}
339342
}

Diff for: middleware.ts

+30-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { next, RequestContext } from "@vercel/edge";
22
import { ipAddress } from "@vercel/functions";
33
import * as redirectionio from "@redirection.io/redirectionio";
4-
import type { NextRequest } from "next/server";
4+
import { NextResponse, type NextRequest } from "next/server";
55

66
const REDIRECTIONIO_TOKEN = process.env.REDIRECTIONIO_TOKEN || "";
77
const REDIRECTIONIO_INSTANCE_NAME = process.env.REDIRECTIONIO_INSTANCE_NAME || "redirection-io-vercel-middleware";
@@ -13,6 +13,8 @@ const REDIRECTIONIO_TIMEOUT = process.env.REDIRECTIONIO_TIMEOUT ? parseInt(proce
1313

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

1820
type Middleware = (request: Request | NextRequest, context: RequestContext) => Response | Promise<Response>;
@@ -23,6 +25,8 @@ type CreateMiddlewareConfig = {
2325
previousMiddleware?: Middleware;
2426
nextMiddleware?: Middleware;
2527
matcherRegex?: string | null;
28+
mode?: "full" | "light";
29+
logged?: boolean;
2630
};
2731

2832
export const createRedirectionIoMiddleware = (config: CreateMiddlewareConfig): Middleware => {
@@ -64,7 +68,7 @@ export const createRedirectionIoMiddleware = (config: CreateMiddlewareConfig): M
6468
middlewareRequest = middlewareResponseToRequest(middlewareRequest, response, body);
6569
}
6670

67-
return handler(middlewareRequest, context, async (request, useFetch): Promise<Response> => {
71+
return handler(middlewareRequest, context, config, async (request, useFetch): Promise<Response> => {
6872
let response: Response | null = null;
6973

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

81+
// If light mode, only return the response
82+
if (config.mode === "light") {
83+
return response;
84+
}
85+
7786
request = middlewareResponseToRequest(request, response, body);
7887
}
7988

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

106115
export default defaultMiddleware;
107116

108-
async function handler(request: Request, context: RequestContext, fetchResponse: FetchResponse): Promise<Response> {
117+
async function handler(
118+
request: Request,
119+
context: RequestContext,
120+
config: CreateMiddlewareConfig,
121+
fetchResponse: FetchResponse,
122+
): Promise<Response> {
109123
if (!REDIRECTIONIO_TOKEN) {
110124
console.warn("No REDIRECTIONIO_TOKEN environment variable found. Skipping redirection.io middleware.");
111125

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

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

131-
if (location && location.startsWith("/")) {
146+
if (hasLocation) {
132147
response.headers.set("Location", url.origin + location);
133148
}
134149

135-
context.waitUntil(
136-
(async function () {
137-
await log(response, backendStatusCode, redirectionIORequest, startTimestamp, action, ip);
138-
})(),
139-
);
150+
if (config.logged) {
151+
context.waitUntil(
152+
(async function () {
153+
await log(response, backendStatusCode, redirectionIORequest, startTimestamp, action, ip);
154+
})(),
155+
);
156+
}
157+
158+
if (config.mode === "light" && hasLocation) {
159+
return NextResponse.redirect(url.origin + location, response.status);
160+
}
140161

141162
return response;
142163
}

Diff for: next.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ type CreateMiddlewareConfig = {
44
previousMiddleware?: Middleware;
55
nextMiddleware?: Middleware;
66
matcherRegex?: string | null;
7+
mode?: "full" | "light";
8+
logged?: boolean;
79
};
810
export declare const createRedirectionIoMiddleware: (config: CreateMiddlewareConfig) => Middleware;
911
export {};

Diff for: next.js

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export const createRedirectionIoMiddleware = (config) => {
2020
previousMiddleware,
2121
nextMiddleware,
2222
...(configMatcherRegex ? { matcherRegex: configMatcherRegex } : {}),
23+
mode: config.mode ?? "full",
24+
logged: config.logged ?? true,
2325
});
2426
return async (req, context) => {
2527
const response = await edgeMiddleware(req, context);

Diff for: next.ts

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ type CreateMiddlewareConfig = {
88
previousMiddleware?: Middleware;
99
nextMiddleware?: Middleware;
1010
matcherRegex?: string | null;
11+
mode?: "full" | "light";
12+
logged?: boolean;
1113
};
1214

1315
export const createRedirectionIoMiddleware = (config: CreateMiddlewareConfig): Middleware => {
@@ -34,6 +36,8 @@ export const createRedirectionIoMiddleware = (config: CreateMiddlewareConfig): M
3436
previousMiddleware,
3537
nextMiddleware,
3638
...(configMatcherRegex ? { matcherRegex: configMatcherRegex } : {}),
39+
mode: config.mode ?? "full",
40+
logged: config.logged ?? true,
3741
});
3842

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

0 commit comments

Comments
 (0)