Problem Statement
Heavy charting packages and crypto parsing bundles degrade page loads on slow mobile connections. The current monolithic bundle weighs 2.4 MB (uncompressed) and includes all charting libraries (Recharts, d3, vis-network), crypto libraries (@stellar/stellar-sdk, tweetnacl, bignumber.js), and the full UI component tree — all loaded on every page regardless of whether the user lands on the login page or the analytics dashboard. First Contentful Paint on 3G is 8.2 seconds, well above the 2.5s target.
Technical Bounds & Invariants
- Target FCP on 3G (400 kbps, 400ms RTT): < 2.5 seconds
- Target Time to Interactive (TTI) on 3G: < 4 seconds
- Maximum critical bundle size for initial route (login): < 100 KB gzipped
- Code splitting granularity: route-based + component-level (dynamic import for heavy components)
- Framework: Next.js 14 App Router (supports
next/dynamic and React.lazy natively)
Codebase Navigation Guide
- Primary target:
/src/app/dashboard/layout.tsx
- Route structure:
/src/app/(auth)/login/page.tsx, /src/app/(dashboard)/dashboard/page.tsx, /src/app/(analytics)/analytics/page.tsx
- Current import map:
/src/app/layout.tsx — global imports at lines 1-20
- Bundle analysis config:
/next.config.js — withBundleAnalyzer at line 10
Step-by-Step Resolution Blueprint
- Analyze the current bundle composition using
@next/bundle-analyzer to identify the top 10 largest modules; record baseline sizes in a ticket comment
- Configure
next/dynamic with ssr: false for all charting components: const AttestationChart = dynamic(() => import('@/components/charts/AttestationPerformance'), { ssr: false, loading: () => <ChartSkeleton /> }) — charts are loaded only on the analytics route
- Create a lazy-loaded
CryptoCore module that defers @stellar/stellar-sdk import to when wallet interaction is first triggered (not on page load): const StellarSdk = await import('@stellar/stellar-sdk') called inside useWeb3Auth().login() — the SDK bundle is fetched only when the user clicks "Connect Wallet"
- Add route group layout splitting: wrap
(auth) routes in a minimal layout that imports only login-specific CSS and components; wrap (dashboard) routes in the full layout with sidebar and nav; wrap (analytics) with analytics-specific heavy imports
- Configure
experimental.nextScriptWorkers: true in next.config.js and move non-critical third-party scripts (Sentry, analytics, hotjar) to a Partytown web worker
- Add resource hints:
<link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2" crossorigin> for critical fonts; add <link rel="modulepreload" href="/_next/static/chunks/app/(dashboard)/layout.js"> for the most common landing route after login
- Set a performance budget CI check: add a
Lighthouse CI step in GitHub Actions that asserts: FCP < 2.5s, TBT < 200ms, LCP < 4s on a simulated 3G connection; fail the build if any budget is exceeded
Problem Statement
Heavy charting packages and crypto parsing bundles degrade page loads on slow mobile connections. The current monolithic bundle weighs 2.4 MB (uncompressed) and includes all charting libraries (Recharts, d3, vis-network), crypto libraries (
@stellar/stellar-sdk,tweetnacl,bignumber.js), and the full UI component tree — all loaded on every page regardless of whether the user lands on the login page or the analytics dashboard. First Contentful Paint on 3G is 8.2 seconds, well above the 2.5s target.Technical Bounds & Invariants
next/dynamicand React.lazy natively)Codebase Navigation Guide
/src/app/dashboard/layout.tsx/src/app/(auth)/login/page.tsx,/src/app/(dashboard)/dashboard/page.tsx,/src/app/(analytics)/analytics/page.tsx/src/app/layout.tsx— global imports at lines 1-20/next.config.js—withBundleAnalyzerat line 10Step-by-Step Resolution Blueprint
@next/bundle-analyzerto identify the top 10 largest modules; record baseline sizes in a ticket commentnext/dynamicwithssr: falsefor all charting components:const AttestationChart = dynamic(() => import('@/components/charts/AttestationPerformance'), { ssr: false, loading: () => <ChartSkeleton /> })— charts are loaded only on the analytics routeCryptoCoremodule that defers@stellar/stellar-sdkimport to when wallet interaction is first triggered (not on page load):const StellarSdk = await import('@stellar/stellar-sdk')called insideuseWeb3Auth().login()— the SDK bundle is fetched only when the user clicks "Connect Wallet"(auth)routes in a minimal layout that imports only login-specific CSS and components; wrap(dashboard)routes in the full layout with sidebar and nav; wrap(analytics)with analytics-specific heavy importsexperimental.nextScriptWorkers: trueinnext.config.jsand move non-critical third-party scripts (Sentry, analytics, hotjar) to a Partytown web worker<link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2" crossorigin>for critical fonts; add<link rel="modulepreload" href="/_next/static/chunks/app/(dashboard)/layout.js">for the most common landing route after loginLighthouse CIstep in GitHub Actions that asserts: FCP < 2.5s, TBT < 200ms, LCP < 4s on a simulated 3G connection; fail the build if any budget is exceeded