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

ReactJS+PocketBase PWA实时连接无法恢复问题求助

问题

我用PocketBase做后端,前端通过Server-Sent Events(SSE)订阅后端实时更新。浏览器里一切正常,但把应用以PWA形式安装到手机后,最小化再重新打开会丢失SSE连接,页面/数据也不重新加载,连包含router.refresh()的初始useEffect块都没触发。

useVisibility.tsx

"use client";

import { useState, useEffect } from "react";

export function useVisibility() {
  const [isVisible, setVisible] = useState(true);

  useEffect(() => {
    const handleVisibilityChange = () => {
      setVisible(!document.hidden);
    };

    document.addEventListener("visibilitychange", handleVisibilityChange);
    return () => {
      document.removeEventListener("visibilitychange", handleVisibilityChange);
    };
  }, []);

  return isVisible;
}

AgendaLive.tsx

"use client";

import { Reservation } from "@/types";
import { useEffect } from "react";
import PocketBase from "pocketbase";
import { reservations } from "../tables";
import { useRouter } from "next/navigation";
import { useVisibility } from "@/hooks/useVisibility";

type Props = {
  token: string;
  date: string;
};

const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);

export function AgendaLive({ token, date }: Props) {
  const router = useRouter();
  const isVisible = useVisibility();

  useEffect(() => {
    if (!isVisible) return;
    router.refresh();
  }, [isVisible, router]);

  useEffect(() => {
    if (!isVisible) return;

    pb.authStore.save(token);
    pb.collection(reservations).subscribe<Reservation>("*", (event) => {
      if (event.record.date !== date) {
        return;
      }
      router.refresh();
    });

    return () => {
      pb.collection(reservations).unsubscribe("*");
    };
  }, [token, date, router, isVisible]);

  return null;
}
解决方案

1. 修复可见性状态的初始值与更新逻辑

当前useVisibility初始状态固定为true,但PWA从后台恢复时,document.hidden可能已经是false,不会触发状态变更,导致依赖isVisible的useEffect不执行。修改后让初始状态直接匹配当前页面可见性:

"use client";

import { useState, useEffect } from "react";

export function useVisibility() {
  // 初始状态直接取当前页面可见性
  const [isVisible, setVisible] = useState(!document.hidden);

  useEffect(() => {
    const handleVisibilityChange = () => {
      setVisible(!document.hidden);
    };

    document.addEventListener("visibilitychange", handleVisibilityChange);
    return () => {
      document.removeEventListener("visibilitychange", handleVisibilityChange);
    };
  }, []);

  return isVisible;
}

2. 补充监听窗口焦点事件

部分场景下PWA恢复时visibilitychange事件触发延迟或不触发,新增window.focus监听确保页面激活时能触发更新:

"use client";

import { useState, useEffect } from "react";

export function useVisibility() {
  const [isVisible, setVisible] = useState(!document.hidden);

  useEffect(() => {
    const handleVisibilityChange = () => {
      setVisible(!document.hidden);
    };

    const handleFocus = () => {
      // 页面获得焦点时强制更新可见状态
      setVisible(true);
    };

    document.addEventListener("visibilitychange", handleVisibilityChange);
    window.addEventListener("focus", handleFocus);

    return () => {
      document.removeEventListener("visibilitychange", handleVisibilityChange);
      window.removeEventListener("focus", handleFocus);
    };
  }, []);

  return isVisible;
}

3. 页面恢复可见时重新建立SSE订阅

PWA后台运行时SSE连接可能被浏览器主动断开,需要在页面恢复可见时重新订阅,并先清理旧连接:

"use client";

import { Reservation } from "@/types";
import { useEffect } from "react";
import PocketBase from "pocketbase";
import { reservations } from "../tables";
import { useRouter } from "next/navigation";
import { useVisibility } from "@/hooks/useVisibility";

type Props = {
  token: string;
  date: string;
};

const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL);

export function AgendaLive({ token, date }: Props) {
  const router = useRouter();
  const isVisible = useVisibility();

  useEffect(() => {
    if (!isVisible) return;
    // 页面恢复可见时强制刷新数据
    router.refresh();
  }, [isVisible, router]);

  useEffect(() => {
    let unsubscribe: (() => void) | undefined;

    const setupSubscription = () => {
      pb.authStore.save(token);
      unsubscribe = pb.collection(reservations).subscribe<Reservation>("*", (event) => {
        if (event.record.date !== date) {
          return;
        }
        router.refresh();
      });
    };

    if (isVisible) {
      setupSubscription();
    }

    // 监听可见性变化,重新订阅
    const handleVisibilityChange = () => {
      if (!document.hidden) {
        // 先取消旧订阅(如果存在)
        unsubscribe?.();
        setupSubscription();
        router.refresh();
      }
    };

    document.addEventListener("visibilitychange", handleVisibilityChange);

    return () => {
      unsubscribe?.();
      document.removeEventListener("visibilitychange", handleVisibilityChange);
    };
  }, [token, date, router, isVisible]);

  return null;
}

4. 优化PWA的Service Worker缓存策略(可选)

如果用Next.js的PWA插件,确保SSE请求不被缓存拦截,在next.config.js中添加配置:

/** @type {import('next').NextConfig} */
const nextConfig = {
  // ...其他配置
  pwa: {
    dest: "public",
    runtimeCaching: [
      {
        urlPattern: /^https:\/\/your-pocketbase-url\/api\/realtime.*/,
        handler: "NetworkFirst", // 优先使用网络请求保证实时性
        options: {
          cacheName: "sse-realtime",
          expiration: {
            maxEntries: 1,
            maxAgeSeconds: 60,
          },
        },
      },
    ],
  },
};

module.exports = nextConfig;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 00:55:06