diff --git a/packages/client/next.config.js b/packages/client/next.config.js index ca4387d2..bb943022 100644 --- a/packages/client/next.config.js +++ b/packages/client/next.config.js @@ -3,6 +3,7 @@ const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const withPWA = require('next-pwa'); const { getConfig } = require('@think/config'); const config = getConfig(); +const pwaRuntimeCaching = require('./pwa-cache'); /** @type {import('next').NextConfig} */ const nextConfig = semi({ @@ -34,6 +35,7 @@ const nextConfig = semi({ disable: process.env.NODE_ENV !== 'production', dest: '.next', sw: 'service-worker.js', + runtimeCaching: pwaRuntimeCaching, }, }); diff --git a/packages/client/pwa-cache.js b/packages/client/pwa-cache.js new file mode 100644 index 00000000..a5117db1 --- /dev/null +++ b/packages/client/pwa-cache.js @@ -0,0 +1,143 @@ +module.exports = [ + { + urlPattern: /^https:\/\/fonts\.(?:gstatic)\.com\/.*/i, + handler: 'CacheFirst', + options: { + cacheName: 'google-fonts-webfonts', + expiration: { + maxEntries: 4, + maxAgeSeconds: 365 * 24 * 60 * 60, // 365 days + }, + }, + }, + { + urlPattern: /^https:\/\/fonts\.(?:googleapis)\.com\/.*/i, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'google-fonts-stylesheets', + expiration: { + maxEntries: 4, + maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days + }, + }, + }, + { + urlPattern: /\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'static-font-assets', + expiration: { + maxEntries: 4, + maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days + }, + }, + }, + { + urlPattern: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'static-image-assets', + expiration: { + maxEntries: 64, + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, + }, + { + urlPattern: /\/_next\/image\?url=.+$/i, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'next-image', + expiration: { + maxEntries: 64, + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, + }, + { + urlPattern: /\.(?:mp3|wav|ogg)$/i, + handler: 'CacheFirst', + options: { + rangeRequests: true, + cacheName: 'static-audio-assets', + expiration: { + maxEntries: 32, + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, + }, + { + urlPattern: /\.(?:mp4)$/i, + handler: 'CacheFirst', + options: { + rangeRequests: true, + cacheName: 'static-video-assets', + expiration: { + maxEntries: 32, + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, + }, + { + urlPattern: /\.(?:js)$/i, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'static-js-assets', + expiration: { + maxEntries: 32, + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, + }, + { + urlPattern: /\.(?:css|less)$/i, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'static-style-assets', + expiration: { + maxEntries: 32, + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, + }, + { + urlPattern: /\/_next\/data\/.+\/.+\.json$/i, + handler: 'StaleWhileRevalidate', + options: { + cacheName: 'next-data', + expiration: { + maxEntries: 32, + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, + }, + { + urlPattern: /\.(?:json|xml|csv)$/i, + handler: 'NetworkFirst', + options: { + cacheName: 'static-data-assets', + expiration: { + maxEntries: 32, + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + }, + }, + { + urlPattern: ({ url }) => { + const isSameOrigin = self.origin === url.origin; + if (!isSameOrigin) return false; + const pathname = url.pathname; + if (pathname.startsWith('/api/')) return false; + return true; + }, + handler: 'NetworkFirst', + options: { + cacheName: 'others', + expiration: { + maxEntries: 32, + maxAgeSeconds: 24 * 60 * 60, // 24 hours + }, + networkTimeoutSeconds: 10, + }, + }, +]; diff --git a/packages/client/src/components/document/collaboration/index.tsx b/packages/client/src/components/document/collaboration/index.tsx index 7ab8951a..27a04926 100644 --- a/packages/client/src/components/document/collaboration/index.tsx +++ b/packages/client/src/components/document/collaboration/index.tsx @@ -51,7 +51,9 @@ export const DocumentCollaboration: React.FC = ({ wikiId, documentId, di const toastedUsersRef = useRef>([]); const { user: currentUser } = useUser(); const [visible, toggleVisible] = useToggle(false); - const { users, loading, error, addUser, updateUser, deleteUser } = useDoumentMembers(documentId); + const { users, loading, error, addUser, updateUser, deleteUser } = useDoumentMembers(documentId, { + enabled: visible, + }); const [inviteUser, setInviteUser] = useState(''); const [collaborationUsers, setCollaborationUsers] = useState([]);