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

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,显示加载状态,不存在水合匹配问题(水合仅发生在首屏)

进阶优化建议

  1. 封装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函数直接调用该函数,减少重复代码
  2. 预取优化:在链接上添加鼠标悬停预取,提前加载数据:
    <a href="/products" on:mouseenter={async () => await fetchProducts()}>Products</a>
    
    让用户点击时数据已加载完成,进一步提升流畅度
  3. 错误状态处理:在组件中添加错误捕获,显示友好的错误提示,避免用户看到空白页面
  4. 加载状态美化:替换文字加载提示为Spinner动画,提升视觉体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 00:20:56