5.9 KiB
lux/explore — Vite 8 SPA migration (WIP scaffold)
Status: Scaffold only. The Next.js app under ../pages is still the production build.
Why a scaffold, not a rewrite
A full Next.js → Vite conversion of this codebase is a multi-week project, not a one-shot:
| Coupling point | Count |
|---|---|
Total next/* imports |
427 |
Files using next/router |
177 |
Files using nextjs-routes generated types |
154 |
Pages with getServerSideProps |
105 |
pages/ file-system routes |
99 |
pages/api/ server routes |
9 |
Plus: the Next.js runtime container has a custom entrypoint.sh that generates
/assets/envs.js, envs-validator, sitemap-generator, favicon-generator,
and og-image-generator — all tied to next start. ghcr.io/hanzoai/spa is
pure static serving; none of those runtime helpers run inside it.
What this scaffold provides
vite/package.json— Vite 8 + React 19.2 + wouter, no Next depsvite/vite.config.ts— aliases point at the existing../lib,../ui,../configs,../toolkit,../nextjstrees so source can be migrated file by file instead of movedvite/tsconfig.json— mirrors the aliases for TypeScriptvite/index.html— security headers as<meta>tags (wasnextjs/headers.js)vite/src/env.ts— runtime env resolution, preserves thewindow.__envscontract from/assets/envs.jsand addsVITE_API_BASE_URLfallbackvite/src/redirects.ts— port ofnextjs/redirects.jsas a client-side match + substitute table (69 rules, 4 deprecated permanent)vite/src/App.tsx— wouter router with redirect resolver + landing pagevite/Dockerfile— 2-stage build →ghcr.io/hanzoai/spa
What's NOT migrated
These are the tasks the next engineer must complete before this replaces the production Next.js app:
-
99 pages. Every file in
../pages/*.tsxneeds to become a wouter<Route>insrc/App.tsx. The component bodies usually transfer as-is because Blockscout components are already client-heavy — the hard part is:next/router(177 files) →useLocation+useSearchfrom wouternextjs-routes(154 files) → a hand-maintainedRoutetype uniongetServerSideProps(105 files) → React QueryuseQueryhooks against the public API. The existingnextjs/getServerSideProps/main.tshelpers mostly just pre-fetched data that's also available client-side.next/dynamic(heavy use in_app.tsx) →React.lazy+Suspense
-
9 API routes. These run server-side in Next.js; none can run in a static SPA. Options per route:
/api/config— runtime env injection. Replace with the SPA's existing/assets/envs.jspattern (thehanzoai/spaimage can template this at container boot)./api/pchain— CORS-avoiding proxy for JSON-RPC. Either (a) enable CORS on the gateway for/v1/bc/P/*, or (b) deploy a separate tiny Go proxy alongside the SPA. Option (a) is cleaner./api/csrf,/api/proxy— Blockscout account API auth. Must move to the same pattern: gateway-side, or the account API itself exposes them./api/metrics— Prometheus. Drop for frontend, or scrape the pod directly./api/log,/api/healthz,/api/monitoring/*,/api/tokens/*— re-home case by case. Most can be deleted — they were convenience endpoints.
-
Runtime container tooling. The existing
deploy/scripts/entrypoint.shwires together envs-validator, sitemap-generator, favicon-generator. These need either:- Moved to build-time (preferred — bake favicons + sitemap into
dist/during Vite build), or - Rewritten as init-containers in K8s that populate the shared static
volume before
hanzoai/spareads it.
- Moved to build-time (preferred — bake favicons + sitemap into
-
next/imageusage. There are ~60 imports. Replace with<img>+ Vite's static asset imports, or addvite-imagetoolsif per-size variants matter. -
next-themes. Replace with@hanzo/guitheme provider used by cloud/web. -
Tailwind. Already configured via
postcss.config.mjs— should Just Work oncevite/src/styles.csspulls in the same@tailwinddirectives.
Estimated migration effort
- % of UI preserved at full completion: ~85%. Blockscout's components are
framework-agnostic React; only route wiring changes. The remaining 15% is
_app.tsxproviders,_document.tsx(replaced byindex.html), and pages that lean ongetServerSidePropsfor SEO-critical data. - Time to ship: ~2–4 weeks for one engineer. The bulk is mechanical
transform of
router.push(url)→navigate(url)and deletinggetServerSidePropsexports. - Bail-out threshold: If upstream Blockscout merges come in, re-evaluate. A permanent fork from Blockscout means owning all feature work forever.
Critical TODOs before going live
vite buildmust produce a workingdist/(currently builds an empty landing page — not the explorer)- Smoke-test every route group:
/block/:h,/tx/:h,/address/:h,/token/:h,/stats,/chains,/bridge,/account/*,/api-docs - Wire React Query + replace
getServerSidePropswithuseQuery - Validate runtime env injection via
window.__envsfrom SPA container - Configure CSP for the SPA — current Next.js CSP in
nextjs/csp/must port - Replace
/api/pchain— enable gateway CORS OR ship a sidecar proxy - Decide favicon/sitemap generation strategy (build-time vs init-container)
universe/K8s manifests must switch image from@luxfi/explore(Next) to@luxfi/explore-vite(SPA) once parity is reached- Preserve
NEXT_PUBLIC_*env names for a release cycle to avoid breaking operator deployments — map them insidevite/src/env.ts(already done)
Reference implementation
~/work/lux/cloud/apps/web/ is the canonical Vite 8 + wouter + hanzoai/spa
pattern. That app is ~2k LOC; explore is ~176k. The patterns copy over,
the volume does not.