Skip to content

[WIP] feat: migrate from Next.js 12 to Vite + React Router 7 + Fastify - #1142

Open
DiRaiks wants to merge 8 commits into
developfrom
feature/simple
Open

[WIP] feat: migrate from Next.js 12 to Vite + React Router 7 + Fastify#1142
DiRaiks wants to merge 8 commits into
developfrom
feature/simple

Conversation

@DiRaiks

@DiRaiks DiRaiks commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Description

Full stack replacement behind an unchanged product surface (Vite + React SPA + Fastify").
Feature code stays in place — next/* imports resolve to thin shims (react-router 7 / react-helmet-async), so app sources are ported, not rewritten.

Frontend:

  • Vite 8 build, SPA via createBrowserRouter (hash router for IPFS — replaces the hand-rolled fragment routing incl. HomePageIpfs)
  • app/ bootstrap + route table mirroring pages/ (no /referral, no 500)
  • runtime env keeps the window.env contract; dynamics normalize() handles raw-string values from the nginx entrypoint
  • SEO: head-only prerender per route + sitemap.xml + robots.txt + JSON-LD; PUBLIC_ORIGIN placeholder resolved per env by nginx sub_filter
  • getStaticProps data moved to runtime: manifest via react-query (as before), validation blocklist now fetched from /api/validation-file

API (server/ workspace, Fastify 5, esbuild bundle):

  • ports all 8 pages/api routes incl. the security-critical /api/rpc with allowlists derived from getContractAddress() (all supported chains, batch cap 20, usde events) — networks-map decoupled from next/config
  • earn vaults-apr/tvl compute for real, reusing the frontend fetchers (vault consts split into pure consts-data, standardFetcher unbarreled)
  • satanizer masking for all logs, eth_stake_widget_ui_ metrics preserved (ABI-decoded eth_call labels, startup checks, build info)
  • legacy semantics kept: Cache-Control values, 405 + Allow, per-route CORS
  • new GET /api/validation-file (was FS read in getStaticProps)

Infra:

  • Dockerfile.web (nginx-unprivileged, static) + Dockerfile.api (node)
  • nginx: security headers via shared include (add_header inheritance fix), CSP assembled from CSP_* env in entrypoint, s-maxage typo fix, manifest.json/token-lists CORS parity, SELF_ORIGIN guardrails

Removed: next + plugins, server.mjs, pages/, utilsApi/, config/csp, webpack loaders, .babelrc, next-logger (masking moved into the api).

Verified: tsc 0 errors (root+api), eslint clean, 410+31 unit tests, web +
IPFS + api builds, api smoke (health/metrics/405/rpc allowlist).

Demo

Code review notes

Testing notes

Checklist:

  • Checked the changes locally.
  • Created / updated analytics events.
  • Created / updated the technical documentation (README.md / docs / etc.).
  • Affects / requires changes in other services (Matomo / Sentry / CloudFlare / etc.).

@DiRaiks
DiRaiks requested a review from a team as a code owner July 21, 2026 15:42
Full stack replacement behind an unchanged product surface (Vite + React
SPA + Fastify").
Feature code stays in place — `next/*` imports resolve to thin shims
(react-router 7 / react-helmet-async), so app sources are ported, not
rewritten.

Frontend:
- Vite 8 build, SPA via createBrowserRouter (hash router for IPFS —
  replaces the hand-rolled fragment routing incl. HomePageIpfs)
- app/ bootstrap + route table mirroring pages/ (no /referral, no 500)
- runtime env keeps the window.__env__ contract; dynamics normalize()
  handles raw-string values from the nginx entrypoint
- SEO: head-only prerender per route + sitemap.xml + robots.txt +
  JSON-LD;
  __PUBLIC_ORIGIN__ placeholder resolved per env by nginx sub_filter
- getStaticProps data moved to runtime: manifest via react-query (as
  before), validation blocklist now fetched from /api/validation-file

API (server/ workspace, Fastify 5, esbuild bundle):
- ports all 8 pages/api routes incl. the security-critical /api/rpc with
  allowlists derived from getContractAddress() (all supported chains,
  batch cap 20, usde events) — networks-map decoupled from next/config
- earn vaults-apr/tvl compute for real, reusing the frontend fetchers
  (vault consts split into pure consts-data, standardFetcher unbarreled)
- satanizer masking for all logs, eth_stake_widget_ui_ metrics preserved
  (ABI-decoded eth_call labels, startup checks, build info)
- legacy semantics kept: Cache-Control values, 405 + Allow, per-route
  CORS
- new GET /api/validation-file (was FS read in getStaticProps)

Infra:
- Dockerfile.web (nginx-unprivileged, static) + Dockerfile.api (node)
- nginx: security headers via shared include (add_header inheritance
  fix),
  CSP assembled from CSP_* env in entrypoint, s-maxage typo fix,
  manifest.json/token-lists CORS parity, SELF_ORIGIN guardrails

Removed: next + plugins, server.mjs, pages/**, utilsApi/**, config/csp,
webpack loaders, .babelrc, next-logger (masking moved into the api).

Verified: tsc 0 errors (root+api), eslint clean, 410+31 unit tests, web
+
IPFS + api builds, api smoke (health/metrics/405/rpc allowlist).
One workflow, matrix over both images (staking-widget-web / -api),
reusing
the org k8s-build-push-harbor workflow with this repo's
harbor_dev_release
environment. Triggers on pushes to feature/simple; pushes to the same
Harbor repos the existing `staking-widget` helm release (PoC stand)
watches via ArgoCD Image Updater — no chart changes needed.

Align Dockerfile.api runtime layout with the image the chart currently
runs (WORKDIR /app/server, `node dist/index.js`, port 3001) so the new
image is a drop-in replacement in the same pod.
@DiRaiks
DiRaiks requested a review from a team as a code owner July 21, 2026 15:53
@DiRaiks
DiRaiks had a problem deploying to harbor_dev_release July 21, 2026 15:53 — with GitHub Actions Failure
@DiRaiks
DiRaiks had a problem deploying to harbor_dev_release July 21, 2026 15:53 — with GitHub Actions Failure
@DiRaiks
DiRaiks temporarily deployed to harbor_dev_release July 21, 2026 15:57 — with GitHub Actions Inactive
@DiRaiks
DiRaiks temporarily deployed to harbor_dev_release July 21, 2026 15:57 — with GitHub Actions Inactive
DiRaiks added 2 commits July 21, 2026 19:46
FAQ texts move to faq-data.ts — the single source rendered BOTH by the
React accordions (faq-list.tsx; internal links upgraded to SPA
navigation, data-matomo click tracking preserved) and by the build-time
prerender: /withdrawals/request (25 Q&A) and /withdrawals/claim (13)
now carry the full FAQ in the served body for crawlers and no-JS
readers, plus FAQPage structured data in the head.

The body markup is replaced (never hydrated) on mount via createRoot,
so the no-hydration invariant holds. The min/max-amounts entry stays a
live component (contract-read numbers) and is excluded from statics.
Removes the 26 per-question components superseded by the data module.
The newline check used `*"$(printf '\n')"*` — command substitution
strips
the trailing newline, degrading the pattern to `*""*`, which matches any
string, so the container exited on startup with "forbidden characters".
Use a literal-newline variable instead; verified the full entrypoint in
a
sandbox (window-env.js, sub_filter, CSP render, no unsubstituted vars).
@DiRaiks
DiRaiks temporarily deployed to harbor_dev_release July 21, 2026 16:47 — with GitHub Actions Inactive
@DiRaiks
DiRaiks temporarily deployed to harbor_dev_release July 21, 2026 16:47 — with GitHub Actions Inactive
Port develop's config-manifest-from-file feature to the Fastify
workspace: /api/config-manifest route, CONFIG_MANIFEST_PATH file
source with last-known-good fallback, load-error metric, startup
check, useConfigManifestFile runtime flag (window.__env__ / nginx
entrypoint). Relative CONFIG_MANIFEST_PATH is normalized against the
repo root in load-env (parity with develop, where the Next server ran
from the repo root; absolute k8s configmap paths pass through).

Convert new allocation icon libraries to Vite ?react imports. Drop
getStaticProps-era shouldRedirectToRoot (its only consumer was
deleted with pages/). Fix double histogram observation per failed
manifest fetch attempt.
@preview-stands

preview-stands Bot commented Aug 4, 2026

Copy link
Copy Markdown

Preview stand status

Deploying...

The develop merge switched the repo to a committed Yarn pinned by
yarnPath in .yarnrc.yml; the web/api Dockerfiles only copied
package.json/yarn.lock/.yarnrc.yml before install, so every yarn
invocation died with ENOENT on .yarn/releases/yarn-4.17.1.cjs.
Copy .yarn/releases alongside and drop corepack — the node image's
stock Yarn 1 delegates to the vendored release (same pattern as
develop's Dockerfile.k8s).
@DiRaiks
DiRaiks temporarily deployed to harbor_dev_release August 4, 2026 15:34 — with GitHub Actions Inactive
@DiRaiks
DiRaiks temporarily deployed to harbor_dev_release August 4, 2026 15:34 — with GitHub Actions Inactive
Audit fixes:
- security: mask viem errors in every server console.* (RPC keys leaked
  to stdout on upstream failures); JSON-escape all window-env.js values
  and sed-escape CSP vars in the nginx entrypoint (a quote broke the
  app,
  `&` in report-uri corrupted the CSP / crashed the pod)
- router semantics vs Next: searchQuery in the router shim (route params
  leaked into redirect/replace query strings), useMatch-based earn-vault
  checks (4 dead bracket-pattern sites), basePath from BASE_URL,
  ScrollRestoration, IPFS tab-active compare, dev:ipfs env sync
- parity: 405+Allow on disabled proxy routes; bounded methodEncoded
  metric
  labels; SUPPORTED_CHAINS default aligned with env-dynamics (Hoodi
  only);
  CoW child-src + media-src none per infra CSP; stale-while-revalidate
  restored; upstream keepalive for /api/; 17 rpc-route tests
- perf: route-level code splitting (only home in the entry chunk);
  NoSSRWrapper dropped (no SSR exists); reactive useRouterPath
- cleanup: dead Next-era files/branches removed (consts/metrics,
  generate-build-id, revalidation group, next-app shim, isClientSide,
  4 next/link sites -> plain anchors)

Address validation reworked (three layers):
1. /api/validation: external service, then blocklist-file fallback
   server-side (broken file fail-closed), X-Validation-Source header;
   /api/validation-file route removed
   2. api pod down (web + wallet RPCs survive a DDoS): SPA falls back to
   /runtime/validation.json — the same configmap served statically by
   the web pod nginx, prefetched per session
3. blocklist ships sha256-hashed only (helm hashes at template render,
   write-window-env.mjs in dev); matcher accepts plain and hashed
   entries

Plus: cached-proxy surfaces undici cause codes in 502 bodies (`error`
key only — extractErrorMessage must not leak proxy internals into UI);
relative CONFIG_MANIFEST_PATH/VALIDATION_FILE_PATH resolved from repo
root; startup RPC metrics silent when checks are disabled.
@DiRaiks
DiRaiks temporarily deployed to harbor_dev_release August 5, 2026 12:20 — with GitHub Actions Inactive
@DiRaiks
DiRaiks temporarily deployed to harbor_dev_release August 5, 2026 12:20 — with GitHub Actions Inactive
- centralize all QA localStorage keys in consts/qa-keys.ts (typed
  registry with groups/descriptors; unit test forbids inline key
  literals outside the registry)
- drawer opens with 5 quick taps on the footer or Ctrl+Shift+8, only
  when enableQaHelpers is on; sections: feature flags (applied live),
  QA mocks with per-type input validation, external manifest mock,
  read-only config / window.__env__ / manifest snapshot
- manifest mock is validated with the real zod ManifestEntrySchema and
  fail-closed to the fetched config; it deliberately does not affect
  the security-banner kill-switch (reads raw fetchMeta)
- extract shared right-drawer shell (shared/components/drawer) from
  the earn side panel; earn DrawerRight reuses it, markup unchanged
- revive rpcSettingsPageOnInfraIsEnabled: gates /settings and the
  header gear on infra builds, toggleable live from the drawer
- drawer ships as a lazy chunk; tests: registry invariants, utils/qa
  gating, manifest-mock overlay (498 unit tests green)
@DiRaiks
DiRaiks temporarily deployed to harbor_dev_release August 6, 2026 10:08 — with GitHub Actions Inactive
@DiRaiks
DiRaiks temporarily deployed to harbor_dev_release August 6, 2026 10:08 — with GitHub Actions Inactive
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.

1 participant