我需要为多语言网站实现基于 cookie 的重定向逻辑。我有 Next.js 中间件的可用代码,但不幸的是,它无法在 Docker 的独立设置中使用(我不使用 Vercel)。
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 files)
* - URLs ending with a file extension (e.g., .html, .js, .css, etc.)
*/
'/((?!api|_next|\\.\\.).+)',
],
};
export function middleware(request: NextRequest) {
const { pathname, search, defaultLocale, locale } = request.nextUrl;
const localeCookie = request.cookies.get('NEXT_LOCALE')?.value;
if (!localeCookie && locale !== defaultLocale) {
return NextResponse.redirect(new URL(`/${defaultLocale}${pathname}${search}`, request.url));
}
if (localeCookie && locale !== localeCookie) {
return NextResponse.redirect(new URL(`/${localeCookie}${pathname}${search}`, request.url));
}
return NextResponse.next();
}
我想实现 Varnish 设置来代替 Next.js 解决方案:
vcl 4.1;
import cookie;
sub vcl_recv {
...
if (req.url ~ "^/_next" || req.url ~ "/api/" || req.url ~ "/\.(.*)$/") {
return (pass);
}
# Extract the locale from the URL, the default locale is hidden
if (req.url ~ "^/[a-z]{2}/") {
set req.http.locale = regsub(req.url, "^/([a-z]{2})(/.*)?$", "\1");
} else {
set req.http.locale = "";
}
# Extract NEXT_LOCALE cookie
if (req.http.cookie) {
cookie.parse(req.http.cookie);
set req.http.localeCookie = cookie.get("NEXT_LOCALE");
set req.http.cookie = cookie.get_string();
}
// If locale is specified and NEXT_LOCALE is missing, redirect to the default locale.
if (req.http.locale != "" && !req.http.localeCookie) {
# set req.http.X-Redirect-Url = ...;
return (synth(302));
}
// If NEXT_LOCALE is present and the locale is not equal to the locale from cookie, then redirect to the cookie locale
else if (req.http.locale != "" && req.http.locale != req.http.localeCookie) {
# set req.http.X-Redirect-Url = ...;
return (synth(302));
}
...
return (hash);
}
sub vcl_synth {
# Perform the actual redirection based on the X-Redirect-Url header
if (resp.status == 302 && req.http.X-Redirect-Url) {
set resp.http.location = req.http.X-Redirect-Url;
set resp.reason = "Moved";
return (deliver);
}
}
答案1
如果有人对原始问题感兴趣,“强制”重定向到默认语言环境,而不管请求语言环境如何,除非设置了 cookie:
vcl 4.1;
import std;
import cookie;
sub vcl_recv {
...
// Bypass redirection logic for certain request paths
if (req.url ~ "^/_next" || req.url ~ "/api/" || req.url ~ "/\.(.*)$/") {
return (pass);
}
// Extract the locale from the URL
if (req.url ~ "^/([a-z]{2})(/|$|\?)") {
set req.http.X-Request-Locale = regsub(req.url, "^/([a-z]{2})(/|$|\?).*", "\1");
}
// Extract NEXT_LOCALE cookie
if (req.http.cookie) {
cookie.parse(req.http.cookie);
set req.http.X-Next-Locale = cookie.get("NEXT_LOCALE");
set req.http.cookie = cookie.get_string();
}
// If X-Next-Locale is `uk` (default) unset it
if (req.http.X-Next-Locale == "uk") {
unset req.http.X-Next-Locale;
}
// If X-Next-Locale is not present and request locale is `uk` (default) perform a 301 (Moved Permanently) redirect
if (req.http.X-Next-Locale !~ "^(uk|ru)$" && req.http.X-Request-Locale == "uk") {
set req.http.X-Status-Code = 301;
// Remove the locale from the URL
set req.url = regsub(req.url, "^/([a-z]{2})(/|$|\?)", "/\2");
// Remove double slashes if present in the URL
set req.url = regsuball(req.url, "//+", "/");
return (synth(750));
}
// If X-Request-Locale is within existing locales or X-Next-Locale is within existing locales and X-Request-Locale is missing and
// X-Request-Locale (url locale) does not match X-Next-Locale (cookie locale)
// redirect to X-Next-Locale (cookie) if present, or to the default locale if absent
if (
(req.http.X-Request-Locale ~ "^(uk|ru)$" || (req.http.X-Next-Locale ~ "^(uk|ru)$" && !req.http.X-Request-Locale)) &&
(req.http.X-Next-Locale != req.http.X-Request-Locale)
) {
// Perform a 307 (Temporary Redirect)
set req.http.X-Status-Code = 307;
// If the URL doesn't contain a locale, add the X-Next-Locale to it (the default locale is empty)
if (req.url !~ "^/([a-z]{2})(/|$|\?)") {
set req.url = "/" + req.http.X-Next-Locale + req.url;
}
// If the URL already contains a locale, update it with X-Next-Locale
else {
set req.url = regsub(req.url, "^/([a-z]{2})(/|$|\?)", "/" + req.http.X-Next-Locale + "\2");
}
// Remove double slashes if present in the URL
set req.url = regsuball(req.url, "//+", "/");
return (synth(750));
}
// Remove any cookies left
unset req.http.Cookie;
return (hash);
}
sub vcl_synth {
...
}
答案2
在 Chat-GPT 正则表达式的帮助下,我最终得到了更简单的基于 cookie 的重定向逻辑和默认(隐藏)语言环境处理。谢谢您的建议。
vcl 4.1;
import std;
import cookie;
...
sub vcl_recv {
...
// Bypass redirection logic for certain request paths
if (req.url ~ "^/_next" || req.url ~ "/api/" || req.url ~ "/\.(.*)$/") {
return (pass);
}
// Extract the locale from the URL
if (req.url ~ "^/([a-z]{2})(/|$|\?)") {
set req.http.X-Request-Locale = regsub(req.url, "^/([a-z]{2})(/|$|\?).*", "\1");
}
// Extract NEXT_LOCALE cookie
if (req.http.cookie) {
cookie.parse(req.http.cookie);
set req.http.X-Next-Locale = cookie.get("NEXT_LOCALE");
set req.http.cookie = cookie.get_string();
}
// If X-Next-Locale is `uk` (default) unset it
if (req.http.X-Next-Locale == "uk") {
unset req.http.X-Next-Locale;
}
// If X-Request-Locale is `uk` perform a 301 (Moved Permanently) redirect
if (req.http.X-Request-Locale == "uk" && req.http.X-Next-Locale !~ "^(uk|ru)$") {
set req.http.X-Status-Code = 301;
// Remove the locale from the URL
set req.url = regsub(req.url, "^/([a-z]{2})(/|$|\?)", "/\2");
// Remove double slashes if present in the URL
set req.url = regsuball(req.url, "//+", "/");
return (synth(750));
}
// Perform a 307 (Temporary Redirect) redirect to X-Next-Locale (cookie locale) if:
// X-Next-Locale is within existing locales and
// X-Request-Locale is within existing locales or is missing and
// X-Request-Locale (url locale) does not match X-Next-Locale (cookie locale)
if (
(req.http.X-Next-Locale ~ "^(uk|ru)$") &&
(req.http.X-Request-Locale ~ "^(uk|ru)$" || !req.http.X-Request-Locale) &&
(req.http.X-Next-Locale != req.http.X-Request-Locale)
) {
set req.http.X-Status-Code = 307;
if (req.url !~ "^/([a-z]{2})(/|$|\?)") {
// If the URL doesn't contain a locale, add the X-Next-Locale to it
set req.url = "/" + req.http.X-Next-Locale + req.url;
} else {
// If the URL already contains a locale, update it with X-Next-Locale
set req.url = regsub(req.url, "^/([a-z]{2})(/|$|\?)", "/" + req.http.X-Next-Locale + "\2");
}
// Remove double slashes if present in the URL
set req.url = regsuball(req.url, "//+", "/");
return (synth(750));
}
// Remove any cookies left
unset req.http.Cookie;
return (hash);
}
sub vcl_synth {
// Modify response status and location header for redirects
if (resp.status == 750) {
set resp.status = std.integer(req.http.X-Status-Code);
set resp.http.location = "https://" + req.http.Host + req.url;
set resp.reason = "Moved";
return (deliver);
}
}
PS 不要忘记绕过机器人用户代理的重定向。