diff --git a/keyserver/src/updaters/thread-updaters.js b/keyserver/src/updaters/thread-updaters.js
--- a/keyserver/src/updaters/thread-updaters.js
+++ b/keyserver/src/updaters/thread-updaters.js
@@ -67,7 +67,7 @@
   verifyUserOrCookieIDs,
 } from '../fetchers/user-fetchers.js';
 import type { Viewer } from '../session/viewer.js';
-import { neynarClient, fcCache } from '../utils/fc-cache.js';
+import { fcCache } from '../utils/fc-cache.js';
 import { findUserIdentities } from '../utils/identity-utils.js';
 import { redisCache } from '../utils/redis-cache.js';
 import RelationshipChangeset from '../utils/relationship-changeset.js';
@@ -990,7 +990,7 @@
   ignorePromiseRejections(
     (async () => {
       const followedChannels =
-        await neynarClient?.fetchFollowedFarcasterChannels(farcasterID);
+        await fcCache?.getFollowedFarcasterChannelsForFID(farcasterID);
       if (followedChannels) {
         await Promise.allSettled(
           followedChannels.map(followedChannel =>
diff --git a/lib/components/base-auto-join-community-handler.react.js b/lib/components/base-auto-join-community-handler.react.js
--- a/lib/components/base-auto-join-community-handler.react.js
+++ b/lib/components/base-auto-join-community-handler.react.js
@@ -63,7 +63,7 @@
 
   const fid = useCurrentUserFID();
 
-  const neynarClient = React.useContext(NeynarClientContext)?.client;
+  const fcCache = React.useContext(NeynarClientContext)?.fcCache;
 
   const identityClientContext = React.useContext(IdentityClientContext);
   invariant(identityClientContext, 'IdentityClientContext should be set');
@@ -87,7 +87,7 @@
     }
 
     prevCanQueryRef.current = canQuery;
-    if (!canQuery || !isActive || !fid || !neynarClient) {
+    if (!canQuery || !isActive || !fid || !fcCache) {
       return;
     }
 
@@ -100,13 +100,17 @@
       })();
 
       const followedFarcasterChannelsPromise =
-        neynarClient.fetchFollowedFarcasterChannels(fid);
+        fcCache.getFollowedFarcasterChannelsForFID(fid);
 
       const [authMetadata, followedFarcasterChannels] = await Promise.all([
         authMetadataPromise,
         followedFarcasterChannelsPromise,
       ]);
 
+      if (!followedFarcasterChannels) {
+        return;
+      }
+
       const headers = authMetadata
         ? createDefaultHTTPRequestHeaders(authMetadata)
         : {};
@@ -178,7 +182,7 @@
     threadInfos,
     fid,
     isActive,
-    neynarClient,
+    fcCache,
     getAuthMetadata,
     keyserverInfos,
     canQuery,
diff --git a/lib/utils/fc-cache.js b/lib/utils/fc-cache.js
--- a/lib/utils/fc-cache.js
+++ b/lib/utils/fc-cache.js
@@ -25,6 +25,14 @@
   +farcasterChannel: ?NeynarChannel | Promise<?NeynarChannel>,
 };
 
+type FollowedFarcasterChannelsQueryCacheEntry = {
+  +fid: string,
+  +expirationTime: number,
+  +followedFarcasterChannels:
+    | ?$ReadOnlyArray<NeynarChannel>
+    | Promise<?$ReadOnlyArray<NeynarChannel>>,
+};
+
 class FCCache {
   client: NeynarClient;
 
@@ -36,6 +44,12 @@
   farcasterChannelQueryCache: Map<string, FarcasterChannelQueryCacheEntry> =
     new Map();
 
+  // Maps from FIDs to a cache entry for the Farcaster user's followed channels
+  followedFarcasterChannelsQueryCache: Map<
+    string,
+    FollowedFarcasterChannelsQueryCacheEntry,
+  > = new Map();
+
   constructor(client: NeynarClient) {
     this.client = client;
   }
@@ -236,6 +250,83 @@
 
     return farcasterChannel;
   }
+
+  getFollowedFarcasterChannelsForFID(
+    fid: string,
+  ): Promise<?$ReadOnlyArray<NeynarChannel>> {
+    const cachedChannelEntry =
+      this.getCachedFollowedFarcasterChannelsEntryForFID(fid);
+
+    if (cachedChannelEntry) {
+      return Promise.resolve(cachedChannelEntry.followedFarcasterChannels);
+    }
+
+    const fetchFollowedFarcasterChannelsPromise = (async () => {
+      let followedFarcasterChannels;
+      try {
+        followedFarcasterChannels = await Promise.race([
+          this.client.fetchFollowedFarcasterChannels(fid),
+          throwOnTimeout(`followed channels for ${fid}`),
+        ]);
+      } catch (e) {
+        console.log(e);
+        return null;
+      }
+
+      this.followedFarcasterChannelsQueryCache.set(fid, {
+        fid,
+        expirationTime: Date.now() + cacheTimeout,
+        followedFarcasterChannels,
+      });
+
+      return followedFarcasterChannels;
+    })();
+
+    this.followedFarcasterChannelsQueryCache.set(fid, {
+      fid,
+      expirationTime: Date.now() + queryTimeout * 2,
+      followedFarcasterChannels: fetchFollowedFarcasterChannelsPromise,
+    });
+
+    return fetchFollowedFarcasterChannelsPromise;
+  }
+
+  getCachedFollowedFarcasterChannelsEntryForFID(
+    fid: string,
+  ): ?FollowedFarcasterChannelsQueryCacheEntry {
+    const cacheResult = this.followedFarcasterChannelsQueryCache.get(fid);
+    if (!cacheResult) {
+      return undefined;
+    }
+
+    const { expirationTime } = cacheResult;
+    if (expirationTime <= Date.now()) {
+      this.followedFarcasterChannelsQueryCache.delete(fid);
+      return undefined;
+    }
+
+    return cacheResult;
+  }
+
+  getCachedFollowedFarcasterChannelsForFID(
+    fid: string,
+  ): ?$ReadOnlyArray<NeynarChannel> {
+    const cacheResult = this.getCachedFollowedFarcasterChannelsEntryForFID(fid);
+    if (!cacheResult) {
+      return undefined;
+    }
+
+    const { followedFarcasterChannels } = cacheResult;
+    if (
+      typeof followedFarcasterChannels !== 'object' ||
+      followedFarcasterChannels instanceof Promise ||
+      !followedFarcasterChannels
+    ) {
+      return undefined;
+    }
+
+    return followedFarcasterChannels;
+  }
 }
 
 export { FCCache };
diff --git a/native/community-settings/tag-farcaster-channel/tag-channel-button.react.js b/native/community-settings/tag-farcaster-channel/tag-channel-button.react.js
--- a/native/community-settings/tag-farcaster-channel/tag-channel-button.react.js
+++ b/native/community-settings/tag-farcaster-channel/tag-channel-button.react.js
@@ -41,17 +41,22 @@
   const neynarClientContext = React.useContext(NeynarClientContext);
   invariant(neynarClientContext, 'NeynarClientContext is missing');
 
-  const { client } = neynarClientContext;
+  const { fcCache } = neynarClientContext;
 
   React.useEffect(() => {
     void (async () => {
-      const channels = await client.fetchFollowedFarcasterChannels(fid);
+      const channels = await fcCache.getFollowedFarcasterChannelsForFID(fid);
+      if (!channels) {
+        return;
+      }
 
-      const sortedChannels = channels.sort((a, b) => a.id.localeCompare(b.id));
+      const sortedChannels = [...channels].sort((a, b) =>
+        a.id.localeCompare(b.id),
+      );
 
       setChannelOptions(sortedChannels);
     })();
-  }, [client, fid]);
+  }, [fcCache, fid]);
 
   const activeTheme = useSelector(state => state.globalThemeInfo.activeTheme);
 
