The live site had been stripped to a static export (output:'export') to fit GitHub Pages, which forced removal of every dynamic feature: the @luxfi/ui Header/Footer chrome rendered as plain 'LUX' text, the @hanzo/commerce checkout, the lux.id auth API routes + middleware, and leaked gold/amber accents. Restore the full app on top of the post-quantum landing content: - Dynamic output:'standalone' Node server (next.config.mjs); drop static export. - Real @luxfi/ui Header/Footer with the canonical @luxfi/logo ▼ mark (logoVariant='full'); mega-menus + full footer. - @hanzo/commerce /checkout + commerce bag, login routed to lux.id, restored src/app/api/auth/* routes and src/middleware.ts (device + auth). - Pure monochrome: neutralize all rainbow/gold chain + app gradients to from-foreground/[0.06]; kill the gold 'Bridge Assets' button. - site-def: re-enable auth (login->lux.id) + commerce (both stripped before). Fixes uncovered along the way: - patch @hanzo/[email protected]: invalid 'type App' in a runtime await-import destructure broke the auth API route compile. - next.config SVGR loader: @luxfi/ui's footer imports an inline .svg as a component; without the loader it resolves to an object -> hydration crash (black page). This was the real root cause behind a cascade of symptoms. - ShowBody MutationObserver: @luxfi/ui RootLayout ships <body display:none> and reveals it from an effect that loses a hydration race on standalone; pin it. - src/middleware: react-device-detect throws on UA-less requests (k8s probes, curl) -> 500; default to 'desktop' and add a static-asset matcher. Build, real-browser verify (e2e/restore-verify.mjs + rewritten homepage.spec): logo, 0 gold/amber, mega-menu, full footer, checkout host, login->lux.id, no console crash, monochrome — 8/8 green. Dockerfile = standalone Node runner.
4.9 KiB
network — lux.network
Org: lux-apps · Ecosystem: lux · Path: /Users/a/work/lux/lux-apps/network
Origin: https://github.com/luxfi/lux-network.git (also checked out at ~/work/lux/luxfi/lux-network)
Package: @luxfi/network · Live: https://lux.network (Cloudflare-fronted)
CLAUDE.md is a symlink to this file.
What this is
The flagship lux.network site: a full dynamic Next.js 14 app (NOT a static
export). Real @luxfi/ui Header/Footer chrome (Lux ▼ logo + mega-menus),
@hanzo/commerce crypto-native /checkout, @hanzo/auth login routed to
lux.id, auth API routes, device/auth middleware, and a pure-monochrome
theme (zero gold/amber/orange). The post-quantum landing content (14 chains,
hero copy) sits on top of that full chrome.
Architecture (one way)
- Dynamic,
output: 'standalone'Node server (next.config.mjs). The app has API routes (src/app/api/auth/{login,logout,set-auth-token,clear-auth-token}),src/middleware.ts(determineDeviceMW), SSR chrome, and SSR commerce — none of which work underoutput: 'export'. Do not re-introduce static export. - Pages:
/(HomeContent),/[slug](coin, validator — server segment → clientProductDetailContent),/chain/[id]+/docs/[chain](14 chains, SSG),/docs,/checkout(force-dynamic). - Chrome lives in client components. The
@luxfi/uiHeader/Footer nav passes function props that cannot cross an RSC server→client boundary, so every page that renders them is a client component (or a server segment that delegates to one). Route segments that embed auth/commerce areexport const dynamic = 'force-dynamic'. - Theme: monochrome only.
src/data/chains.tschain cards +pageuse a single neutralfrom-foreground/[0.06]gradient — never per-chain rainbow, never gold/amber/orange. Hero CTAs arebg-primary/bg-accent/outline (all monochrome in the dark theme). - Login → lux.id:
@hanzo/authAuthWidget builds${NEXT_PUBLIC_LOGIN_SITE_URL}?redirectUrl=…. SetNEXT_PUBLIC_LOGIN_SITE_URL=https://lux.id/loginat build time (Dockerfile ARG + CI env) or the link bakes asundefined.
Required patches (patches/, via pnpm patchedDependencies)
@luxfi/[email protected]— point the header/footerLuxLogoat the canonical@luxfi/logo▼ mark (monochrome,variant='white').@hanzo/[email protected]— fix invalid TS inserver/firebase-support.ts(const { …, type App } = await import(...)→ drop the inlinetypefrom the runtime destructure; the build otherwise fails to compile the auth API routes).
Two non-obvious gotchas (do not regress)
- SVGR loader is required.
next.config.mjsadds the@svgr/webpackrule so*.svgimports become React components.@luxfi/ui's footer (community column) imports an inline.svgas a component; without the loader it resolves to an asset object and React throws "Element type is invalid … got: object" on hydration (whole page goes black). This was the real cause behind a cascade of red-herring symptoms — fix it here, not by stripping features. - Body un-hide.
@luxfi/ui's RootLayout ships<body style="display:none">and relies on its<Analytics/>effect to reveal it; that loses a hydration race on the standalone build, leaving the page invisible.src/components/ShowBody.tsxpins the body visible with aMutationObserver. Keep it mounted inlayout.tsx.
Build & verify
# build the standalone server (validates the whole app compiles)
NEXT_PUBLIC_LOGIN_SITE_URL=https://lux.id/login pnpm build # -> .next/standalone/server.js
# run it like the container does (copy assets, then serve)
cp -r .next/static .next/standalone/.next/static && cp -r public .next/standalone/public
( cd .next/standalone && PORT=3000 NEXT_PUBLIC_LOGIN_SITE_URL=https://lux.id/login node server.js )
# real-browser proof: logo, 0 gold, mega-menu, checkout, login→lux.id, no crash
BASE=http://localhost:3000 node e2e/restore-verify.mjs
Gotcha when testing locally: kill stale node server.js by port (lsof -tiTCP:<port> -sTCP:LISTEN | xargs kill -9), not pkill -f — detached servers
serve an old build's HTML with mismatched chunk hashes → all JS 400s → black page.
Deploy (unified infra)
Dockerfile → multi-stage Node runner baking .next/standalone + .next/static
public. CI on luxfi/lux-network (lux-buildself-hosted ARC runner on evo — no GitHub builders) publishesghcr.io/luxfi/lux-network:<semver>; the tag is pinned inluxfi/universe(k8s/lux-web/lux-network.yaml, namespacelux-web), reconciled ontodo-sfo3-lux-k8sbehind hanzoai/ingress + cert-manager, fronted by Cloudflare (Alux.network→ ingress LB, proxied). The universe Deployment's container/probes must target the Node server (port 3000, probe/) — NOT the oldhanzoai/static+/healthzstatic-export variant.
Sibling repos
See /Users/a/work/lux/lux-apps/LLM.md.