SvelteKit认证流程报错:buffer兼容及TypeError问题求助
问题:SvelteKit服务端load函数使用jsonwebtoken导致浏览器500错误
我在Svelte/SvelteKit中搭建认证流程,服务端日志显示JWT验证、解码均成功,但浏览器端出现500错误,报错信息包括:
- Module "buffer"已针对浏览器兼容性外部化
- TypeError: Cannot read properties of undefined (reading 'from')
return (<Root>创建时缺少预期的form属性 )
我原本以为+page.js是服务端组件,不会在客户端加载jsonwebtoken模块,不清楚错误原因,求解决方法。
我的认证流程代码:
- 根路由登录页(/)代码:
<script> let statusMessage = ""; function changeStatusMessage(message){ statusMessage = message; }; async function handleSubmit(param){ param.preventDefault(); let nicknameInput = document.querySelector("#nicknameInput"); let passwordInput = document.querySelector("#passwordInput"); console.log("用户名输入值: ", nicknameInput.value); console.log("密码输入值: ", passwordInput.value); let request = await fetch("/auth/logining", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ nickname: nicknameInput.value, password: passwordInput.value }) }); let response = await request.json(); console.log(response); if(!response.status){ changeStatusMessage(response.message); } else { location.assign(`/panel?token=${response.body.token}`) }; } </script> <h2 class="login-page-title">欢迎来到Necdetiye预会计系统!</h2> <main class="login-page"> <form on:submit={handleSubmit}> <label for="nicknameInput">用户名:</label> <input type="text" name="nickname" id="nicknameInput" placeholder="您的用户名"> <label for="passwordInput">密码:</label> <input type="password" name="password" id="passwordInput" placeholder="您的密码"> <input type="submit" value="登录"> <p>{statusMessage}</p> </form> </main>
/auth/logining接口代码:
import { redirect, json } from "@sveltejs/kit"; import { fetchDatasWithFiltering } from "../../../lib/firebase"; import { decrypt4 } from "necdetiye-crypto"; import { sign } from "jsonwebtoken"; export async function POST(context){ console.log("上下文对象: ", context); let body = await context.request.json(); console.log("请求体: ", body); let getUserObject = await fetchDatasWithFiltering("users", "nickname", "==", body.nickname); console.log("用户对象: ", getUserObject); if(!getUserObject || getUserObject.length === 0 || getUserObject[0].nickname !== body.nickname){ return json({ status: false, message: "错误:不存在该用户名的用户。" }); }; if(decrypt4(getUserObject[0].password, "asdfsdasfasdsfd") !== body.password){ return json({ status: false, message: "错误:用户密码错误。" }); }; let userObj = { nickname: getUserObject[0].nickname, email: getUserObject[0].email, role: getUserObject[0].role } let jwtToken = sign({ "nickname": userObj.nickname, "email": userObj.email, "role": userObj.role }, "kfnlksdfhvnkjhsdfkgjs", { expiresIn: "100 days" }); return new Response(JSON.stringify({ status: 200, headers: { "Content-Type": "application/json" }, body: { message: "认证成功", token: jwtToken } })); };
/panel/+page.js服务端load函数代码:
import { redirect } from '@sveltejs/kit'; import { decode, verify } from 'jsonwebtoken'; /** @type {import('./$types').PageLoad} */ export function load({url, request, cookies}) { console.log("URL对象: ", url); console.log("Token参数: ", url.searchParams.get("token")) let ourToken = url.searchParams.get("token"); if(!ourToken){ throw redirect(303, "/?error=unauthorized"); }; let verifyToken = verify(ourToken, "kfnlksdfhvnkjhsdfkgjs"); if(!verifyToken){ throw redirect(303, "/?error=malformedtoken"); }; let user = decode(ourToken); console.log("/panel服务端用户对象: ", user); return { props: { data: user } } }
/panel/+layout.svelte代码:
<script> export let data; console.log("+layout.svelte数据: ", data); </script> <slot {data}></slot>
/panel/+page.svelte代码:
<script> import { setContext, hasContext, getContext } from 'svelte'; export let data; console.log("+page.svelte数据对象: ", data); if(!hasContext("user")){ setContext("user", data.props.data); } console.log("上下文用户数据: ", getContext("user")); </script>
解决方法
1. 明确load函数的运行时机
SvelteKit的+page.js中load函数默认是universal函数,会同时在服务端和客户端运行(比如客户端路由跳转时会重新执行)。jsonwebtoken依赖Node.js核心模块(如buffer),浏览器环境不支持,因此触发报错。
2. 将load函数改为仅服务端运行
有两种方式确保load只在服务端执行:
方法A:添加服务端渲染标记
在/panel/+page.js中添加export const ssr = true;,强制页面仅服务端渲染,load函数不会在客户端执行:
import { redirect } from '@sveltejs/kit'; import { decode, verify } from 'jsonwebtoken'; export const ssr = true; /** @type {import('./$types').PageLoad} */ export function load({url}) { let ourToken = url.searchParams.get("token"); if(!ourToken){ throw redirect(303, "/?error=unauthorized"); }; let verifyToken; try { verifyToken = verify(ourToken, "kfnlksdfhvnkjhsdfkgjs"); } catch (err) { throw redirect(303, "/?error=invalidtoken"); } let user = decode(ourToken); return { user } }
方法B:使用server-only包强制服务端加载
安装依赖:
npm install server-only
在/panel/+page.js顶部导入该包,客户端加载时会直接报错,确保代码仅在服务端运行:
import 'server-only'; import { redirect } from '@sveltejs/kit'; import { decode, verify } from 'jsonwebtoken'; // 其余代码不变
3. 修正数据传递结构
原代码中load返回{ props: { data: user } },但SvelteKit中load返回的对象会直接作为页面组件的data属性,不需要嵌套props。修改返回值:
return { user: user }
对应修改+page.svelte中的数据读取:
<script> import { setContext, hasContext, getContext } from 'svelte'; export let data; if(!hasContext("user")){ setContext("user", data.user); } </script>
4. 捕获JWT验证错误
jsonwebtoken的verify函数验证失败时会抛出错误,而非返回false,需用try/catch包裹,避免服务端未捕获异常引发客户端500错误:
let verifyToken; try { verifyToken = verify(ourToken, "kfnlksdfhvnkjhsdfkgjs"); } catch (err) { throw redirect(303, "/?error=invalidtoken"); }
5. 优化登录跳转逻辑
使用SvelteKit内置的goto函数替代location.assign,保证客户端路由跳转的一致性:
<script> import { goto } from '$app/navigation'; let statusMessage = ""; async function handleSubmit(param){ param.preventDefault(); // 其余代码不变 if(!response.status){ changeStatusMessage(response.message); } else { await goto(`/panel?token=${response.body.token}`); }; } </script>
内容的提问来源于stack exchange,提问作者Necoo33
相关产品推荐
相关产品推荐

