如何用goto实现登录页首屏展示及Supabase身份验证拦截
未登录限制访问首页的两种实现方案(Supabase + SvelteKit)
你的问题核心是没有对首页添加登录验证逻辑,所以未登录用户可以直接访问。下面是两种可行的实现方案:
方案一:服务端全局拦截(推荐,更安全)
通过hooks.server.js在服务端统一验证用户登录状态,未登录时直接重定向到登录页,避免客户端验证被绕过。
步骤:
- 在
src目录下创建hooks.server.js文件 - 编写如下验证逻辑:
// src/hooks.server.js import { redirect } from '@sveltejs/kit'; import supabase from '$routes/supa'; // 对应你的supabase实例路径 export async function handle({ event, resolve }) { // 获取当前用户会话 const { data: { session } } = await supabase.auth.getSession(); // 定义无需登录即可访问的页面 const publicPaths = ['/login', '/signup']; // 未登录且访问非公开页面时,重定向到登录页 if (!session && !publicPaths.includes(event.url.pathname)) { throw redirect(303, '/login'); } // 已登录则正常处理请求 const response = await resolve(event); return response; }
注意事项:
- 确保
supabase实例的导入路径正确,和你登录/注册页面中的路径一致 - 303状态码用于规范重定向逻辑,避免POST请求的重复提交问题
- 你的登录/注册页面已经通过
goto("/")跳转首页,登录成功后会自动进入首页
方案二:客户端弹窗限制(适合展示首页内容但禁止操作)
如果希望用户能看到首页内容但无法操作,可在首页添加登录弹窗,未登录时覆盖页面并禁用交互元素。
修改后的首页代码:
// src/routes/+page.svelte <script> import { onMount } from 'svelte'; import supabase from '../supa'; import { goto } from '$app/navigation'; let totalStudyTime = 0; let goalHours = 0; let goalMinutes = 0; let completed = false; let userSession = null; let loginEmail = ''; let loginPassword = ''; // 检查登录状态 async function checkAuth() { const { data: { session } } = await supabase.auth.getSession(); userSession = session; userSession && loadLocalData(); } // 加载本地存储的学习数据 function loadLocalData() { goalHours = parseInt(localStorage.getItem('goalHours') || '0', 10); goalMinutes = parseInt(localStorage.getItem('goalMinutes') || '0', 10); const studyTime = parseInt(localStorage.getItem('totalStudyTime') || '0', 10) + parseInt(localStorage.getItem('elapsedTime') || '0', 10); totalStudyTime = studyTime; setGoal(); } function calculateCompletion(time, goal) { const totalTimeMinutes = time / 60; completed = totalTimeMinutes >= goal; } function setGoal() { if (!userSession) return; const totalGoalMinutes = goalHours * 60 + goalMinutes; localStorage.setItem('dailyStudyGoal', totalGoalMinutes); localStorage.setItem('goalHours', goalHours); localStorage.setItem('goalMinutes', goalMinutes); totalStudyTime = parseInt(localStorage.getItem('totalStudyTime') || '0', 10) + parseInt(localStorage.getItem('elapsedTime') || '0', 10); calculateCompletion(totalStudyTime, totalGoalMinutes); } function formatStudyTime(seconds) { const days = Math.floor(seconds / (3600 * 24)); const hours = Math.floor((seconds % (3600 * 24)) / 3600); const minutes = Math.floor((seconds % 3600) / 60); const remainingSeconds = seconds % 60; const formattedDays = days > 0 ? `${days} day${days > 1 ? 's' : ''}` : ''; const formattedHours = hours > 0 ? `${hours} hour${hours > 1 ? 's' : ''}` : ''; const formattedMinutes = minutes > 0 ? `${minutes} minute${minutes > 1 ? 's' : ''}` : ''; const formattedSeconds = remainingSeconds > 0 ? `${remainingSeconds} second${remainingSeconds > 1 ? 's' : ''}` : ''; const timeArray = [formattedDays, formattedHours, formattedMinutes, formattedSeconds].filter(Boolean); return timeArray.join(', '); } // 登录操作 async function handleLogin() { const { error } = await supabase.auth.signIn({ email: loginEmail, password: loginPassword }); if (!error) { userSession = (await supabase.auth.getSession()).data.session; loadLocalData(); } else { alert('登录失败:' + error.message); } } onMount(() => { checkAuth(); // 监听登录状态变化 supabase.auth.onAuthStateChange((_event, session) => { userSession = session; session && loadLocalData(); }); }); </script> <!-- 未登录时显示登录弹窗遮罩 --> {#if !userSession} <div class="auth-overlay"> <div class="login-modal"> <h2>请先登录</h2> <input type="email" bind:value={loginEmail} placeholder="邮箱" /> <input type="password" bind:value={loginPassword} placeholder="密码" /> <button on:click={handleLogin}>登录</button> <p>还没有账号?<button on:click={() => goto('/signup')} class="link-btn">立即注册</button></p> </div> </div> {/if} <main> <h1><b>Welcome to studyclock!</b></h1> <h3> Start a study session! </h3> <p>Set your daily goal! <b> {goalHours} hr {goalMinutes} min </b></p> <label for="hoursInput">Hours</label> <input type="number" id="hoursInput" bind:value={goalHours} min="0" disabled={!userSession}> <label for="minutesInput">Minutes</label> <input type="number" id="minutesInput" bind:value={goalMinutes} min="0" max="59" disabled={!userSession}> <button on:click={setGoal} disabled={!userSession}>Set Goal</button> {#if completed} <p>Great job! You completed your daily study goal! 🥳</p> {/if} <p> Total study time: <b>{formatStudyTime(totalStudyTime)}</b> </p> </main> <style> main { text-align: center; } label, input, button { display: block; margin: 8px auto; text-align: center; width: fit-content; } input { margin-bottom: 16px; } button { margin-top: 16px; } /* 登录遮罩样式 */ .auth-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); display: flex; justify-content: center; align-items: center; z-index: 1000; } .login-modal { background: white; padding: 30px; border-radius: 15px; box-shadow: 0 0 10px rgba(0,0,0,0.3); width: 300px; } .login-modal input { width: 100%; padding: 8px; margin: 10px 0; box-sizing: border-box; } .link-btn { background: none; border: none; color: blue; cursor: pointer; text-decoration: underline; } </style>
方案特点:
- 未登录时用户可查看首页内容,但所有交互元素(输入框、按钮)均被禁用
- 弹窗遮罩强制用户登录,登录后自动加载数据并启用交互
- 监听Supabase的登录状态变化,实时更新页面状态
内容的提问来源于stack exchange,提问作者Claryssa
相关产品推荐
相关产品推荐

