Skip to content

Commit 51710e6

Browse files
authored
feat: reduce frontend bundle size with tree shaking and dependency audit (#422)
- Add @next/bundle-analyzer with ANALYZE=true script for bundle visibility - Configure next.config.ts: transpilePackages for Web3 libs, optimizePackageImports for lucide-react/framer-motion/all Radix UI packages, Node.js polyfill fallbacks - Lazy-init @web3auth/modal via getWeb3Auth() async fn — removes ~500KB from initial bundle; SDK only loads on first social login click - Scope WagmiProvider + QueryClientProvider to dashboard routes only via DashboardProviders — landing page no longer loads wagmi/viem - Convert landing page (app/page.tsx) to Server Component; extract animated sections into minimal client islands (HeroCTA, HeroAnimations) - Dynamically import Sidebar, Header, PWAWrapper in dashboard layout - Dynamically import SocialLogin and WalletConnect on auth page with skeleton fallbacks so Web3 SDK is deferred until tab renders - Replace framer-motion motion.div entrance animations in all dashboard pages with zero-JS CSS FadeIn component (animate-fade-in-up keyframe) - Fix web3auth logout: use provider check instead of non-existent .connected prop - Fix auth page provider race: DashboardProviders is static import so WagmiProvider is mounted before WalletConnect calls useConnect() - Remove unused DashboardInvoice import and dead handleSubmitWork function
1 parent d06d25f commit 51710e6

21 files changed

Lines changed: 775 additions & 930 deletions

File tree

backend/frontend/app/auth/page.tsx

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,44 @@
11
'use client';
22

33
import { motion } from 'framer-motion';
4-
import { SocialLogin } from '@/components/auth/SocialLogin';
5-
import { WalletConnect } from '@/components/auth/WalletConnect';
4+
import dynamic from 'next/dynamic';
65
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
76
import { Wallet, Users } from 'lucide-react';
7+
import { Skeleton } from '@/components/ui/skeleton';
8+
// DashboardProviders must be a static import so WagmiProvider is available
9+
// synchronously before any child component calls useAccount() or useConnect().
10+
// Dynamically importing it would cause a race where WalletConnect renders
11+
// before the provider mounts, throwing a wagmi context error.
12+
import { DashboardProviders } from '@/components/providers-dashboard';
13+
14+
// Dynamically import the heavy auth components — they pull in @web3auth/modal
15+
// and wagmi connectors. Loading them on-demand keeps the auth page's initial
16+
// JS small while the provider is already in place.
17+
const SocialLogin = dynamic(
18+
() => import('@/components/auth/SocialLogin').then((m) => m.SocialLogin),
19+
{
20+
loading: () => (
21+
<div className="space-y-3 mt-6">
22+
{[1, 2, 3].map((i) => (
23+
<Skeleton key={i} className="h-12 w-full rounded-md" />
24+
))}
25+
</div>
26+
),
27+
ssr: false,
28+
}
29+
);
30+
31+
const WalletConnect = dynamic(
32+
() => import('@/components/auth/WalletConnect').then((m) => m.WalletConnect),
33+
{
34+
loading: () => (
35+
<div className="space-y-3 mt-6">
36+
<Skeleton className="h-12 w-full rounded-md" />
37+
</div>
38+
),
39+
ssr: false,
40+
}
41+
);
842

943
export default function AuthPage() {
1044
return (
@@ -28,31 +62,32 @@ export default function AuthPage() {
2862
<h1 className="text-3xl font-bold text-gray-900 mb-2">
2963
Welcome to AgenticPay
3064
</h1>
31-
<p className="text-gray-600">
32-
Get paid instantly for your work
33-
</p>
65+
<p className="text-gray-600">Get paid instantly for your work</p>
3466
</div>
3567

36-
<Tabs defaultValue="social" className="w-full">
37-
<TabsList className="grid w-full grid-cols-2 mb-6">
38-
<TabsTrigger value="social" className="flex items-center gap-2">
39-
<Users className="h-4 w-4" />
40-
Social Login
41-
</TabsTrigger>
42-
<TabsTrigger value="wallet" className="flex items-center gap-2">
43-
<Wallet className="h-4 w-4" />
44-
Web3 Wallet
45-
</TabsTrigger>
46-
</TabsList>
68+
{/* Provider must wrap the tabs so WalletConnect can call useConnect() */}
69+
<DashboardProviders>
70+
<Tabs defaultValue="social" className="w-full">
71+
<TabsList className="grid w-full grid-cols-2 mb-6">
72+
<TabsTrigger value="social" className="flex items-center gap-2">
73+
<Users className="h-4 w-4" />
74+
Social Login
75+
</TabsTrigger>
76+
<TabsTrigger value="wallet" className="flex items-center gap-2">
77+
<Wallet className="h-4 w-4" />
78+
Web3 Wallet
79+
</TabsTrigger>
80+
</TabsList>
4781

48-
<TabsContent value="social">
49-
<SocialLogin />
50-
</TabsContent>
82+
<TabsContent value="social">
83+
<SocialLogin />
84+
</TabsContent>
5185

52-
<TabsContent value="wallet">
53-
<WalletConnect />
54-
</TabsContent>
55-
</Tabs>
86+
<TabsContent value="wallet">
87+
<WalletConnect />
88+
</TabsContent>
89+
</Tabs>
90+
</DashboardProviders>
5691

5792
<p className="text-xs text-gray-500 text-center mt-6">
5893
By continuing, you agree to our Terms of Service and Privacy Policy
@@ -62,4 +97,3 @@ export default function AuthPage() {
6297
</div>
6398
);
6499
}
65-

backend/frontend/app/dashboard/invoices/page.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
'use client';
22

33
import { useState } from 'react';
4-
import { useDashboardData, DashboardInvoice } from '@/lib/hooks/useDashboardData';
4+
import { useDashboardData } from '@/lib/hooks/useDashboardData';
55
import { Card, CardContent } from '@/components/ui/card';
66
import { Button } from '@/components/ui/button';
77
import { CheckCircle2, Clock, AlertCircle, Filter, FileText } from 'lucide-react';
8-
import { motion } from 'framer-motion';
8+
import { FadeIn } from '@/components/ui/fade-in';
99
import Link from 'next/link';
1010
import { InvoiceCardSkeleton } from '@/components/ui/loading-skeletons';
1111
import { EmptyState } from '@/components/empty/EmptyState';
@@ -88,12 +88,7 @@ export default function InvoicesPage() {
8888

8989
<div className="grid grid-cols-1 gap-4">
9090
{filteredInvoices.map((invoice, index) => (
91-
<motion.div
92-
key={invoice.id}
93-
initial={{ opacity: 0, y: 20 }}
94-
animate={{ opacity: 1, y: 0 }}
95-
transition={{ delay: index * 0.05 }}
96-
>
91+
<FadeIn key={invoice.id} delay={index * 0.05}>
9792
<Link href={`/dashboard/projects/${invoice.projectId}`}>
9893
<Card className="hover:shadow-lg transition-all cursor-pointer">
9994
<CardContent className="p-6">
@@ -124,7 +119,7 @@ export default function InvoicesPage() {
124119
</CardContent>
125120
</Card>
126121
</Link>
127-
</motion.div>
122+
</FadeIn>
128123
))}
129124
</div>
130125

backend/frontend/app/dashboard/layout.tsx

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,26 @@
33
import { useAuthStore } from '@/store/useAuthStore';
44
import { useRouter } from 'next/navigation';
55
import { useEffect } from 'react';
6-
import { Sidebar } from '@/components/layout/Sidebar';
7-
import { Header } from '@/components/layout/Header';
6+
import dynamic from 'next/dynamic';
7+
import { DashboardProviders } from '@/components/providers-dashboard';
88
import { ErrorBoundary } from '@/components/errors/ErrorBoundary';
99

10+
// Sidebar and Header are dashboard-only — load them as part of the dashboard
11+
// chunk rather than the root bundle.
12+
const Sidebar = dynamic(
13+
() => import('@/components/layout/Sidebar').then((m) => m.Sidebar),
14+
{ ssr: false }
15+
);
16+
const Header = dynamic(
17+
() => import('@/components/layout/Header').then((m) => m.Header),
18+
{ ssr: false }
19+
);
20+
21+
// PWA components are non-critical — load after the dashboard is interactive.
22+
const PWAWrapper = dynamic(() => import('@/components/PWAWrapper'), {
23+
ssr: false,
24+
});
25+
1026
export default function DashboardLayout({
1127
children,
1228
}: {
@@ -26,17 +42,19 @@ export default function DashboardLayout({
2642
}
2743

2844
return (
29-
<ErrorBoundary>
30-
<div className="flex h-screen bg-gray-50">
31-
<Sidebar />
32-
<div className="flex-1 flex flex-col overflow-hidden lg:ml-64">
33-
<Header />
34-
<main className="flex-1 overflow-y-auto p-4 sm:p-6">
35-
<ErrorBoundary>{children}</ErrorBoundary>
36-
</main>
45+
<DashboardProviders>
46+
<ErrorBoundary>
47+
<div className="flex h-screen bg-gray-50">
48+
<Sidebar />
49+
<div className="flex-1 flex flex-col overflow-hidden lg:ml-64">
50+
<Header />
51+
<main className="flex-1 overflow-y-auto p-4 sm:p-6">
52+
<ErrorBoundary>{children}</ErrorBoundary>
53+
</main>
54+
</div>
3755
</div>
38-
</div>
39-
</ErrorBoundary>
56+
<PWAWrapper />
57+
</ErrorBoundary>
58+
</DashboardProviders>
4059
);
4160
}
42-

backend/frontend/app/dashboard/page.tsx

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useDashboardData } from '@/lib/hooks/useDashboardData';
44
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
55
import { DollarSign, Clock, Folder, CheckCircle2, TrendingUp } from 'lucide-react';
6-
import { motion } from 'framer-motion';
6+
import { FadeIn } from '@/components/ui/fade-in';
77
import { DashboardStatsSkeleton } from '@/components/ui/loading-skeletons';
88

99
export default function DashboardPage() {
@@ -43,11 +43,7 @@ export default function DashboardPage() {
4343

4444
{/* Stats Grid */}
4545
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6">
46-
<motion.div
47-
initial={{ opacity: 0, y: 20 }}
48-
animate={{ opacity: 1, y: 0 }}
49-
transition={{ delay: 0.1 }}
50-
>
46+
<FadeIn delay={0.1}>
5147
<Card className="hover:shadow-lg transition-shadow">
5248
<CardHeader className="flex flex-row items-center justify-between pb-2">
5349
<CardTitle className="text-sm font-medium text-gray-600">
@@ -63,13 +59,9 @@ export default function DashboardPage() {
6359
</p>
6460
</CardContent>
6561
</Card>
66-
</motion.div>
62+
</FadeIn>
6763

68-
<motion.div
69-
initial={{ opacity: 0, y: 20 }}
70-
animate={{ opacity: 1, y: 0 }}
71-
transition={{ delay: 0.2 }}
72-
>
64+
<FadeIn delay={0.2}>
7365
<Card className="hover:shadow-lg transition-shadow">
7466
<CardHeader className="flex flex-row items-center justify-between pb-2">
7567
<CardTitle className="text-sm font-medium text-gray-600">
@@ -82,13 +74,9 @@ export default function DashboardPage() {
8274
<p className="text-xs text-yellow-600 mt-1">Awaiting approval</p>
8375
</CardContent>
8476
</Card>
85-
</motion.div>
77+
</FadeIn>
8678

87-
<motion.div
88-
initial={{ opacity: 0, y: 20 }}
89-
animate={{ opacity: 1, y: 0 }}
90-
transition={{ delay: 0.3 }}
91-
>
79+
<FadeIn delay={0.3}>
9280
<Card className="hover:shadow-lg transition-shadow">
9381
<CardHeader className="flex flex-row items-center justify-between pb-2">
9482
<CardTitle className="text-sm font-medium text-gray-600">
@@ -101,13 +89,9 @@ export default function DashboardPage() {
10189
<p className="text-xs text-blue-600 mt-1">In progress</p>
10290
</CardContent>
10391
</Card>
104-
</motion.div>
92+
</FadeIn>
10593

106-
<motion.div
107-
initial={{ opacity: 0, y: 20 }}
108-
animate={{ opacity: 1, y: 0 }}
109-
transition={{ delay: 0.4 }}
110-
>
94+
<FadeIn delay={0.4}>
11195
<Card className="hover:shadow-lg transition-shadow">
11296
<CardHeader className="flex flex-row items-center justify-between pb-2">
11397
<CardTitle className="text-sm font-medium text-gray-600">
@@ -120,15 +104,11 @@ export default function DashboardPage() {
120104
<p className="text-xs text-gray-600 mt-1">Projects done</p>
121105
</CardContent>
122106
</Card>
123-
</motion.div>
107+
</FadeIn>
124108
</div>
125109

126110
{/* Recent Activity */}
127-
<motion.div
128-
initial={{ opacity: 0, y: 20 }}
129-
animate={{ opacity: 1, y: 0 }}
130-
transition={{ delay: 0.5 }}
131-
>
111+
<FadeIn delay={0.5}>
132112
<Card>
133113
<CardHeader>
134114
<CardTitle>Recent Activity</CardTitle>
@@ -152,7 +132,7 @@ export default function DashboardPage() {
152132
)}
153133
</CardContent>
154134
</Card>
155-
</motion.div>
135+
</FadeIn>
156136
</div>
157137
);
158138
}

backend/frontend/app/dashboard/payments/page.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useDashboardData } from '@/lib/hooks/useDashboardData';
44
import { Card, CardContent } from '@/components/ui/card';
55
import { CheckCircle2, Clock, XCircle, ExternalLink, Wallet } from 'lucide-react';
6-
import { motion } from 'framer-motion';
6+
import { FadeIn } from '@/components/ui/fade-in';
77
import { PaymentCardSkeleton } from '@/components/ui/loading-skeletons';
88
import { EmptyState } from '@/components/empty/EmptyState';
99

@@ -48,12 +48,7 @@ export default function PaymentsPage() {
4848

4949
<div className="space-y-4">
5050
{payments.map((payment, index) => (
51-
<motion.div
52-
key={payment.id}
53-
initial={{ opacity: 0, y: 20 }}
54-
animate={{ opacity: 1, y: 0 }}
55-
transition={{ delay: index * 0.05 }}
56-
>
51+
<FadeIn key={payment.id} delay={index * 0.05}>
5752
<Card className="hover:shadow-lg transition-all">
5853
<CardContent className="p-6">
5954
<div className="flex items-center justify-between">
@@ -95,7 +90,7 @@ export default function PaymentsPage() {
9590
)}
9691
</CardContent>
9792
</Card>
98-
</motion.div>
93+
</FadeIn>
9994
))}
10095
</div>
10196

0 commit comments

Comments
 (0)