diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx
index 9f6f53e..b63bfdf 100644
--- a/src/AppRouter.tsx
+++ b/src/AppRouter.tsx
@@ -1,106 +1,124 @@
+import { lazy, Suspense } from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { ScrollToTop } from "./components/ScrollToTop";
+// Eagerly load only the most-visited pages
import Index from "./pages/Index";
-import MapPage from "./pages/MapPage";
-import EventsPage from "./pages/EventsPage";
-import FoodPage from "./pages/FoodPage";
-import WalletPage from "./pages/WalletPage";
-import SettingsPage from "./pages/SettingsPage";
-import CreateSpotPage from "./pages/CreateSpotPage";
-import CreateShopPage from "./pages/CreateShopPage";
-import CreateEventPage from "./pages/CreateEventPage";
-import CreateListingPage from "./pages/CreateListingPage";
-import CreateTourPage from "./pages/CreateTourPage";
-import EditSpotPage from "./pages/EditSpotPage";
-import EditShopPage from "./pages/EditShopPage";
-import EditEventPage from "./pages/EditEventPage";
-import GearSwapPage from "./pages/GearSwapPage";
-import SpotDetailPage from "./pages/SpotDetailPage";
-import ShopDetailPage from "./pages/ShopDetailPage";
-import EventDetailPage from "./pages/EventDetailPage";
-import OnboardingPage from "./pages/OnboardingPage";
-import PyramidPage from "./pages/PyramidPage";
-import LabPage from "./pages/LabPage";
-import BecomeCoachPage from "./pages/BecomeCoachPage";
-import BecomeFilmerPage from "./pages/BecomeFilmerPage";
-import BecomeEditorPage from "./pages/BecomeEditorPage";
-import CoachDetailPage from "./pages/CoachDetailPage";
-import TourDetailPage from "./pages/TourDetailPage";
-import AboutPage from "./pages/AboutPage";
-import FAQPage from "./pages/FAQPage";
-import CommunityStatsPage from "./pages/CommunityStatsPage";
-import SpotCollectionsPage from "./pages/SpotCollectionsPage";
-import CollectionDetailPage from "./pages/CollectionDetailPage";
-import DirectMessagesPage from "./pages/DirectMessagesPage";
-import ConversationPage from "./pages/ConversationPage";
-import BountyLeaderboardPage from "./pages/BountyLeaderboardPage";
-import SignersPage from "./pages/SignersPage";
-import StashPage from "./pages/StashPage";
-import { NIP19Page } from "./pages/NIP19Page";
-import LicensePage from "./pages/LicensePage";
-import SupportersPage from "./pages/SupportersPage";
-import RiderCardPage from "./pages/RiderCardPage";
-import AmberCallbackPage from "./pages/AmberCallbackPage";
-import NotFound from "./pages/NotFound";
+
+// Lazy load everything else — each page becomes its own JS chunk
+const MapPage = lazy(() => import("./pages/MapPage"));
+const EventsPage = lazy(() => import("./pages/EventsPage"));
+const LabPage = lazy(() => import("./pages/LabPage"));
+const WalletPage = lazy(() => import("./pages/WalletPage"));
+const SettingsPage = lazy(() => import("./pages/SettingsPage"));
+const CreateSpotPage = lazy(() => import("./pages/CreateSpotPage"));
+const CreateShopPage = lazy(() => import("./pages/CreateShopPage"));
+const CreateEventPage = lazy(() => import("./pages/CreateEventPage"));
+const CreateListingPage = lazy(() => import("./pages/CreateListingPage"));
+const CreateTourPage = lazy(() => import("./pages/CreateTourPage"));
+const EditSpotPage = lazy(() => import("./pages/EditSpotPage"));
+const EditShopPage = lazy(() => import("./pages/EditShopPage"));
+const EditEventPage = lazy(() => import("./pages/EditEventPage"));
+const GearSwapPage = lazy(() => import("./pages/GearSwapPage"));
+const SpotDetailPage = lazy(() => import("./pages/SpotDetailPage"));
+const ShopDetailPage = lazy(() => import("./pages/ShopDetailPage"));
+const EventDetailPage = lazy(() => import("./pages/EventDetailPage"));
+const OnboardingPage = lazy(() => import("./pages/OnboardingPage"));
+const PyramidPage = lazy(() => import("./pages/PyramidPage"));
+const BecomeCoachPage = lazy(() => import("./pages/BecomeCoachPage"));
+const BecomeFilmerPage = lazy(() => import("./pages/BecomeFilmerPage"));
+const BecomeEditorPage = lazy(() => import("./pages/BecomeEditorPage"));
+const CoachDetailPage = lazy(() => import("./pages/CoachDetailPage"));
+const TourDetailPage = lazy(() => import("./pages/TourDetailPage"));
+const AboutPage = lazy(() => import("./pages/AboutPage"));
+const FAQPage = lazy(() => import("./pages/FAQPage"));
+const FoodPage = lazy(() => import("./pages/FoodPage"));
+const CommunityStatsPage = lazy(() => import("./pages/CommunityStatsPage"));
+const SpotCollectionsPage = lazy(() => import("./pages/SpotCollectionsPage"));
+const CollectionDetailPage = lazy(() => import("./pages/CollectionDetailPage"));
+const DirectMessagesPage = lazy(() => import("./pages/DirectMessagesPage"));
+const ConversationPage = lazy(() => import("./pages/ConversationPage"));
+const BountyLeaderboardPage = lazy(() => import("./pages/BountyLeaderboardPage"));
+const SignersPage = lazy(() => import("./pages/SignersPage"));
+const StashPage = lazy(() => import("./pages/StashPage"));
+const NIP19Page = lazy(() => import("./pages/NIP19Page").then(m => ({ default: m.NIP19Page })));
+const LicensePage = lazy(() => import("./pages/LicensePage"));
+const SupportersPage = lazy(() => import("./pages/SupportersPage"));
+const RiderCardPage = lazy(() => import("./pages/RiderCardPage"));
+const AmberCallbackPage = lazy(() => import("./pages/AmberCallbackPage"));
+const MarketplacePage = lazy(() => import("./pages/MarketplacePage"));
+const NotFound = lazy(() => import("./pages/NotFound"));
+
+// Minimal inline fallback — no extra component needed
+function PageLoader() {
+ return (
+
+ );
+}
export function AppRouter() {
return (
-
- } />
- } />
- } />
- } />
- {/* Legacy route redirect */}
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- {/* Bounty System */}
- } />
- {/* Security & Signers */}
- } />
- {/* Stash - Private Inventory */}
- } />
- {/* Info pages */}
- } />
- } />
- } />
- {/* Amber NIP-55 callback — Amber redirects here after signing/login */}
- } />
- {/* NIP-19 route for npub1, note1, naddr1, nevent1, nprofile1 */}
- } />
- {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
- } />
-
+ }>
+
+ } />
+ } />
+ } />
+ } />
+ {/* Legacy route redirect */}
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ {/* Bounty System */}
+ } />
+ {/* Security & Signers */}
+ } />
+ {/* Stash - Private Inventory */}
+ } />
+ {/* Info pages */}
+ } />
+ } />
+ } />
+ {/* Amber NIP-55 callback — Amber redirects here after signing/login */}
+ } />
+ {/* Marketplace */}
+ } />
+ {/* NIP-19 route for npub1, note1, naddr1, nevent1, nprofile1 */}
+ } />
+ {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
+ } />
+
+
);
}
diff --git a/src/hooks/useMarketplace.ts b/src/hooks/useMarketplace.ts
index 21d248c..9822b48 100644
--- a/src/hooks/useMarketplace.ts
+++ b/src/hooks/useMarketplace.ts
@@ -2,15 +2,17 @@ import { useQuery } from '@tanstack/react-query';
import { MARKETPLACE_ITEM_KIND, parseMarketplaceItem, type MarketplaceItem } from '@/lib/types';
import { useSkateRelays } from '@/hooks/useSkateRelays';
+const STALE_TIME = 5 * 60 * 1000; // 5 min
+const GC_TIME = 30 * 60 * 1000; // 30 min
+const TIMEOUT = 8000;
+
export function useMarketplaceItems(limit = 100) {
const { querySkateContent, getQueryKeySuffix } = useSkateRelays();
return useQuery({
queryKey: ['marketplace-items', limit, getQueryKeySuffix()],
queryFn: async () => {
- const signal = AbortSignal.timeout(10000);
-
- console.log('[useMarketplaceItems] Querying for marketplace items...');
+ const signal = AbortSignal.timeout(TIMEOUT);
const events = await querySkateContent(
[
@@ -23,20 +25,17 @@ export function useMarketplaceItems(limit = 100) {
{ signal }
);
- console.log(`[useMarketplaceItems] Received ${events.length} events`);
-
const items: MarketplaceItem[] = [];
for (const event of events) {
const item = parseMarketplaceItem(event);
if (item) items.push(item);
}
- console.log(`[useMarketplaceItems] Parsed items: ${items.length}`);
-
return items.sort((a, b) => b.createdAt - a.createdAt);
},
- staleTime: 30000,
- refetchOnMount: 'always',
+ staleTime: STALE_TIME,
+ gcTime: GC_TIME,
+ refetchOnMount: true,
});
}
@@ -46,13 +45,10 @@ export function useMarketplaceAtSpot(spotPubkey: string, spotIdentifier: string,
return useQuery({
queryKey: ['marketplace-at-spot', spotPubkey, spotIdentifier, getQueryKeySuffix()],
queryFn: async () => {
- const signal = AbortSignal.timeout(10000);
+ const signal = AbortSignal.timeout(TIMEOUT);
- // Build spot reference (naddr format without encoding)
const spotRef = `30818:${spotPubkey}:${spotIdentifier}`;
- console.log(`[useMarketplaceAtSpot] Querying for items at spot: ${spotRef}`);
-
const events = await querySkateContent(
[
{
@@ -65,23 +61,19 @@ export function useMarketplaceAtSpot(spotPubkey: string, spotIdentifier: string,
{ signal }
);
- console.log(`[useMarketplaceAtSpot] Received ${events.length} events`);
-
const items: MarketplaceItem[] = [];
for (const event of events) {
const item = parseMarketplaceItem(event);
- // Only show items that are active or don't have a status
if (item && (!item.status || item.status === 'active')) {
items.push(item);
}
}
- console.log(`[useMarketplaceAtSpot] Parsed active items: ${items.length}`);
-
return items.sort((a, b) => b.createdAt - a.createdAt);
},
enabled: !!spotPubkey && !!spotIdentifier,
- staleTime: 30000,
- refetchOnMount: 'always',
+ staleTime: STALE_TIME,
+ gcTime: GC_TIME,
+ refetchOnMount: true,
});
}
diff --git a/src/hooks/useShops.ts b/src/hooks/useShops.ts
index 8b72f72..05ae996 100644
--- a/src/hooks/useShops.ts
+++ b/src/hooks/useShops.ts
@@ -10,8 +10,6 @@ export function useShops(limit = 100) {
queryFn: async () => {
const signal = AbortSignal.timeout(10000);
- console.log('[useShops] Querying for shops');
-
// Query new shop kind (30017)
const newShops = await querySkateContent(
[
@@ -23,8 +21,6 @@ export function useShops(limit = 100) {
],
{ signal }
);
- console.log(`[useShops] Received ${newShops.length} new shops (kind 30017)`);
-
// Query old shops (kind 30818 with 'shop' type) for backward compatibility
const oldShops = await querySkateContent(
[
@@ -36,7 +32,7 @@ export function useShops(limit = 100) {
],
{ signal }
);
- console.log(`[useShops] Received ${oldShops.length} old shops (kind 30818)`);
+
const allEvents = [...newShops, ...oldShops];
@@ -72,19 +68,15 @@ export function useShops(limit = 100) {
if (shop) {
shops.push(shop);
- } else {
- console.warn('[useShops] Failed to parse event:', event);
}
}
- console.log('[useShops] Parsed shops:', shops.length);
-
// Sort by created_at descending
return shops.sort((a, b) => b.createdAt - a.createdAt);
},
- staleTime: 30000,
- gcTime: 300000,
- refetchOnMount: 'always',
+ staleTime: 5 * 60 * 1000,
+ gcTime: 30 * 60 * 1000,
+ refetchOnMount: true,
});
}
@@ -96,8 +88,6 @@ export function useShop(identifier: string, pubkey?: string) {
queryFn: async () => {
const signal = AbortSignal.timeout(10000);
- console.log(`[useShop] Querying for shop: ${identifier}`);
-
// Query new shop format (kind 30017)
const newFilter: {
kinds: number[];
@@ -112,8 +102,6 @@ export function useShop(identifier: string, pubkey?: string) {
if (pubkey) newFilter.authors = [pubkey];
const newShops = await querySkateContent([newFilter], { signal });
- console.log(`[useShop] Received ${newShops.length} new shops`);
-
// Query old shop format (kind 30818 with 'shop' type) for backward compatibility
const oldFilter: {
kinds: number[];
@@ -130,14 +118,9 @@ export function useShop(identifier: string, pubkey?: string) {
if (pubkey) oldFilter.authors = [pubkey];
const oldShops = await querySkateContent([oldFilter], { signal });
- console.log(`[useShop] Received ${oldShops.length} old shops`);
-
const allEvents = [...newShops, ...oldShops];
- if (allEvents.length === 0) {
- console.warn(`[useShop] No events found for identifier: ${identifier}`);
- return null;
- }
+ if (allEvents.length === 0) return null;
// If multiple events, take the most recent
const mostRecent = allEvents.reduce((latest, current) =>
@@ -174,8 +157,9 @@ export function useShop(identifier: string, pubkey?: string) {
return shop;
},
enabled: !!identifier,
- staleTime: 30000,
- refetchOnMount: 'always',
+ staleTime: 5 * 60 * 1000,
+ gcTime: 30 * 60 * 1000,
+ refetchOnMount: true,
});
}
@@ -208,7 +192,8 @@ export function useUserShops(pubkey: string) {
return shops.sort((a, b) => b.createdAt - a.createdAt);
},
enabled: !!pubkey,
- staleTime: 30000,
- refetchOnMount: 'always',
+ staleTime: 5 * 60 * 1000,
+ gcTime: 30 * 60 * 1000,
+ refetchOnMount: true,
});
}
diff --git a/src/hooks/useSkateSpots.ts b/src/hooks/useSkateSpots.ts
index cf6f22f..e3998a1 100644
--- a/src/hooks/useSkateSpots.ts
+++ b/src/hooks/useSkateSpots.ts
@@ -3,6 +3,11 @@ import { SKATE_SPOT_KIND, parseSkateSpot, type SkateSpot } from '@/lib/types';
import { useSkateRelays } from '@/hooks/useSkateRelays';
import { cacheSpots, getAllCachedSpots, isOffline } from '@/lib/offlineCache';
+// Spots change infrequently — cache for 5 minutes, keep for 30 minutes
+const SPOTS_STALE_TIME = 5 * 60 * 1000; // 5 min
+const SPOTS_GC_TIME = 30 * 60 * 1000; // 30 min
+const QUERY_TIMEOUT = 8000; // 8s — fail fast, show error sooner
+
export function useSkateSpots(limit = 100) {
const { querySkateContent, getQueryKeySuffix } = useSkateRelays();
@@ -11,12 +16,11 @@ export function useSkateSpots(limit = 100) {
queryFn: async () => {
// If offline, return cached spots
if (isOffline()) {
- console.log('[useSkateSpots] Offline, returning cached spots');
const cachedSpots = await getAllCachedSpots();
return cachedSpots.sort((a, b) => b.createdAt - a.createdAt).slice(0, limit);
}
- const signal = AbortSignal.timeout(30000);
+ const signal = AbortSignal.timeout(QUERY_TIMEOUT);
const events = await querySkateContent(
[
@@ -31,32 +35,26 @@ export function useSkateSpots(limit = 100) {
const spots: SkateSpot[] = [];
const spotsWithEvents: Array<{ event: typeof events[0]; spot: SkateSpot }> = [];
-
+
for (const event of events) {
const spot = parseSkateSpot(event);
if (spot) {
spots.push(spot);
spotsWithEvents.push({ event, spot });
- } else {
- console.warn('[useSkateSpots] Failed to parse event:', event);
}
}
- console.log('[useSkateSpots] Parsed spots:', spots.length);
-
// Cache spots in the background for offline use
if (spotsWithEvents.length > 0) {
- cacheSpots(spotsWithEvents).catch(err =>
- console.warn('[useSkateSpots] Failed to cache spots:', err)
- );
+ cacheSpots(spotsWithEvents).catch(() => {});
}
- // Sort by created_at descending
return spots.sort((a, b) => b.createdAt - a.createdAt);
},
- staleTime: 30000,
- gcTime: 300000,
- refetchOnMount: 'always',
+ staleTime: SPOTS_STALE_TIME,
+ gcTime: SPOTS_GC_TIME,
+ // Don't force a relay round-trip on every navigation — use cache if fresh
+ refetchOnMount: true,
});
}
@@ -68,16 +66,12 @@ export function useSkateSpot(identifier: string, pubkey?: string) {
queryFn: async () => {
// If offline, try to get from cache first
if (isOffline() && pubkey) {
- console.log('[useSkateSpot] Offline, checking cache');
const { getCachedSpot } = await import('@/lib/offlineCache');
const cached = await getCachedSpot(pubkey, identifier);
- if (cached) {
- console.log('[useSkateSpot] Found in cache:', identifier);
- return cached;
- }
+ if (cached) return cached;
}
- const signal = AbortSignal.timeout(30000);
+ const signal = AbortSignal.timeout(QUERY_TIMEOUT);
const filter: {
kinds: number[];
@@ -96,26 +90,22 @@ export function useSkateSpot(identifier: string, pubkey?: string) {
const events = await querySkateContent([filter], { signal });
- if (events.length === 0) {
- console.warn(`[useSkateSpot] No events found for identifier: ${identifier}`);
- return null;
- }
+ if (events.length === 0) return null;
const spot = parseSkateSpot(events[0]);
-
+
// Cache the spot for offline use
if (spot) {
const { cacheSpot } = await import('@/lib/offlineCache');
- cacheSpot(events[0], spot).catch(err =>
- console.warn('[useSkateSpot] Failed to cache spot:', err)
- );
+ cacheSpot(events[0], spot).catch(() => {});
}
return spot;
},
enabled: !!identifier,
- staleTime: 30000,
- refetchOnMount: 'always',
+ staleTime: SPOTS_STALE_TIME,
+ gcTime: SPOTS_GC_TIME,
+ refetchOnMount: true,
});
}
@@ -125,7 +115,7 @@ export function useUserSkateSpots(pubkey: string) {
return useQuery({
queryKey: ['user-skate-spots', pubkey, getQueryKeySuffix()],
queryFn: async () => {
- const signal = AbortSignal.timeout(30000);
+ const signal = AbortSignal.timeout(QUERY_TIMEOUT);
const events = await querySkateContent(
[
@@ -148,7 +138,8 @@ export function useUserSkateSpots(pubkey: string) {
return spots.sort((a, b) => b.createdAt - a.createdAt);
},
enabled: !!pubkey,
- staleTime: 30000,
- refetchOnMount: 'always',
+ staleTime: SPOTS_STALE_TIME,
+ gcTime: SPOTS_GC_TIME,
+ refetchOnMount: true,
});
}
diff --git a/src/hooks/useTours.ts b/src/hooks/useTours.ts
index ed1307c..0cf6264 100644
--- a/src/hooks/useTours.ts
+++ b/src/hooks/useTours.ts
@@ -2,15 +2,17 @@ import { useQuery } from '@tanstack/react-query';
import { SKATE_TOUR_KIND, parseSkateTour, type SkateTour } from '@/lib/types';
import { useSkateRelays } from '@/hooks/useSkateRelays';
+const STALE_TIME = 5 * 60 * 1000; // 5 min
+const GC_TIME = 30 * 60 * 1000; // 30 min
+const TIMEOUT = 8000;
+
export function useTours(limit = 100) {
const { querySkateContent, getQueryKeySuffix } = useSkateRelays();
return useQuery({
queryKey: ['skate-tours', limit, getQueryKeySuffix()],
queryFn: async () => {
- const signal = AbortSignal.timeout(10000);
-
- console.log('[useTours] Querying for tours');
+ const signal = AbortSignal.timeout(TIMEOUT);
const events = await querySkateContent(
[
@@ -23,26 +25,17 @@ export function useTours(limit = 100) {
{ signal }
);
- console.log(`[useTours] Received ${events.length} events`);
-
const tours: SkateTour[] = [];
for (const event of events) {
const tour = parseSkateTour(event);
- if (tour) {
- tours.push(tour);
- } else {
- console.warn('[useTours] Failed to parse event:', event);
- }
+ if (tour) tours.push(tour);
}
- console.log('[useTours] Parsed tours:', tours.length);
-
- // Sort by created_at descending
return tours.sort((a, b) => b.createdAt - a.createdAt);
},
- staleTime: 30000,
- gcTime: 300000,
- refetchOnMount: 'always',
+ staleTime: STALE_TIME,
+ gcTime: GC_TIME,
+ refetchOnMount: true,
});
}
@@ -52,7 +45,7 @@ export function useTour(identifier: string, pubkey?: string) {
return useQuery({
queryKey: ['skate-tour', identifier, pubkey, getQueryKeySuffix()],
queryFn: async () => {
- const signal = AbortSignal.timeout(10000);
+ const signal = AbortSignal.timeout(TIMEOUT);
const filter: {
kinds: number[];
@@ -69,22 +62,16 @@ export function useTour(identifier: string, pubkey?: string) {
filter.authors = [pubkey];
}
- console.log(`[useTour] Querying for tour: ${identifier}`);
-
const events = await querySkateContent([filter], { signal });
- console.log(`[useTour] Received ${events.length} events`);
-
- if (events.length === 0) {
- console.warn(`[useTour] No events found for identifier: ${identifier}`);
- return null;
- }
+ if (events.length === 0) return null;
return parseSkateTour(events[0]);
},
enabled: !!identifier,
- staleTime: 30000,
- refetchOnMount: 'always',
+ staleTime: STALE_TIME,
+ gcTime: GC_TIME,
+ refetchOnMount: true,
});
}
@@ -94,7 +81,7 @@ export function useUserTours(pubkey: string) {
return useQuery({
queryKey: ['user-skate-tours', pubkey, getQueryKeySuffix()],
queryFn: async () => {
- const signal = AbortSignal.timeout(10000);
+ const signal = AbortSignal.timeout(TIMEOUT);
const events = await querySkateContent(
[
@@ -117,7 +104,8 @@ export function useUserTours(pubkey: string) {
return tours.sort((a, b) => b.createdAt - a.createdAt);
},
enabled: !!pubkey,
- staleTime: 30000,
- refetchOnMount: 'always',
+ staleTime: STALE_TIME,
+ gcTime: GC_TIME,
+ refetchOnMount: true,
});
}
diff --git a/src/pages/MapPage.tsx b/src/pages/MapPage.tsx
index 3f9bb81..951dd5a 100644
--- a/src/pages/MapPage.tsx
+++ b/src/pages/MapPage.tsx
@@ -1,8 +1,10 @@
-import { useState, useMemo, useEffect } from 'react';
+import { useState, useMemo, useEffect, lazy, Suspense } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { Layout } from '@/components/Layout';
-import { SpotMap } from '@/components/SpotMap';
+
+// Lazy-load the map — Leaflet is large (~150KB) and only needed on this page
+const SpotMap = lazy(() => import('@/components/SpotMap').then(m => ({ default: m.SpotMap })));
import { SpotCard, SpotCardSkeleton } from '@/components/SpotCard';
import { WeatherToggle } from '@/components/WeatherToggle';
import { TrustFilter, type TrustFilterLevel, filterByTrustLevel } from '@/components/TrustFilter';
@@ -1061,25 +1063,34 @@ export default function MapPage() {
{viewMode === 'map' ? (
<>
-
+
+
+
+ }>
+
+
{/* Loading indicator */}
{isLoading && (
diff --git a/vite.config.ts b/vite.config.ts
index 0525a15..cb5f9d9 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -20,7 +20,7 @@ export default defineConfig(() => ({
return !log.includes("React Router Future Flag Warning");
},
env: {
- DEBUG_PRINT_LIMIT: '0', // Suppress DOM output that exceeds AI context windows
+ DEBUG_PRINT_LIMIT: '0',
},
},
resolve: {
@@ -32,8 +32,55 @@ export default defineConfig(() => ({
exclude: ['@lightninglabs/lnc-web'],
},
build: {
+ // Strip console.log/warn/debug in production (keeps console.error for real errors)
+ minify: 'esbuild',
commonjsOptions: {
transformMixedEsModules: true,
},
+ rollupOptions: {
+ output: {
+ // Split large vendor libraries into separate cacheable chunks.
+ // Users only re-download a chunk when that specific library updates.
+ manualChunks(id) {
+ // Map — Leaflet is large (~150KB), isolate it so non-map pages don't pay for it
+ if (id.includes('leaflet') || id.includes('ngeohash')) {
+ return 'vendor-map';
+ }
+ // Nostr core — changes infrequently, worth caching separately
+ if (id.includes('nostr-tools') || id.includes('@nostrify') || id.includes('@noble')) {
+ return 'vendor-nostr';
+ }
+ // Bitcoin / Lightning — large, rarely changes
+ if (
+ id.includes('@cashu') ||
+ id.includes('bitcoinjs-lib') ||
+ id.includes('@scure') ||
+ id.includes('@bitcoinerlab') ||
+ id.includes('ecpair') ||
+ id.includes('@getalby') ||
+ id.includes('npubcash')
+ ) {
+ return 'vendor-bitcoin';
+ }
+ // React ecosystem — very stable, long cache life
+ if (id.includes('react-dom') || id.includes('react-router')) {
+ return 'vendor-react';
+ }
+ // Radix UI components — large but stable
+ if (id.includes('@radix-ui')) {
+ return 'vendor-radix';
+ }
+ // TanStack Query
+ if (id.includes('@tanstack')) {
+ return 'vendor-query';
+ }
+ },
+ },
+ },
},
-}));
\ No newline at end of file
+ // Strip console.log/warn/debug calls from production build
+ esbuild: {
+ drop: ['debugger'],
+ pure: ['console.log', 'console.warn', 'console.debug'],
+ },
+}));