diff --git a/src/App.tsx b/src/App.tsx index 2928354..3ac4d93 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,29 +1,31 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, lazy, Suspense } from "react"; import { openUrl } from "@tauri-apps/plugin-opener"; import { Sidebar } from "./components/sidebar/Sidebar"; import { Feed } from "./components/feed/Feed"; -import { SearchView } from "./components/search/SearchView"; -import { RelaysView } from "./components/shared/RelaysView"; -import { SettingsView } from "./components/shared/SettingsView"; -import { ProfileView } from "./components/profile/ProfileView"; -import { ThreadView } from "./components/thread/ThreadView"; -import { ArticleEditor } from "./components/article/ArticleEditor"; -import { ArticleView } from "./components/article/ArticleView"; -import { ArticleFeed } from "./components/article/ArticleFeed"; -import { MediaFeed } from "./components/media/MediaFeed"; import { OnboardingFlow } from "./components/onboarding/OnboardingFlow"; -import { AboutView } from "./components/shared/AboutView"; -import { ZapHistoryView } from "./components/zap/ZapHistoryView"; -import { DMView } from "./components/dm/DMView"; -import { NotificationsView } from "./components/notifications/NotificationsView"; -import { BookmarkView } from "./components/bookmark/BookmarkView"; -import { HashtagFeed } from "./components/feed/HashtagFeed"; -import { PodcastsView } from "./components/podcast/PodcastsView"; -import { FollowsView } from "./components/follows/FollowsView"; import { PodcastPlayerBar } from "./components/podcast/PodcastPlayerBar"; import { ToastContainer } from "./components/shared/ToastContainer"; -import { DebugPanel } from "./components/shared/DebugPanel"; -import { HelpModal } from "./components/shared/HelpModal"; + +// Lazy-loaded views — only fetched when navigated to +const SearchView = lazy(() => import("./components/search/SearchView").then(m => ({ default: m.SearchView }))); +const RelaysView = lazy(() => import("./components/shared/RelaysView").then(m => ({ default: m.RelaysView }))); +const SettingsView = lazy(() => import("./components/shared/SettingsView").then(m => ({ default: m.SettingsView }))); +const ProfileView = lazy(() => import("./components/profile/ProfileView").then(m => ({ default: m.ProfileView }))); +const ThreadView = lazy(() => import("./components/thread/ThreadView").then(m => ({ default: m.ThreadView }))); +const ArticleEditor = lazy(() => import("./components/article/ArticleEditor").then(m => ({ default: m.ArticleEditor }))); +const ArticleView = lazy(() => import("./components/article/ArticleView").then(m => ({ default: m.ArticleView }))); +const ArticleFeed = lazy(() => import("./components/article/ArticleFeed").then(m => ({ default: m.ArticleFeed }))); +const MediaFeed = lazy(() => import("./components/media/MediaFeed").then(m => ({ default: m.MediaFeed }))); +const AboutView = lazy(() => import("./components/shared/AboutView").then(m => ({ default: m.AboutView }))); +const ZapHistoryView = lazy(() => import("./components/zap/ZapHistoryView").then(m => ({ default: m.ZapHistoryView }))); +const DMView = lazy(() => import("./components/dm/DMView").then(m => ({ default: m.DMView }))); +const NotificationsView = lazy(() => import("./components/notifications/NotificationsView").then(m => ({ default: m.NotificationsView }))); +const BookmarkView = lazy(() => import("./components/bookmark/BookmarkView").then(m => ({ default: m.BookmarkView }))); +const HashtagFeed = lazy(() => import("./components/feed/HashtagFeed").then(m => ({ default: m.HashtagFeed }))); +const PodcastsView = lazy(() => import("./components/podcast/PodcastsView").then(m => ({ default: m.PodcastsView }))); +const FollowsView = lazy(() => import("./components/follows/FollowsView").then(m => ({ default: m.FollowsView }))); +const DebugPanel = lazy(() => import("./components/shared/DebugPanel").then(m => ({ default: m.DebugPanel }))); +const HelpModal = lazy(() => import("./components/shared/HelpModal").then(m => ({ default: m.HelpModal }))); import { useUIStore } from "./stores/ui"; import { getTheme, applyTheme } from "./lib/themes"; import { useUpdater } from "./hooks/useUpdater"; @@ -111,23 +113,25 @@ function App() {
- {currentView === "search" && } - {currentView === "relays" && } - {currentView === "settings" && } - {currentView === "profile" && } - {currentView === "thread" && } - {currentView === "articles" && } - {currentView === "media" && } - {currentView === "article-editor" && } - {currentView === "article" && } - {currentView === "about" && } - {currentView === "zaps" && } - {currentView === "dm" && } - {currentView === "notifications" && } - {currentView === "bookmarks" && } - {currentView === "hashtag" && } - {currentView === "podcasts" && } - {currentView === "follows" && } + + {currentView === "search" && } + {currentView === "relays" && } + {currentView === "settings" && } + {currentView === "profile" && } + {currentView === "thread" && } + {currentView === "articles" && } + {currentView === "media" && } + {currentView === "article-editor" && } + {currentView === "article" && } + {currentView === "about" && } + {currentView === "zaps" && } + {currentView === "dm" && } + {currentView === "notifications" && } + {currentView === "bookmarks" && } + {currentView === "hashtag" && } + {currentView === "podcasts" && } + {currentView === "follows" && } + diff --git a/src/components/article/ArticleCard.tsx b/src/components/article/ArticleCard.tsx index 8a88a13..fae7bf8 100644 --- a/src/components/article/ArticleCard.tsx +++ b/src/components/article/ArticleCard.tsx @@ -1,3 +1,4 @@ +import { memo } from "react"; import { NDKEvent, nip19 } from "@nostr-dev-kit/ndk"; import { useProfile } from "../../hooks/useProfile"; import { useUIStore } from "../../stores/ui"; @@ -21,7 +22,7 @@ function buildNaddr(event: NDKEvent): string { }); } -export function ArticleCard({ event }: { event: NDKEvent }) { +export const ArticleCard = memo(function ArticleCard({ event }: { event: NDKEvent }) { const { openArticle, openProfile } = useUIStore(); const profile = useProfile(event.pubkey); @@ -117,4 +118,4 @@ export function ArticleCard({ event }: { event: NDKEvent }) { ); -} +}); diff --git a/src/components/feed/Feed.tsx b/src/components/feed/Feed.tsx index c9ae908..045984a 100644 --- a/src/components/feed/Feed.tsx +++ b/src/components/feed/Feed.tsx @@ -24,10 +24,25 @@ function timeAgo(ts: number): string { } export function Feed() { - const { notes, loading, error, connect, loadCachedFeed, loadFeed, trendingNotes, trendingLoading, loadTrendingFeed, focusedNoteIndex, lastUpdated } = useFeedStore(); - const { loggedIn, follows } = useUserStore(); - const { mutedPubkeys, contentMatchesMutedKeyword } = useMuteStore(); - const { feedTab: tab, setFeedTab: setTab, feedLanguageFilter, setFeedLanguageFilter } = useUIStore(); + const notes = useFeedStore((s) => s.notes); + const loading = useFeedStore((s) => s.loading); + const error = useFeedStore((s) => s.error); + const connect = useFeedStore((s) => s.connect); + const loadCachedFeed = useFeedStore((s) => s.loadCachedFeed); + const loadFeed = useFeedStore((s) => s.loadFeed); + const trendingNotes = useFeedStore((s) => s.trendingNotes); + const trendingLoading = useFeedStore((s) => s.trendingLoading); + const loadTrendingFeed = useFeedStore((s) => s.loadTrendingFeed); + const focusedNoteIndex = useFeedStore((s) => s.focusedNoteIndex); + const lastUpdated = useFeedStore((s) => s.lastUpdated); + const loggedIn = useUserStore((s) => s.loggedIn); + const follows = useUserStore((s) => s.follows); + const mutedPubkeys = useMuteStore((s) => s.mutedPubkeys); + const contentMatchesMutedKeyword = useMuteStore((s) => s.contentMatchesMutedKeyword); + const tab = useUIStore((s) => s.feedTab); + const setTab = useUIStore((s) => s.setFeedTab); + const feedLanguageFilter = useUIStore((s) => s.feedLanguageFilter); + const setFeedLanguageFilter = useUIStore((s) => s.setFeedLanguageFilter); const [followNotes, setFollowNotes] = useState([]); const [followLoading, setFollowLoading] = useState(false); const [, setTick] = useState(0); diff --git a/src/components/feed/NoteCard.tsx b/src/components/feed/NoteCard.tsx index 3aa07da..dae3e01 100644 --- a/src/components/feed/NoteCard.tsx +++ b/src/components/feed/NoteCard.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from "react"; +import { useState, useRef, useEffect, memo } from "react"; import { NDKEvent } from "@nostr-dev-kit/ndk"; import { useProfile } from "../../hooks/useProfile"; import { useNip05Verified } from "../../hooks/useNip05Verified"; @@ -25,7 +25,7 @@ function ParentAuthorName({ pubkey }: { pubkey: string }) { return @{name}; } -export function NoteCard({ event, focused, onReplyInThread }: NoteCardProps) { +export const NoteCard = memo(function NoteCard({ event, focused, onReplyInThread }: NoteCardProps) { const profile = useProfile(event.pubkey); const rawName = profile?.displayName || profile?.name; const name = (typeof rawName === "string" ? rawName : null) || shortenPubkey(event.pubkey); @@ -34,10 +34,18 @@ export function NoteCard({ event, focused, onReplyInThread }: NoteCardProps) { const verified = useNip05Verified(event.pubkey, nip05); const time = event.created_at ? timeAgo(event.created_at) : ""; - const { loggedIn, pubkey: ownPubkey, follows, follow, unfollow } = useUserStore(); - const { mutedPubkeys, mute, unmute } = useMuteStore(); + const loggedIn = useUserStore((s) => s.loggedIn); + const ownPubkey = useUserStore((s) => s.pubkey); + const follows = useUserStore((s) => s.follows); + const follow = useUserStore((s) => s.follow); + const unfollow = useUserStore((s) => s.unfollow); + const mutedPubkeys = useMuteStore((s) => s.mutedPubkeys); + const mute = useMuteStore((s) => s.mute); + const unmute = useMuteStore((s) => s.unmute); const isMuted = mutedPubkeys.includes(event.pubkey); - const { openProfile, openThread, currentView } = useUIStore(); + const openProfile = useUIStore((s) => s.openProfile); + const openThread = useUIStore((s) => s.openThread); + const currentView = useUIStore((s) => s.currentView); const parentEventId = getParentEventId(event); // The immediate parent author is typically the last p tag (NIP-10 ordering mirrors e tags). @@ -194,4 +202,4 @@ export function NoteCard({ event, focused, onReplyInThread }: NoteCardProps) { ); -} +});