diff --git a/src/main.tsx b/src/main.tsx
index fe9bf50..ec8b30a 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,14 +1,38 @@
-import React from "react";
+import React, { Suspense, lazy } from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { WalletProvider } from "./contexts/WalletContext";
import { i18nReady } from "./i18n";
import App from "./App";
-import Help from "./pages/Help";
-import Ranking from "./pages/Ranking";
import "./App.css";
import "./styles/theme.css";
+// Heavy routes (Mapbox GL, ZK/WASM prover, Stellar RPC) are code-split so they
+// are only fetched when the user actually navigates to them, keeping the
+// initial bundle and Time-To-Interactive low.
+const Help = lazy(() => import("./pages/Help"));
+const Ranking = lazy(() => import("./pages/Ranking"));
+
+function RouteFallback() {
+ return (
+
+ Loading…
+
+ );
+}
+
function render() {
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
@@ -20,11 +44,13 @@ function render() {
*/}
-
- } />
- } />
- } />
-
+ }>
+
+ } />
+ } />
+ } />
+
+
,
diff --git a/src/pages/Help.tsx b/src/pages/Help.tsx
index be2146e..5ef3490 100644
--- a/src/pages/Help.tsx
+++ b/src/pages/Help.tsx
@@ -32,11 +32,6 @@ import {
subscribeToContractEvents,
clearWalletAddress,
} from "../lib/contract";
-import {
- buildLocationProofZone,
- generateLocationProof,
- shortProofId,
-} from "../lib/zk";
import useDocumentTitle from "../lib/useDocumentTitle";
import Button from "../components/ui/Button";
import Input from "../components/ui/Input";
@@ -46,6 +41,25 @@ import "./Help.css";
const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_TOKEN;
+// The ZK prover (Noir + Barretenberg WASM, several MB) is only fetched once a
+// proof is actually requested. Everything else in the module (zone building,
+// proof-id truncation) is cheap, but keeping the whole module out of the
+// initial bundle avoids pulling @stellar/stellar-sdk and WASM into the route
+// chunk until proof generation begins.
+let _zkModule: typeof import("../lib/zk") | null = null;
+function loadZk() {
+ if (!_zkModule) {
+ _zkModule = import("../lib/zk");
+ }
+ return _zkModule;
+}
+
+function shortProofId(value: unknown): string {
+ const text = String(value || "");
+ if (text.length <= 18) return text;
+ return `${text.slice(0, 10)}...${text.slice(-6)}`;
+}
+
const MAP_STYLES = [
{
id: "satellite",
@@ -2714,11 +2728,13 @@ export default function Help() {
address,
radiusMeters = 3000,
}) {
- const zone = buildLocationProofZone({ lat, lng, radiusMeters });
dispatchZk({ type: "SET_STATUS", payload: "proving" });
dispatchZk({ type: "SET_ERROR", payload: "" });
dispatchZk({ type: "PUSH_LOG", payload: "Preparing private witness" });
- const proof = await generateLocationProof({
+ // Load the ZK prover only now that a proof is actually requested.
+ const zk = await loadZk();
+ const zone = zk.buildLocationProofZone({ lat, lng, radiusMeters });
+ const proof = await zk.generateLocationProof({
lat,
lng,
campaignId,
diff --git a/vite.config.ts b/vite.config.ts
index 94536e7..e6cd430 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -140,6 +140,7 @@ export default defineConfig({
rollupOptions: {
output: {
manualChunks(id) {
+ if (!id.includes("node_modules")) return undefined;
if (id.includes("mapbox-gl") || id.includes("react-map-gl"))
return "mapbox";
if (
@@ -149,6 +150,18 @@ export default defineConfig({
return "stellar";
if (id.includes("@noir-lang") || id.includes("@aztec/bb.js"))
return "zk";
+ if (
+ id.includes("react") ||
+ id.includes("react-dom") ||
+ id.includes("react-router") ||
+ id.includes("scheduler") ||
+ id.includes("react-i18next") ||
+ id.includes("i18next")
+ )
+ return "react-vendor";
+ if (id.includes("@supabase")) return "supabase";
+ if (id.includes("buffer")) return "buffer";
+ return "vendor";
},
},
},