diff --git a/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.react.js b/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.react.js
--- a/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.react.js
+++ b/web/tag-farcaster-channel/create-farcaster-channel-tag-modal.react.js
@@ -33,7 +33,7 @@
   const neynarClientContext = React.useContext(NeynarClientContext);
   invariant(neynarClientContext, 'NeynarClientContext is missing');
 
-  const { client, fcCache } = neynarClientContext;
+  const { fcCache } = neynarClientContext;
 
   const [channelOptions, setChannelOptions] = React.useState<
     $ReadOnlyArray<DropdownOption>,
@@ -44,9 +44,12 @@
 
   React.useEffect(() => {
     void (async () => {
-      const channels = await client.fetchFollowedFarcasterChannels(fid);
+      const channels = await fcCache.getFollowedFarcasterChannelsForFID(fid);
+      if (!channels) {
+        return;
+      }
 
-      const sortedChannels = channels
+      const sortedChannels = [...channels]
         .sort((a, b) => a.id.localeCompare(b.id))
         .map(channel => ({
           id: channel.id,
@@ -57,7 +60,7 @@
 
       setChannelOptions(options);
     })();
-  }, [client, fid]);
+  }, [fcCache, fid]);
 
   const onChangeSelectedOption = React.useCallback((option: string) => {
     setError(null);