-
Notifications
You must be signed in to change notification settings - Fork 2
/
middleware.ts
121 lines (115 loc) · 3.9 KB
/
middleware.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { get } from "@vercel/edge-config";
import { NextRequest, NextResponse } from "next/server";
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api|_next/static|_next/image|favicon.ico).*)",
],
};
// Configuration stored in Edge Config.
interface BlueGreenConfig {
deploymentDomainBlue: string;
deploymentDomainGreen: string;
trafficGreenPercent: number;
}
export async function middleware(req: NextRequest) {
// We don't want to run blue-green during development.
if (process.env.NODE_ENV !== "production") {
return NextResponse.next();
}
// Skip if the middleware has already run.
if (req.headers.get("x-deployment-override")) {
return getDeploymentWithCookieBasedOnEnvVar();
}
// We skip blue-green when accesing from deployment urls
if (req.nextUrl.hostname === process.env.VERCEL_URL) {
return NextResponse.next();
}
// We only want to run blue-green for GET requests that are for HTML documents.
if (req.method !== "GET") {
return NextResponse.next();
}
if (req.headers.get("sec-fetch-dest") !== "document") {
return NextResponse.next();
}
// Skip if the request is coming from Vercel's deployment system.
if (/vercel/i.test(req.headers.get("user-agent") || "")) {
return NextResponse.next();
}
if (!process.env.EDGE_CONFIG) {
console.warn("EDGE_CONFIG env variable not set. Skipping blue-green.");
return NextResponse.next();
}
// Get the blue-green configuration from Edge Config.
const blueGreenConfig = await get<BlueGreenConfig>(
"blue-green-configuration"
);
if (!blueGreenConfig) {
console.warn("No blue-green configuration found");
return NextResponse.next();
}
const servingDeploymentDomain = process.env.VERCEL_URL;
const selectedDeploymentDomain =
selectBlueGreenDeploymentDomain(blueGreenConfig);
console.info(
"Selected deployment domain",
selectedDeploymentDomain,
blueGreenConfig
);
if (!selectedDeploymentDomain) {
return NextResponse.next();
}
// The selected deployment domain is the same as the one serving the request.
if (servingDeploymentDomain === selectedDeploymentDomain) {
return getDeploymentWithCookieBasedOnEnvVar();
}
// Fetch the HTML document from the selected deployment domain and return it to the user.
const headers = new Headers(req.headers);
headers.set("x-deployment-override", selectedDeploymentDomain);
headers.set(
"x-vercel-protection-bypass",
process.env.VERCEL_AUTOMATION_BYPASS_SECRET || "unknown"
);
const url = new URL(req.url);
url.hostname = selectedDeploymentDomain;
return fetch(url, {
headers,
redirect: "manual",
});
}
// Selects the deployment domain based on the blue-green configuration.
function selectBlueGreenDeploymentDomain(blueGreenConfig: BlueGreenConfig) {
const random = Math.random() * 100;
const selected =
random < blueGreenConfig.trafficGreenPercent
? blueGreenConfig.deploymentDomainGreen
: blueGreenConfig.deploymentDomainBlue || process.env.VERCEL_URL;
if (!selected) {
console.error("Blue green configuration error", blueGreenConfig);
}
if (/^http/.test(selected || "")) {
return new URL(selected || "").hostname;
}
return selected;
}
function getDeploymentWithCookieBasedOnEnvVar() {
console.log(
"Setting cookie based on env var",
process.env.VERCEL_DEPLOYMENT_ID
);
const response = NextResponse.next();
// We need to set this cookie because next.js does not do this by default, but we do want
// the deployment choice to survive a client-side navigation.
response.cookies.set("__vdpl", process.env.VERCEL_DEPLOYMENT_ID || "", {
sameSite: "strict",
httpOnly: true,
maxAge: 60 * 60 * 24, // 24 hours
});
return response;
}