SvelteKit通用渲染骨架加载问题求助
解决SvelteKit首屏SSR+客户端流畅导航(组件级加载)的问题
核心思路
要实现首屏SSR渲染完整内容,后续客户端导航时立即切换页面并显示组件级加载状态,关键在于分离服务端预取数据与客户端导航的数据加载逻辑:
- 服务端(首屏请求):在
load函数中同步获取数据,确保SSR输出完整内容 - 客户端(页面导航):跳过
load函数的数据等待,由组件在挂载后自行获取数据,实现页面无延迟切换
解决方案代码实现
1. 页面数据加载逻辑(+page.ts)
以首页为例,load函数仅在服务端执行时获取数据,客户端导航时返回空状态:
import { browser } from '$app/environment'; type Todo = { id: number; todo: string; completed: boolean; userId: number; }; export async function load({ fetch }) { // 服务端SSR时预取数据,确保首屏有内容 if (!browser) { const res = await fetch('https://dummyjson.com/todos'); const data = await res.json(); return { todos: data.todos as Todo[] }; } // 客户端导航时返回空,让组件自行加载 return { todos: null }; }
产品页的+page.ts逻辑完全一致,仅需替换接口和类型:
import { browser } from '$app/environment'; type Product = { id: number; title: string; }; export async function load({ fetch }) { if (!browser) { const res = await fetch('https://dummyjson.com/products'); const data = await res.json(); return { products: data.products as Product[] }; } return { products: null }; }
2. 页面组件(+page.svelte)
组件中区分首屏SSR数据和客户端导航状态,挂载后获取数据并显示加载状态:
<script lang="ts"> import { browser } from '$app/environment'; import { onMount } from 'svelte'; let { data } = $props(); let todos = data.todos; // 客户端导航时,组件挂载后触发数据获取 onMount(async () => { if (browser && !todos) { try { const res = await fetch('https://dummyjson.com/todos'); const data = await res.json(); todos = data.todos; } catch (err) { console.error('Failed to load todos:', err); // 这里可以添加错误提示逻辑 } } }); </script> <h1>TODOS</h1> <div class="content"> {#if todos} {#each todos as todo} <div class="todo-item">{todo.todo}</div> {/each} {:else} <div class="loading">Loading todos...</div> {/if} </div> <style> .loading { padding: 2rem; color: #666; font-style: italic; } .todo-item { padding: 0.6rem; border-bottom: 1px solid #eee; } </style>
产品页组件同理,替换todos为products即可。
为什么能解决水合不匹配问题?
- 首屏SSR时,服务端返回已加载的
todos数据,组件渲染完整内容,客户端水合时初始状态与服务端DOM完全一致,无冲突 - 客户端导航时,进入的是全新页面,初始状态为
null,显示加载状态,不存在水合匹配问题(水合仅发生在首屏)
进阶优化建议
- 封装API请求:把重复的fetch逻辑抽离到
src/lib/api.ts,比如:
组件和// src/lib/api.ts export async function fetchTodos() { const res = await fetch('https://dummyjson.com/todos'); if (!res.ok) throw new Error('Failed to fetch todos'); const data = await res.json(); return data.todos; }load函数直接调用该函数,减少重复代码 - 预取优化:在链接上添加鼠标悬停预取,提前加载数据:
让用户点击时数据已加载完成,进一步提升流畅度<a href="/products" on:mouseenter={async () => await fetchProducts()}>Products</a> - 错误状态处理:在组件中添加错误捕获,显示友好的错误提示,避免用户看到空白页面
- 加载状态美化:替换文字加载提示为Spinner动画,提升视觉体验
内容的提问来源于stack exchange,提问作者frezzzen
相关产品推荐
相关产品推荐

