Skip to content

chore: bundle size shrink#32

Merged
tac0turtle merged 3 commits intomainfrom
marko/bundle_size
Mar 19, 2026
Merged

chore: bundle size shrink#32
tac0turtle merged 3 commits intomainfrom
marko/bundle_size

Conversation

@tac0turtle
Copy link
Contributor

@tac0turtle tac0turtle commented Mar 18, 2026

Overview

Bundle Size Shrink — Change Synopsis

  1. Preact replaces React (package.json, vite.config.ts)
  • Removed react, react-dom, @vitejs/plugin-react; added preact, @preact/preset-vite
  • vite.config.ts now uses @preact/preset-vite plugin + aliasing
    react/react-dom/react/jsx-runtime → preact/compat
  • Result: react-dom (177 kB) → preact/compat (~25 kB)
  1. Axios → native fetch (api/client.ts)
  • Complete replacement: axios removed, thin wrapper around fetch + AbortController
  • Same public API (client.get, client.post), same error shape (ApiError), same
    10s timeout, same Retry-After parsing
  • Key behavioral change: client now returns T directly instead of { data: T }
  1. All API modules updated (api/*.ts)
  • Removed .data unwrapping from all call sites (9 files) to match the new client
    return type
  1. Route-based code splitting (App.tsx)
  • All 15 page imports converted to React.lazy() + Suspense
  • Pages only load when first navigated to; initial bundle contains only the shell +
    shared components
  1. Vendor chunk splitting (vite.config.ts)
  • manualChunks: vendor-preact and vendor-router as separate cacheable chunks
  • Prevents vendor hash churn when app code changes
  1. Dynamic import warning fixed (AddressPage.tsx)
  • Converted await import('../api/tokens') inside a useEffect to a static module-level
    import
  • Was causing a Vite warning since tokens.ts was already statically imported
    elsewhere

Net result: Initial JS download 108 kB gzip → 17 kB gzip (~84% reduction). Build time
1.5s → 811ms.

Summary by CodeRabbit

  • New Features

    • Pages now display a loading indicator when navigating, providing user feedback during transitions.
  • Improvements

    • Optimized application performance through framework and tooling updates.
    • Streamlined pagination controls by removing the "Last page" navigation button.
    • Simplified internal API communication for better stability.

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

This pull request migrates the frontend from React to Preact, replaces the Axios HTTP client with a fetch-based implementation, and adds lazy loading for page components with Suspense boundaries. All API endpoints are refactored to use the new client while maintaining existing function signatures and behavior.

Changes

Cohort / File(s) Summary
Framework Migration
frontend/package.json, frontend/vite.config.ts
Removed React dependencies and Vite React plugin; added Preact and Preact Vite preset. Added module aliasing to map React imports to Preact compatibility layers. Introduced build-level code splitting with separate vendor chunks for Preact and router libraries.
HTTP Client Refactoring
frontend/src/api/client.ts
Replaced Axios-based HTTP client with fetch-based implementation. Added helper functions for URL building, request handling with timeout support via AbortController, and retry-after header parsing. Exported simplified client object with generic get and post methods.
API Endpoints Update
frontend/src/api/addresses.ts, frontend/src/api/blocks.ts, frontend/src/api/logs.ts, frontend/src/api/nfts.ts, frontend/src/api/proxies.ts, frontend/src/api/search.ts, frontend/src/api/status.ts, frontend/src/api/tokens.ts, frontend/src/api/faucet.ts
Refactored all API functions to return client promises directly instead of extracting and returning response.data. Consolidated imports and removed intermediate response variables. Maintained function signatures and return type compatibility.
Transactions API
frontend/src/api/transactions.ts
Simplified standard transaction endpoints to return client promises directly. Enhanced transfer endpoints (getTxErc20Transfers, getTxNftTransfers) to defensively handle responses as unknown type and normalize to array format, supporting both raw array and object-wrapped payloads.
Component Lazy Loading
frontend/src/App.tsx
Introduced React.lazy for dynamic component loading on all page routes. Added PageLoader component for loading state display. Wrapped lazy-loaded components in Suspense boundaries with PageLoader fallback.
Page Constants & UI
frontend/src/pages/BlocksPage.tsx
Extracted magic number 20 into BLOCKS_PER_PAGE constant for page size. Applied constant to useBlocks limit and data slicing logic. Removed Last page navigation button and updated pager comments.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • add faucet page #31: Modifies HTTP client implementation and error/retry handling with parseRetryAfterSeconds logic and client.get/post behavior changes.
  • feat: status page #16: Updates frontend/src/api/status.ts with getHeight and getChainStatus response type changes.
  • ci and fixes #14: Changes frontend/src/api/transactions.ts to handle getTxErc20Transfers and getTxNftTransfers responses with defensive array normalization.

Suggested reviewers

  • pthmas

Poem

🐰 Preact hops where React once tread,
Fetch now carries what Axios led,
Lazy pages load with grace,
Constants guide the race,
Lighter, faster, overhead shed! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: bundle size shrink' accurately reflects the main objective of the pull request, which focuses on reducing bundle size through React-to-Preact migration, Axios-to-fetch replacement, route-based code splitting, and vendor chunk optimization.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch marko/bundle_size
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
frontend/src/api/transactions.ts (1)

36-54: Consider logging unexpected response shapes for debugging.

The defensive extraction logic handles multiple response formats gracefully, which is good for robustness. However, silently returning an empty array when the response shape is unexpected could mask API contract changes or bugs.

Consider adding a console warning in development when the response doesn't match expected shapes:

💡 Optional improvement
 export async function getTxErc20Transfers(txHash: string): Promise<TxErc20Transfer[]> {
   const body = await client.get<unknown>(`/transactions/${txHash}/erc20-transfers`);
   if (Array.isArray(body)) return body as TxErc20Transfer[];
   if (typeof body === 'object' && body !== null && 'data' in body) {
     const data = (body as { data?: unknown }).data;
     if (Array.isArray(data)) return data as TxErc20Transfer[];
   }
+  if (import.meta.env.DEV && body !== null && body !== undefined) {
+    console.warn('Unexpected response shape for erc20-transfers:', body);
+  }
   return [];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/api/transactions.ts` around lines 36 - 54, Add a
development-only warning when the API response shape is unexpected in both
getTxErc20Transfers and getTxNftTransfers: after the existing checks that return
arrays, detect the fallback path and call a non-production log (e.g.,
console.warn guarded by NODE_ENV !== 'production' or a logger.isDev flag)
including the txHash, the endpoint string, and the actual body (or typeof body)
so developers can see unexpected response shapes instead of silently returning
[].
frontend/src/App.tsx (1)

36-50: Consider consolidating Suspense boundaries.

The current implementation wraps each route element individually with <Suspense>. This works correctly but is verbose. You could simplify by wrapping at the <Layout> level or creating a helper:

💡 Optional: Helper component to reduce repetition
function LazyRoute({ component: Component }: { component: React.LazyExoticComponent<() => JSX.Element> }) {
  return (
    <Suspense fallback={<PageLoader />}>
      <Component />
    </Suspense>
  );
}

// Usage:
<Route path="blocks" element={<LazyRoute component={BlocksPage} />} />

Alternatively, a single <Suspense> wrapping the <Outlet /> in Layout would work for most cases. The current approach is valid if you need per-route fallback customization in the future.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/App.tsx` around lines 36 - 50, The route list wraps every
element in <Suspense fallback={<PageLoader />}> which is verbose; either move a
single <Suspense fallback={<PageLoader />}> into the Layout component around the
<Outlet /> (so all routes inherit the same fallback) or implement a small helper
component (e.g., LazyRoute) that takes a lazy component prop and returns it
wrapped with <Suspense fallback={<PageLoader />>) and replace per-route wrappers
with <Route ... element={<LazyRoute component={BlocksPage} />} />; update usages
of Suspense, PageLoader, Layout, Outlet, and Route elements accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/api/client.ts`:
- Around line 66-68: The code currently returns response.json() directly in the
client function, which will throw a raw SyntaxError for 2xx responses with
invalid or empty JSON; wrap the JSON parsing in a try/catch around the
response.json() call (in the same function that currently returns
response.json() as Promise<T>) and on parse failure create and throw the
structured ApiError used elsewhere (include response.status, response.statusText
and the original parse error/message) so callers receive a consistent ApiError
instead of an unhandled SyntaxError.

---

Nitpick comments:
In `@frontend/src/api/transactions.ts`:
- Around line 36-54: Add a development-only warning when the API response shape
is unexpected in both getTxErc20Transfers and getTxNftTransfers: after the
existing checks that return arrays, detect the fallback path and call a
non-production log (e.g., console.warn guarded by NODE_ENV !== 'production' or a
logger.isDev flag) including the txHash, the endpoint string, and the actual
body (or typeof body) so developers can see unexpected response shapes instead
of silently returning [].

In `@frontend/src/App.tsx`:
- Around line 36-50: The route list wraps every element in <Suspense
fallback={<PageLoader />}> which is verbose; either move a single <Suspense
fallback={<PageLoader />}> into the Layout component around the <Outlet /> (so
all routes inherit the same fallback) or implement a small helper component
(e.g., LazyRoute) that takes a lazy component prop and returns it wrapped with
<Suspense fallback={<PageLoader />>) and replace per-route wrappers with <Route
... element={<LazyRoute component={BlocksPage} />} />; update usages of
Suspense, PageLoader, Layout, Outlet, and Route elements accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9e2e8551-9c3f-4ede-933e-ab70317bc9ce

📥 Commits

Reviewing files that changed from the base of the PR and between 6cafa1e and 636f0cd.

⛔ Files ignored due to path filters (1)
  • frontend/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • frontend/package.json
  • frontend/src/App.tsx
  • frontend/src/api/addresses.ts
  • frontend/src/api/blocks.ts
  • frontend/src/api/client.ts
  • frontend/src/api/logs.ts
  • frontend/src/api/nfts.ts
  • frontend/src/api/proxies.ts
  • frontend/src/api/search.ts
  • frontend/src/api/status.ts
  • frontend/src/api/tokens.ts
  • frontend/src/api/transactions.ts
  • frontend/src/pages/AddressPage.tsx
  • frontend/vite.config.ts

Comment on lines +66 to +68

return response.json() as Promise<T>;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle JSON parse failure on successful responses.

If the server returns a 2xx response with invalid JSON (or empty body), response.json() will reject with a SyntaxError. This would propagate as an unhandled error rather than a structured ApiError.

🛡️ Proposed defensive fix
-  return response.json() as Promise<T>;
+  try {
+    return await response.json() as T;
+  } catch {
+    throw { error: 'Invalid response from server' } as ApiError;
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return response.json() as Promise<T>;
}
try {
return await response.json() as T;
} catch {
throw { error: 'Invalid response from server' } as ApiError;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/api/client.ts` around lines 66 - 68, The code currently returns
response.json() directly in the client function, which will throw a raw
SyntaxError for 2xx responses with invalid or empty JSON; wrap the JSON parsing
in a try/catch around the response.json() call (in the same function that
currently returns response.json() as Promise<T>) and on parse failure create and
throw the structured ApiError used elsewhere (include response.status,
response.statusText and the original parse error/message) so callers receive a
consistent ApiError instead of an unhandled SyntaxError.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/src/App.tsx (1)

38-54: Optional: Reduce Suspense wrapper repetition.

Each route duplicates the same <Suspense fallback={<PageLoader />}> wrapper. A small helper could reduce boilerplate while keeping the individual boundary behavior.

♻️ Optional helper to reduce repetition
// Helper component
function LazyRoute({ component: Component }: { component: React.LazyExoticComponent<() => JSX.Element> }) {
  return (
    <Suspense fallback={<PageLoader />}>
      <Component />
    </Suspense>
  );
}

// Usage in routes:
<Route index element={<LazyRoute component={WelcomePage} />} />
<Route path="blocks" element={<LazyRoute component={BlocksPage} />} />
// ... etc
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/App.tsx` around lines 38 - 54, Many routes repeat the same
Suspense fallback with PageLoader; create a small helper component (e.g.,
LazyRoute) that wraps Suspense and renders a passed lazy component to eliminate
boilerplate. Implement LazyRoute (accepting a prop like component or children)
that returns <Suspense fallback={<PageLoader />}><Component /></Suspense>, then
replace occurrences such as element={<Suspense fallback={<PageLoader
/>}><WelcomePage /></Suspense>} with element={<LazyRoute component={WelcomePage}
/>} (and similarly for BlocksPage, BlockDetailPage, TransactionsPage,
SearchResultsPage, AddressesPage, TransactionDetailPage, AddressPage, NFTsPage,
NFTContractPage, NFTTokenPage, StatusPage, TokensPage, TokenDetailPage,
FaucetPage, NotFoundPage>).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/App.tsx`:
- Around line 24-30: The loader's text color in PageLoader doesn't adapt to dark
mode; update the JSX rendering inside the PageLoader component to use a Tailwind
dark-mode variant for the span's text class (e.g., add a dark:... class
alongside text-gray-500) so the loader text is readable in both light and dark
themes.

---

Nitpick comments:
In `@frontend/src/App.tsx`:
- Around line 38-54: Many routes repeat the same Suspense fallback with
PageLoader; create a small helper component (e.g., LazyRoute) that wraps
Suspense and renders a passed lazy component to eliminate boilerplate. Implement
LazyRoute (accepting a prop like component or children) that returns <Suspense
fallback={<PageLoader />}><Component /></Suspense>, then replace occurrences
such as element={<Suspense fallback={<PageLoader />}><WelcomePage /></Suspense>}
with element={<LazyRoute component={WelcomePage} />} (and similarly for
BlocksPage, BlockDetailPage, TransactionsPage, SearchResultsPage, AddressesPage,
TransactionDetailPage, AddressPage, NFTsPage, NFTContractPage, NFTTokenPage,
StatusPage, TokensPage, TokenDetailPage, FaucetPage, NotFoundPage>).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0fb4f61a-8a3a-4426-a114-f4d090c71eff

📥 Commits

Reviewing files that changed from the base of the PR and between 636f0cd and 88c3e76.

📒 Files selected for processing (4)
  • frontend/src/App.tsx
  • frontend/src/api/faucet.ts
  • frontend/src/api/status.ts
  • frontend/src/pages/BlocksPage.tsx

Comment on lines +24 to +30
function PageLoader() {
return (
<div className="flex items-center justify-center h-64">
<span className="text-gray-500 text-sm">Loading...</span>
</div>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider dark mode support for the loader text.

The text-gray-500 class won't adapt when dark mode is active. Since the app uses ThemeProvider, consider adding a dark mode variant.

🎨 Proposed fix for dark mode support
 function PageLoader() {
   return (
     <div className="flex items-center justify-center h-64">
-      <span className="text-gray-500 text-sm">Loading...</span>
+      <span className="text-gray-500 dark:text-gray-400 text-sm">Loading...</span>
     </div>
   );
 }

Based on learnings: "Frontend uses Tailwind CSS for styling across all components and pages."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function PageLoader() {
return (
<div className="flex items-center justify-center h-64">
<span className="text-gray-500 text-sm">Loading...</span>
</div>
);
}
function PageLoader() {
return (
<div className="flex items-center justify-center h-64">
<span className="text-gray-500 dark:text-gray-400 text-sm">Loading...</span>
</div>
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/App.tsx` around lines 24 - 30, The loader's text color in
PageLoader doesn't adapt to dark mode; update the JSX rendering inside the
PageLoader component to use a Tailwind dark-mode variant for the span's text
class (e.g., add a dark:... class alongside text-gray-500) so the loader text is
readable in both light and dark themes.

@tac0turtle tac0turtle merged commit b15a481 into main Mar 19, 2026
8 checks passed
@tac0turtle tac0turtle deleted the marko/bundle_size branch March 19, 2026 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants