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

如何在Sapper应用中为每个路由仅请求一次GraphQL数据

解决Sapper页面GraphQL数据重复请求的方案

这个需求很合理——既要保证每个页面只获取自己需要的数据,又不想用户来回跳转时重复发起冗余请求。结合Sapper的preload特性和Svelte store,我们可以用一个页面数据缓存store来实现这个目标,具体步骤如下:

1. 创建页面数据缓存Store

首先我们需要一个专门的store来缓存各个页面的数据,它要能判断数据是否存在、读取缓存、写入缓存。创建stores/pageDataCache.js文件:

import { writable } from 'svelte/store';

function createPageDataCache() {
  const { subscribe, set, update } = writable({});

  return {
    subscribe,
    // 同步获取指定页面的缓存数据
    get: (pageKey) => {
      let currentCache;
      // 用立即执行的subscribe同步拿到当前store值
      subscribe(cache => currentCache = cache)();
      const entry = currentCache[pageKey];
      // 可选:加过期判断,这里默认1小时过期
      if (entry && entry.expires > Date.now()) {
        return entry.data;
      }
      // 过期就删除无效缓存
      if (entry) {
        update(cache => {
          delete cache[pageKey];
          return cache;
        });
      }
      return null;
    },
    // 写入缓存,支持设置过期时间(毫秒)
    set: (pageKey, data, ttl = 3600000) => {
      update(cache => ({
        ...cache,
        [pageKey]: {
          data,
          expires: Date.now() + ttl
        }
      }));
    },
    // 判断指定页面是否有有效缓存
    has: (pageKey) => {
      return !!this.get(pageKey);
    }
  };
}

export const pageDataCache = createPageDataCache();

2. 在路由preload中使用缓存

Sapper的preload函数会在页面加载前执行,而且在服务端和客户端都会运行。我们可以利用这一点,在客户端优先读取缓存,没有缓存时再请求API;服务端则直接请求(保证SSR能拿到最新数据,服务端的store是每个请求独立的,不会出现缓存共享问题)。

以routes/about.svelte为例:

<script context="module">
  import { pageDataCache } from '../stores/pageDataCache';
  // 假设这是你封装的GraphQL请求函数
  import { fetchAboutPageData } from '../lib/graphql-client';

  export async function preload(page) {
    // 用页面完整路径作为缓存key,比如'/about'或者'/blog/my-post'(动态路由也适用)
    const pageKey = page.path;

    // 客户端环境下先检查缓存
    if (typeof window !== 'undefined') {
      const cachedData = pageDataCache.get(pageKey);
      if (cachedData) {
        return { pageData: cachedData };
      }
    }

    // 没有缓存则发起API请求
    const freshData = await fetchAboutPageData();
    // 将新数据存入缓存
    pageDataCache.set(pageKey, freshData);

    return { pageData: freshData };
  }
</script>

<script>
  // 接收preload传递的数据
  export let pageData;
</script>

<!-- 页面内容,直接使用pageData即可 -->
<h1>{pageData.title}</h1>
<div>{@html pageData.content}</div>

3. 动态路由的适配

如果是带参数的动态路由(比如/blog/[slug].svelte),直接用page.path作为缓存key就可以,因为它已经包含了参数(比如/blog/hello-world),不同参数的页面会自动对应不同的缓存条目,完全不需要额外处理。

关键注意点

  • 服务端缓存逻辑:服务端的store是每个请求单独实例化的,所以服务端不会出现缓存复用的情况,每个请求都会重新获取最新数据,这符合SSR的正确性要求。
  • 缓存过期机制:上面的store加入了默认1小时的过期时间,你可以根据业务需求调整TTL(比如设置为5分钟或者一天),避免用户看到太旧的数据。
  • 缓存清理:如果需要主动清理某个页面的缓存(比如后台更新了页面内容),可以调用pageDataCache.set(pageKey, null)来删除对应条目,下次访问时就会重新请求。

这样就能完美实现你的需求:每个页面只请求一次数据,用户重新访问时客户端复用缓存,服务端保证数据新鲜,同时不会出现冗余请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 18:12:28