如何防止浏览器的前进按钮绕过Next.js中间件?
我正在用Next.js构建一个用于旅行推荐的多标准决策网站。
流程如下:
- 用户在主页输入房间码(
/)。 - 我校验房间码。
- 如果房间存在,用户将被重定向到
/room。 - 在
/room时,用户输入邮箱并通过一次性验证码(OTP)进行验证。
我的要求是,/room 只有在用户先成功输入一个有效房间码后才能访问。
尝试1:会话存储
我把房间码存储在sessionStorage,并在允许访问/room之前进行检查。
这基本上可行,但如果用户:
- 输入有效房间码,
- 被重定向到
/room, - 按下浏览器的后退按钮,
- 然后点击前进,
浏览器仍然返回到/room。此时,sessionStorage 已经为空,因此页面会加载出 roomCode = null。
尝试2:中间件+ Cookie
随后我切换到使用Next.js中间件。
- 输入有效房间码后,我创建了一个Cookie。
- 中间件在允许访问
/room之前会检查这个Cookie。 - 当用户返回到主页时,我删除该Cookie。
然而,如果用户先按后退再按前进,浏览器仍然显示/room,即使Cookie已被删除且中间件应该阻止访问。
我的问题
- 为什么浏览器的前进按钮似乎绕过了Next.js中间件?
- 是否有可能在重定向到主页后清除浏览器历史记录,使用户无法返回或前往受保护的页面?
"use client";
import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import HeroImage from "@/components/heroimage/heroimage";
import Login from "@/components/emaillogin/emaillogin";
import OTPConfirm from "@/components/otp/otp";
export default function RoomPage() {
const router = useRouter();
const [step, setStep] = useState<"email" | "otp">(
"email"
);
const [email, setEmail] = useState("");
const historyLocked = useRef(false);
const isDirty = email.trim() !== "";
const confirmLeave = () => {
if (!isDirty) return true;
return window.confirm(
"Your progress will be lost. Do you want to continue?"
);
};
useEffect(() => {
if (!isDirty || historyLocked.current) return;
window.history.pushState(
null,
"",
window.location.href
);
historyLocked.current = true;
}, [isDirty]);
useEffect(() => {
const handleBeforeUnload = (
e: BeforeUnloadEvent
) => {
if (!isDirty) return;
e.preventDefault();
e.returnValue = "";
};
window.addEventListener(
"beforeunload",
handleBeforeUnload
);
return () => {
window.removeEventListener(
"beforeunload",
handleBeforeUnload
);
};
}, [isDirty]);
useEffect(() => {
const handlePopState = () => {
if (!confirmLeave()) {
window.history.pushState(
null,
"",
window.location.href
);
return;
}
router.push("/");
};
window.addEventListener(
"popstate",
handlePopState
);
return () => {
window.removeEventListener(
"popstate",
handlePopState
);
};
}, [router, isDirty]);
const handleBack = () => {
if (step === "otp") {
setStep("email");
return;
}
if (!confirmLeave()) return;
router.push("/");
};
return (
<main className="relative min-h-screen">
<div
id="hero-section"
className="absolute inset-0 -z-10"
>
<HeroImage />
</div>
<div className="relative z-10 mx-auto flex min-h-screen w-[80%] items-center justify-center">
{step === "email" ? (
<Login
email={email}
setEmail={setEmail}
onSuccess={() => setStep("otp")}
onBack={handleBack}
/>
) : (
<OTPConfirm
email={email}
onBack={handleBack}
/>
)}
</div>
</main>
);
}
这是我的中间件
import { NextRequest, NextResponse } from "next/server";
import { jwtVerify } from "jose";
const getJwtSecretKey = () => {
const secret = process.env.JWT_SECRET_KEY;
if (!secret) {
throw new Error("JWT_SECRET_KEY is not set.");
}
return new TextEncoder().encode(secret);
};
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const method = request.method;
if (pathname === "/" && method === "GET") {
const response = NextResponse.next();
if (request.cookies.has("validateroom")) {
response.cookies.delete("validateroom");
}
return response;
}
if (pathname === "/room") {
const validateRoom = request.cookies.get("validateroom");
if (!validateRoom) {
return NextResponse.redirect(new URL("/", request.url));
}
return NextResponse.next();
}
const match = pathname.match(/^\/room\/([^/]+)\/.+$/);
if (match) {
const roomCodeFromUrl = match[1];
const token = request.cookies.get("room_session")?.value;
if (!token) {
return NextResponse.redirect(new URL("/", request.url));
}
try {
const { payload } = await jwtVerify(
token,
getJwtSecretKey()
);
if (payload.roomCode !== roomCodeFromUrl) {
return NextResponse.redirect(new URL("/", request.url));
}
} catch {
return NextResponse.redirect(new URL("/", request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ["/:path*"],
};
现在为了快速修复我使用了:
useEffect(() => {
router.refresh();
}, [router]);
在我的Hero区域。它已经能完成工作,前进按钮仍然可用(没有变灰),但如果我尝试前进,我会立刻被推回到主页。
解决方案
为什么浏览器的前进按钮似乎绕过了Next.js中间件?
我不确定你完整的客户端路由设置,但很可能在底层使用了 push/pop-state(不经过服务器的客户端路由),因此你的后端根本不会收到一个http请求。你可以用浏览器调试器或服务器日志自行验证。
是否有可能在重定向到主页后清除浏览器历史记录,使用户无法返回或前往受保护的页面?
我建议不要采用试图修改浏览器历史记录来实现某种“特殊业务场景”的方案。相反,你可以在用户进入时设置一个会话值 lastRoomCode。页面重新加载时,roomCode 的值可以用会话中的 lastRoomCode 初始化。然后如果你的业务逻辑/用户操作是通过它完成的,只需从会话中移除此值。
如何阻止浏览器前进按钮绕过Next.js中间件?
也为了回答你在标题中的问题,你之所以会看到这种行为,是因为 react-router使用历史API(上面提到的push/pop-state)来进行导航。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。