You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Svelte 4、SvelteKit结合本地存储实现认证前阻止路由渲染

SvelteKit客户端本地存储认证路由保护方案

针对你遇到的受保护路由在重定向前提前渲染的问题,核心解决思路是在路由渲染流程的最早期拦截未认证请求,阻止页面内容输出。以下是具体实现方案:

1. 全局认证状态管理

创建一个Svelte Store同步本地存储的用户信息,统一管理登录/登出状态:

// src/lib/stores/auth.js
import { writable } from 'svelte/store';

function createAuthStore() {
    // 仅在客户端环境读取localStorage
    const storedUser = typeof window !== 'undefined' 
        ? JSON.parse(localStorage.getItem('user') || 'null') 
        : null;

    const { subscribe, set } = writable(storedUser);

    return {
        subscribe,
        // 登录:保存用户信息到localStorage和Store
        login: (userData) => {
            localStorage.setItem('user', JSON.stringify(userData));
            set(userData);
        },
        // 登出:清除本地存储和Store状态
        logout: () => {
            localStorage.removeItem('user');
            set(null);
        }
    };
}

export const authStore = createAuthStore();

2. 根布局级路由拦截(核心)

利用SvelteKit的根布局+layout.js的客户端Load函数,在所有页面渲染前检查认证状态,直接拦截未认证请求:

// src/routes/+layout.js
import { redirect } from '@sveltejs/kit';

export async function load({ url }) {
    // 仅在客户端执行(服务端无localStorage)
    if (typeof window === 'undefined') return;

    const user = localStorage.getItem('user');
    // 定义无需认证即可访问的公开路由
    const publicRoutes = ['/login', '/register'];

    // 未登录且访问受保护路由:重定向到登录页,携带原路由参数
    if (!user && !publicRoutes.includes(url.pathname)) {
        throw redirect(307, `/login?redirectTo=${encodeURIComponent(url.pathname)}`);
    }

    // 已登录却访问登录页:重定向到主页或原目标路由
    if (user && url.pathname === '/login') {
        const redirectTo = url.searchParams.get('redirectTo') || '/';
        throw redirect(307, redirectTo);
    }
}

3. 登录页面实现

完成登录逻辑,登录成功后跳转到用户原本要访问的页面:

<!-- src/routes/login/+page.svelte -->
<script>
    import { authStore } from '$lib/stores/auth';
    import { page } from '$app/stores';
    import { goto } from '$app/navigation';

    let email = '';
    let password = '';

    async function handleLogin() {
        // 替换为你的实际认证逻辑(比如调用后端API验证)
        if (!email || !password) {
            alert('请输入邮箱和密码');
            return;
        }

        // 模拟用户信息
        const user = { email, id: '123' };
        authStore.login(user);

        // 跳转到原目标路由,默认主页
        const redirectTo = $page.url.searchParams.get('redirectTo') || '/';
        await goto(redirectTo);
    }
</script>

<div class="login-container">
    <h2>登录</h2>
    <input 
        type="email" 
        bind:value={email} 
        placeholder="邮箱"
        required
    />
    <input 
        type="password" 
        bind:value={password} 
        placeholder="密码"
        required
    />
    <button on:click={handleLogin}>登录</button>
</div>

<style>
    .login-container {
        max-width: 300px;
        margin: 2rem auto;
        padding: 1rem;
        border: 1px solid #eee;
        border-radius: 8px;
    }
    input {
        display: block;
        width: 100%;
        margin: 0.5rem 0;
        padding: 0.5rem;
        box-sizing: border-box;
    }
    button {
        width: 100%;
        padding: 0.5rem;
        background: #0070f3;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
    }
</style>

4. 优化:避免服务端渲染时的内容泄漏

由于服务端无法访问localStorage,第一次加载受保护路由时,服务端会先渲染页面内容,客户端验证后才重定向。可通过根布局添加加载状态解决:

<!-- src/routes/+layout.svelte -->
<script>
    import { onMount } from 'svelte';
    import { authStore } from '$lib/stores/auth';
    import { page } from '$app/stores';
    import { goto } from '$app/navigation';

    let isAuthChecked = false;

    onMount(async () => {
        const user = localStorage.getItem('user');
        const publicRoutes = ['/login', '/register'];

        if (!user && !publicRoutes.includes($page.url.pathname)) {
            await goto(`/login?redirectTo=${encodeURIComponent($page.url.pathname)}`);
        } else if (user && $page.url.pathname === '/login') {
            const redirectTo = $page.url.searchParams.get('redirectTo') || '/';
            await goto(redirectTo);
        }
        isAuthChecked = true;
    });
</script>

{#if !isAuthChecked}
    <div style="text-align: center; margin-top: 2rem;">加载中...</div>
{:else}
    <slot />
{/if}

关键注意事项

  • 根布局的Load函数会在所有子路由之前执行,是拦截未认证请求的最佳时机,能彻底避免受保护路由渲染。
  • 始终在客户端环境下访问localStorage,避免服务端渲染时出现错误。
  • 携带redirectTo参数提升用户体验,让用户登录后直接回到原本要访问的页面。

内容的提问来源于stack exchange,提问作者Maciel Eliezer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 20:58:22